一、问题描述
给定n个不同的正整数的集合w={w1,w2,…,wn}和一个正整数W,要求找出w的子集s,使该子集中所有的元素的和为W。例如,当n=4时,w={11,13,24,7},W=31,则满足要求的子集为(11,13,7)和(24,7)。
二、问题求解
和0/1背包问题一样,该问题的解空间数是一颗子集树。设解向量x=(x1,x2,…,xn),这里是求所有满足条件的解,所有一旦搜索到叶子结点(即全部结点搜索完,即i>n),如果相应的子集和为W,则输出x解向量。 if(i>n) { if(tw==W) { count++; solution(x); } }
当搜索到第i个结点(1<=i<=n)的某点结点时,用tw表示选取的整数和,rw表示余下的整数和。
对于n=4的背包问题,其解空间数如下图所示,树中的16个叶子结点分别代表着16个可能的解。
对于以上问题的求解,是非常繁琐的。为了剪去不必要的子树,减少问题求解所需实际生成的状态结点数,我们利用剪枝函数。剪枝函数包括约束函数和限界函数。(解空间数左边是1(选择),右边是0(不选择)!!!
(1)约束函数–剪掉得不到的可行解,避免无谓地搜索。
相当于左剪枝,通过检查当前整数w[i]加入后子集和是否超过W。若超过了则需要剪枝,仅仅扩展满足tw+w[i]<=W的左孩子结点不用剪枝。
if(tw+w[i]<=W) //不用剪左子树的情况,
{
x[i]=1;
dfs(i+1,tw+w[i],rw-w[i],x);
}
(2)限界函数–剪掉得不到最优解的子树。
相当于右剪枝,如果一个结点tw+rw-w[i]<W或者tw+rw<=W,也就是说即便选择剩余所有整数,也不可能找到一个解,仅仅扩展满足tw+rw-w[i]>=W或者tw+rw >W的右孩子结点不用剪枝。
对于第二个点补充解释。为啥是tw+rw-w[i]<W或者tw+rw<W,就要剪枝?
我们是求该子集中所有的元素加起来=W。而tw表示选取的整数和,rw表示余下的整数和,w[i]表示当前的重量。如果当前所选取的重量加上剩余的重量仍然W,就不可能找到一个和W相等的值,至于为啥-w[i],我的理解是右边是不选择,就相当于不选择当前的重量,即使减不减也无所谓(如果理解有误请指出)。
if(tw+rw-w[i]>=W) //不用剪右子树的情况
{
x[i]=0;
dfs(i+1,tw,rw-w[i],x);
}
剪掉之后,如下图所示:
文章来源:https://www.toymoban.com/news/detail-460660.html
- 使用剪枝函数的深度优先生成状态空间数结点的求解方法称为回溯法
- 广度优先生成结点,并使用剪枝函数的方法称为分枝限界法。
三、使用步骤
整体代码
package 算法作业;
//求解子集和问题 所有子集所有元素加起来=W
public class SubsetSum {
static int n=4;
static int W=31; //比如有四个背包 最大载重量为31
static int[]w=new int[100];
static int[] x=new int[100];
//static int[] w ={11,13,24,7}; //存放所有整数,不用下标0的元素,比如每一个背包对应的重量
static int count=0;//累积解的个数
//tw为考虑第i个元素时前面已经选取的整数(重量)的和,rw就是剩下的整数(重量)的和 x[]看你选不选,选的话就为1,不选就为0
public static void dfs(int i, int tw, int rw, int x[]){
if(i>n) //找完所有的整数
{
if(tw==W) //找到一个满足条件的解,就输出它
{
count++;
solution(x);
}
}
else //未找完所有整数
{
if(tw+w[i]<=W) //不用剪左子树的情况,
{
x[i]=1;
dfs(i+1,tw+w[i],rw-w[i],x);
}
if(tw+rw-w[i]>=W) //不用剪右子树的情况
{
x[i]=0;
dfs(i+1,tw,rw-w[i],x);
}
}
}
public static void solution(int x[]) {
System.out.println("第"+count+"个解:");
System.out.println("选取的数为:");
for(int i=1;i<=n;i++){
if(x[i]==1){
System.out.println(w[i]);
}
}
}
public static void main(String[] args){
w[1]=7;
w[2]=11;
w[3]=13;
w[4]=24;
int rw=0;
for(int j=1;j<=n;j++){
rw+=w[j];
}
dfs(1,0,rw,x);
}
}
实验结果:
文章来源地址https://www.toymoban.com/news/detail-460660.html
到了这里,关于回溯法——求解子集和问题(Java)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!