回溯法——求解子集和问题(Java)

https://blog.csdn.net/aaaa11111156/article/details/130489725

文章目录

一、问题描述

给定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);
            }

剪掉之后,如下图所示:
在这里插入图片描述

  • 使用剪枝函数的深度优先生成状态空间数结点的求解方法称为回溯法
  • 广度优先生成结点,并使用剪枝函数的方法称为分枝限界法。

三、使用步骤

整体代码

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);
    }
}

实验结果:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值