如何递归枚举添加到给定总和的所有子集?
Posted
技术标签:
【中文标题】如何递归枚举添加到给定总和的所有子集?【英文标题】:How do I recursively enumerate all subsets adding to a given sum? 【发布时间】:2020-04-12 07:34:31 【问题描述】:当我尝试获取输出时,它会显示具有相同元素的相同解决方案几次,然后再转到另一个解决方案。
我想从等于 sum 的数组中得到不同的解
例如,
解决方案 1 = 14、8
解决方案 2 = 14、5、3
解决方案 3 = 13、9
等等,但我得到的是 22 总和的重复解决方案。我收到此错误
解决方案: 14, 8
解决方案: 14, 8
解决方案: 14, 8
解决方案: 14, 8
解决方案: 14, 8
解决方案: 14, 8
解决方案: 14, 5, 3
解决方案: 13, 6, 3
解决方案: 13, 5, 4
解决方案: 13, 5, 4
解决方案: 13, 6, 3
解决方案: 13, 5, 4
解决方案: 13, 5, 4
还有另外 20 行相同的输出。
#include <iostream>
#include <cstdlib>
#include <iomanip>
#include <vector>
#include <algorithm>
using namespace std;
void printSolution(int* r, int k)
cout << " Solution : ";
for (int j = 0; j < k; j++)
cout << r[j];
if (j < k - 1)
cout << ", ";
cout << " " << endl;
bool subSetSum(int* a, int n, int i, int sum, int* r, int k)
if (sum == 0)
printSolution(r, k);
if (i > n)
return false;
r[k++] = a[i];
if (subSetSum(a, n, i + 1, sum - a[i], r, k))
return true;
k -= 1;
subSetSum(a, n, i + 1, sum, r, k);
int descendingOrder(const void* a, const void* b)
int* p1 = (int*)a;
int* p2 = (int*)b;
return *p2 - *p1;
int main()
int a[] = 4, 10, 7, 12, 6, 10, 10, 8, 5, 13, 13, 11, 3, 14 ;
int n = 14;
int* r = new int[n];
int k = 0;
qsort(a, n, sizeof(int), descendingOrder);
cout << " Array a[] = ";
for (int count = 0; count < n; count++)
cout << a[count] << " ";
cout << endl << endl;
int sum = 22;
bool solFound = subSetSum(a, n, 0, sum, r, k);
return 0;
【问题讨论】:
那么,您的问题到底是什么? 我想得到等于 22 的不同解决方案,例如解决方案 1、14 + 8 = 22 解决方案 2、13、6、3 = 22 等等 是的,但这不是问题。您希望人们只为您编写代码吗?这并不是 *** 的真正用途。请展示您尝试过的方法以及为什么它不起作用。 我很抱歉。我将发布另一个问题,其中包含更好的输入和更具体的信息。我认为这个缺乏信息 @bitmask 我不想在这里太苛刻,但对我来说,“我想从等于 sum 的数组中得到不同的解决方案”一点也不明白。显然,我们谈论的是一个总和为某个值的数组,但这仍然很模糊。他从 14 和 8 的总和为 22 开始,但他为什么不从只包含 22 的数组开始呢?数组可以有多少条目?真的所有的组合都会产生 22 吗?我想我们只谈论自然数?还有,what 的输出?一个可能的“什么”是“一个做这个和那个的函数”。 【参考方案1】:您的subsetSum
函数中有几个错误。首先,您的版本有一个不返回值的分支。 enabling compiler warnings 可以轻松缓解这种情况。
其次,您的终止条件中有一个错误。 i==n
是一个无效的索引,所以你会溢出你的缓冲区。
您多次获得相同结果的原因是因为有多个路径可以通向相同的结果。这很可能与缺少 return 语句有关。
解决此问题的方法是在找到匹配项后(打印时)终止递归下降。保证您不会找到其他结果(除非您的输入中有条目<= 0
)。
bool subSetSum(int* a, int n, int i, int sum, int* r, int k)
if (sum == 0)
printSolution(r, k);
return true;
if (i >= n)
return false;
r[k] = a[i];
bool found = subSetSum(a, n, i + 1, sum - a[i], r, k + 1);
found = subSetSum(a, n, i + 1, sum, r, k) || found;
return found;
请注意,这仍然会为您的输入数组中的重复值找到重复的解决方案(例如10
)。您可以通过不传递i + 1
而是通过查找下一个不重复的索引,轻松添加检查以在对subSetSum
的第二次递归调用中跳过重复值。由于您已经对输入进行了排序,这可以通过递增 i
直到它指向不同的值来完成。
还应该指出,这是非常单一的 C++。 subsetSum
的更好界面如下所示:
template <typename T, typename ConstIterator>
bool subSetSum(ConstIterator begin, ConstIterator end, T sum, std::vector<T>& buf);
template <typename T, typename ConstIterator>
bool subSetSum(ConstIterator begin, ConstIterator end, T sum)
std::vector<T> buf;
return subSetSum(begin,end,std::move(T),buf);
【讨论】:
"保证您不会找到额外的结果(除非您的输入中有 0 个条目)。"或任何负数。 @Voo 你是绝对正确的,谢谢。我更新了声明。【参考方案2】:首先,这里(可能)不能应用效率,因为这个问题是 NP 完全的,这意味着它可能无法在多项式时间内解决。
关于解决方案,我附上代码:
using SolutionT = std::vector<std::set<std::size_t>>;
SolutionT subsetSum(const std::vector<int>& array, int requiredSum, std::size_t index, int currentSum)
if (currentSum > requiredSum) // Remove it if negative integers are present
return ;
if (index >= array.size())
return ;
if (requiredSum == currentSum + array[index])
std::set<std::size_t> indices;
indices.insert(index);
SolutionT solution;
solution.emplace_back(std::move(indices));
return solution;
auto includedSolutions = subsetSum(array, requiredSum, index + 1, currentSum + array[index]);
for (auto& solution : includedSolutions)
solution.insert(index);
auto excludedSolutions = subsetSum(array, requiredSum, index + 1, currentSum);
std::copy(std::make_move_iterator(includedSolutions.begin()),
std::make_move_iterator(includedSolutions.end()),
std::back_inserter(excludedSolutions));
return excludedSolutions;
SolutionT subsetSum(const std::vector<int>& array, int requiredSum)
return subsetSum(array, requiredSum, 0, 0);
代码相当复杂,因为您需要指数级数量的元素,所以如果没有 C++ 容器就很难做到。
【讨论】:
以上是关于如何递归枚举添加到给定总和的所有子集?的主要内容,如果未能解决你的问题,请参考以下文章