如何递归枚举添加到给定总和的所有子集?

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 语句有关。

解决此问题的方法是在找到匹配项后(打印时)终止递归下降。保证您不会找到其他结果(除非您的输入中有条目&lt;= 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++ 容器就很难做到。

【讨论】:

以上是关于如何递归枚举添加到给定总和的所有子集?的主要内容,如果未能解决你的问题,请参考以下文章

改进子集解决方案

如何找出为啥所有数据子集的总和比总和小1

如何在Java中递归地从N元素集中生成所有k元素子集

子集的生成—二进制枚举

计算具有给定总和的子集的有效方法

算法—— 集合的子集