使用递归的子集总和

Subset Sum using recursive

本文关键字:子集 递归      更新时间:2023-10-16

我的输入是:

W[10] = {1, 3, 5, 7, 9, 12, 19, 22, 36, 63}
X[10] = {0};
M = 79;

我通过以下方式调用了该函数:

findSolution(0,0,177); <br>

注意:177 是 W 数组中所有元素的总和。

void findSolution(int s, int k, int r) {
    cout << "fn(" << s << " , " << k << ", " << r << " )" << endl;
    X[k] = 1;
    if (s + W[k] == M){
        printArr(X);
    }
    else if (s + W[k] + W[k + 1] <= M) {
        return findSolution(s + W[k], k + 1, r - W[k]);
    }
    if ((s + r - W[k] >= M) && (s + W[k + 1]) <= M){
        X[k] = 0;
        return findSolution(s, k + 1, r - W[k]);
    }
}

输出:

fn(0 , 0, 177 )
fn(1 , 1, 176 )
fn(4 , 2, 173 )
fn(9 , 3, 168 )
fn(16 , 4, 161 )
fn(25 , 5, 152 )
fn(37 , 6, 140 )
fn(56 , 7, 121 )

上面给出的输出是跟踪函数调用。输出到此结束,不会继续。我的代码有什么问题。我正在尝试打印一个子集,该子集给出所需的总和 = 79。递归调用不会返回。

您的解决方案的问题在于它使用了贪婪的策略(即在找到合适的候选人后不会"回溯")。

您的算法会检查三个条件:

  • 你找到了一个解决方案,
  • 如果将 -th 元素k添加到子集,或者
  • 如果将 -st 元素替换为 k -th k-1则可以解决问题。

这种策略并没有穷尽所有的可能性:例如,可能无法用 k+1 -st 替换k -th 元素,但有可能用 k+1 -st 替换 k -th 之前的几个元素并获得解决方案。你的策略是贪婪的,因为当它发现一个元素可以添加到一个集合中时(即 s + W[k] + W[k + 1] <= M )它走这条路,从不回头(即从该分支返回)。

可以通过重组代码来解决此问题,如下所示:

  • 使函数在找到解决方案时返回true,否则false
  • 保持基本情况if (s + W[k] == M),并在找到解决方案时添加return true
  • 检查是否可以将k -th 元素添加到集合中。如果可能,请添加它,并尝试部分总和s + W[k]
  • 检查递归调用的返回。如果为 true ,则返回true
  • 否则,从集合中删除k -th 元素,并在混合中不使用第 k 个元素进行第二次递归调用。使用相同的 s 部分和。
  • 将上次递归调用的值返回给调用方。

现在你的算法是详尽的,因为对于每个元素,算法都会尝试在元素是解决方案的一部分和元素不是解决方案的一部分时(即 O(2n) 检查所有元素)时找到部分和。

递归调用正在返回;它只是在你找到解决方案之前这样做。 如果达到最后一个if但失败,则可能会发生这种情况。 (请注意,当您调用 printArr 时,看起来像您的基本情况并不一定会停止递归。

//code 在 C++ 中使用递归返回子集总和到 k

int subsetSumToK(int input[], int n, int output[][50], int k) {

由于我们在 int count1 递归调用中减小 k 的值,因此在 k 的时间值将为零,正值和负值也为零,因此我们将只返回那些当输入数组的大小变为零时 k 值为零的子集,否则我们只返回 0。在递归调用中,我们使用两个递归调用,首先我们包含元素,因此现在我们必须找到值 k - input[0](包含元素)并将该元素存储在 o1 输出数组和第二个递归调用中,因为我们不包括元素,所以只需通过 input+1 和 size-1 直接传递调用,使用 o2 输出数组和相同的值 k。

if(n == 0){       //base case
    if(k==0){   
        output[0][0] = 0;
        return 1;
    }else{
        return 0;
    }
}
int o1[1000][50]; //to store the output individually of two recusive calls    
int o2[1000][50];
int count1 = subsetSumToK(input+1,n-1,o1,k-input[0]);     //recursive calls
int count2 = subsetSumToK(input+1,n-1,o2,k);
for(int i=0;i<count1;i++){           //small calulations
    for(int j=1;j<=o1[i][0];j++){
        output[i][j+1] = o1[i][j];
    }
    output[i][0] = o1[i][0] +1;
    output[i][1] = input[0];
}
for(int i=0 ; i<count2 ; i++){
    for(int j=0 ; j<=o2[i][0] ; j++){
        output[i + count1][j] = o2[i][j];
    }
}
return count1 + count2;

}