【问题标题】:Understanding Sum of subsets了解子集的总和
【发布时间】:2013-03-29 09:33:26
【问题描述】:

我刚开始在大学学习Backtracking 算法。不知何故,我设法为子集和问题制作了一个程序。工作正常,但后来我发现我的程序并没有给出所有可能的组合

例如:目标总和可能有一百个组合,但我的程序只给出 30 个。 这是代码。如果有人能指出我的错误是什么,那将是一个很大的帮助。

int tot=0;//tot is the total sum of all the numbers in the set.
int prob[500], d, s[100], top = -1, n; // n = number of elements in the set. prob[i] is the array with the set.
void subset()
{
    int i=0,sum=0; //sum - being updated at every iteration and check if it matches 'd'
    while(i<n)
    {
        if((sum+prob[i] <= d)&&(prob[i] <= d)) 
        {
            s[++top] = i;
            sum+=prob[i];
        }
        if(sum == d) // d is the target sum 
        {
            show(); // this function just displays the integer array 's'
            top = -1; // top points to the recent number added to the int array 's'
            i = s[top+1];
            sum = 0;
        }
        i++;
        while(i == n && top!=-1)
        {
            sum-=prob[s[top]];
            i = s[top--]+1;
        }
    }
}

int main()
{
    cout<<"Enter number of elements : ";cin>>n;
    cout<<"Enter required sum : ";cin>>d;
    cout<<"Enter SET :\n";
    for(int i=0;i<n;i++)
    {
        cin>>prob[i];
        tot+=prob[i];
    }
    if(d <= tot)
    {
        subset();
    }
    return 0;
}

当我运行程序时:

Enter number of elements : 7
Enter the required sum : 12
Enter SET : 
4 3 2 6 8 12 21

SOLUTION 1 : 4, 2, 6
SOLUTION 2 : 12

虽然4、8也是一种解决方案,但我的程序并没有显示出来。 输入数量为 100 或更多时,情况更糟。至少会有 10000 个组合,但我的程序显示 100 个。

我试图遵循的逻辑:

  1. 将主 SET 的元素放入一个子集中,只要 子集的总和保持小于或等于目标总和。
  2. 如果将特定数字添加到子集总和使其 大于目标,它不接受它。
  3. 一旦到达终点 的集合,并且没有找到答案,它删除了最 最近从集合中取出号码并开始查看号码 在最近号码的位置后的位置去掉。 (因为我存储在数组 's' 中的是 从主 SET 中选择的数字)。

【问题讨论】:

  • 如果你的变量有更多的描述性名称会有所帮助(这对你的编程生涯一般来说很有用),或者至少如果你告诉我们每个变量应该是什么意思,它们如何重新声明,初始化等。
  • 您能否添加一些示例输入,您期望的输出是什么,以及输出是什么。完全不清楚这段代码是如何提供任何东西的,或者它是从哪里得到输入的。
  • 抱歉含糊不清。这是我第一次在网上发布代码。
  • 第二个while() 循环似乎很奇怪。 一般来说,我很难弄清楚你的算法的逻辑。你能用几个自然语言的词清楚地表达出来吗?
  • 好的,我刚刚编辑了我的帖子并添加了我尝试使用的逻辑。第二个 while 循环将最近添加的元素删除到子集中。

标签: c++ algorithm backtracking subset-sum


【解决方案1】:

由于步骤 1 中的“只要”子句,您要找到的解决方案取决于集合中条目的顺序。

如果你在不超过目标的情况下参加比赛,一旦你参加了例如'4' 和 '2', '8' 会让你超过目标,所以只要 '2' 在你的集合中在 '8' 之前,你永远不会得到 '4' 和 '8' 的子集。

您应该添加一种可能性以跳过添加条目(或将其添加到一个子集但不添加到另一个子集)或更改集合的顺序并重新检查它。

【讨论】:

  • 这几乎是对的——如果实际上无法从 4 和 2 构建解决方案,那么内部 while 循环将摆脱 2 并开始搜索它的右侧。跨度>
  • @j_random_hacker 的重点是,如果一个解决方案可以用 4 和 2 构建,它仍然应该尝试不使用 2,找到 4 而没有 2 的组合。
  • 没错,但您的回答略有不同。 “所以只要 '2' 在 '8' 之前在你的集合中,你永远不会得到一个带有 '4' 和 '8' 的子集”不是真的——如果有的话,你可以获得一个带有 4 和 8 的子集没有办法用 4 和 2 来解决。
  • @j_random_hacker 我指的是他的示例序列 4 3 2 6 8 ... 在该序列中,以 4 和 2 开头的组合是可能的,因此 2 永远不会被丢弃。你说得对,我应该澄清一下..
【解决方案2】:

可能有一个无堆栈的解决方案是可能的,但实现回溯算法的通常(通常也是最简单的!)方法是通过递归,例如:

int i = 0, n;    // i needs to be visible to show()
int s[100];

// Considering only the subset of prob[] values whose indexes are >= start,
// print all subsets that sum to total.
void new_subsets(int start, int total) {
    if (total == 0) show();    // total == 0 means we already have a solution

    // Look for the next number that could fit
    while (start < n && prob[start] > total) {
        ++start;
    }

    if (start < n) {
        // We found a number, prob[start], that can be added without overflow.
        // Try including it by solving the subproblem that results.
        s[i++] = start;
        new_subsets(start + 1, total - prob[start]);
        i--;

        // Now try excluding it by solving the subproblem that results.
        new_subsets(start + 1, total);
    }
}

然后您可以从main()new_subsets(0, d); 调用它。递归一开始可能很难理解,但理解它很重要——如果上面没有任何意义,请尝试更简单的问题(例如递归生成斐波那契数)。

改为使用您给出的解决方案,我可以看到的一个问题是,一旦您找到解决方案,您就会将其清除并开始从第一个数字右侧的数字中寻找新的解决方案包含在此解决方案中(top = -1; i = s[top+1]; 暗示 i = s[0],还有一个后续的 i++;)。这将错过以相同第一个数字开头的解决方案。您应该改为使用if (sum == d) { show(); },以确保您获得所有信息。

我最初发现你的内部 while 循环非常混乱,但我认为它实际上是在做正确的事情:一旦 i 到达数组的末尾,它将删除添加到部分解决方案中的最后一个数字,如果这个数字是数组中的最后一个数字,它将再次循环以从部分解决方案中删除倒数第二个数字。它永远不会循环超过两次,因为包含在部分解中的数字都在不同的位置。

【讨论】:

    【解决方案3】:

    我没有详细分析算法,但令我印象深刻的是,您的算法没有考虑以下可能性:在获得一个以数字 X 开头的解决方案后,可能会有多个以该数字开头的解决方案。

    第一个改进是避免在打印解决方案后重置堆栈s 和运行总和。

    【讨论】:

      猜你喜欢
      • 2021-11-14
      • 2016-11-17
      • 1970-01-01
      • 1970-01-01
      • 2013-10-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-18
      相关资源
      最近更新 更多