C ++子集和2 ^ n /递归错误/澄清

时间:2012-08-20 05:13:42

标签: c++ algorithm subset

这不是家庭作业,我没有钱上学,所以我在高速公路上的收费站工作(长时间住宿的客户很少)时自学。

我正在尝试实现一个简单的子集求和算法,给定一个整数数组,返回其总和等于所需总和的子集,报告找到它所需的调用次数。

我使用Collections在Java中实现了一个实现,但这是非常臃肿的代码,即使我能够返回所有集合,加上所需的数字,并告诉函数在第一场比赛时停止。

我对这段代码的问题如下:而不是在2 ^ n时间运行(这对于没有找到结果的实现是正确的,不是吗?)它在[2 ^(n + 1)中运行)]-1次; O(2 ^ n)由评论指出。我可以看出为什么我会在更深层次上检查(runningTotal == targetTotal),实际上我自己添加了额外的深度,不是吗?我试图尽可能干净地模拟基本情况,如果你发现任何“代码味道”,请告诉我。我一看到(runningTotal +考虑)== targetTotal?

,我应该立即打破

注意:我不认为这属于“Code Review”,因为我询问的是特定的代码行,而不是整体方法(如果我需要改变方法,那就这样吧,我这样做是为了学习)。

这是我的尝试(除了上面提到的优化缺乏之外,这是“可通过的”C / C ++吗?):

#include <iostream>
using namespace std;
bool setTotalling(int chooseFrom[], int nChoices, int targetTotal,
    int chooseIndex, int runningTotal, int solutionSet[], int &solutionDigits,
    int &nIterations) {
  nIterations++;
  if (runningTotal == targetTotal) {
    return true;
  }
  if (chooseIndex >= nChoices) {
    return false;
  }
  int consider = chooseFrom[chooseIndex];
  if (setTotalling(chooseFrom, nChoices, targetTotal, chooseIndex + 1,
      runningTotal + consider, solutionSet, solutionDigits, nIterations)) {
    solutionSet[solutionDigits++] = consider;
    return true;
  }
  if (setTotalling(chooseFrom, nChoices, targetTotal, chooseIndex + 1,
      runningTotal, solutionSet, solutionDigits, nIterations)) {
    return true;
  }
  return false;
}
void testSetTotalling() {
  int chooseFrom[] = { 1, 2, 5, 9, 10 };
  int nChoices = 5;
  int targetTotal = 23;
  int chooseIndex = 0;
  int runningTotal = 0;
  int solutionSet[] = { 0, 0, 0, 0, 0 };
  int solutionDigits = 0;
  int nIterations = 0;
  cout << "Looking for a set of numbers totalling" << endl << "--> "
      << targetTotal << endl << "choosing from these:" << endl;
  for (int i = 0; i < nChoices; i++) {
    int n = chooseFrom[i];
    cout << n << ", ";
  }
  cout << endl << endl;
  bool setExists = setTotalling(chooseFrom, nChoices, targetTotal, chooseIndex,
      runningTotal, solutionSet, solutionDigits, nIterations);
  if (setExists) {
    cout << "Found:" << endl;
    for (int i = 0; i < solutionDigits; i++) {
      int n = solutionSet[i];
      cout << n << ", ";
    }
    cout << endl;
  } else {
    cout << "Not found." << endl;
  }
  cout << "Iterations: " << nIterations << endl;
}
int main() {
  testSetTotalling();
  return 0;
}

1 个答案:

答案 0 :(得分:2)

重点是如何计算“迭代”。假设你有n=1的简单案例,其目标是一个不为零的和,而不是你拥有的元素。

你调用函数,这会立即递增计数器,然后你进入分叉,函数调用自己两次(一个考虑元素,一个不考虑元素)。这些调用中的每一个都将计为1,因此您最终将得到一个总计数器为3。

我认为这没有任何问题...

如果剩余选项的麻木为零,您可以添加特殊检查以重复测试并避免通话,但这需要重复检查。仅在递归调用位置进行结束检查不会考虑可以直接调用零函数来调用该函数。基本上你是“内联”等级0 ......但是为什么停在零级而不是内联也是1级?

如果您正在寻找加速请注意(假设所有元素都是非负数),如果您知道添加所有剩余的可用数字仍然不足以到达目标那么您可以避免检查所有可能的子集。 通过计算从给定索引到可用元素列表末尾的所有剩余数字的总和(即O(n)计算),您可以保存(剩余2个)迭代。 此外,如果当前总和已经太大,那么考虑添加其他元素也没有意义。

if (targetTotal > runningTotal)
    return false; // We already passed the limit
if (targetTotal - runningTotal > sumOfAllFrom[choseIndex])
    return false; // We're not going to make it

如果您还按降序对元素进行排序,则上述优化可以节省很多。