了解子集的总和

时间:2013-03-29 09:33:26

标签: c++ algorithm backtracking subset-sum

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

例如:目标总和可能有一百种组合,但我的程序只提供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. 一旦到达终点 该集合,并没有找到答案,它删除最多 最近从集合中获取数字并开始查看数字 在最近的号码被删除后的位置。 (因为我在数组中存储的是'的位置 从主SET中选择的数字。)

3 个答案:

答案 0 :(得分:1)

您要查找的解决方案取决于集合中条目的顺序,这取决于您在步骤1中的“只要”条款。

如果您参加参赛作品,只要他们没有让您超过目标,一旦您采取了参赛作品, '4'和'2','8'将带你越过目标,所以只要'8'在'8'之前的集合中,你永远不会得到'4'和'8'的子集。

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

答案 1 :(得分:1)

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

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点击数组的末尾,它将删除添加到部分的最后一个数字解决方案,如果此数字是数组中的最后一个数字,它将再次循环以从部分解决方案中删除倒数第二个数字。它永远不会循环超过两次,因为部分解决方案中包含的数字都处于不同的位置。

答案 2 :(得分:1)

我没有详细分析过这个算法,但让我印象深刻的是你的算法没有考虑到在一个以数字X开头的解决方案之后,可能有多个以这个数字开头的解决方案。

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

相关问题