我刚开始在大学学习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。
我想要遵循的逻辑:
答案 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
和运行总和。