dfs求解01背包与分支限界法求解旅行售货员问题(java)

本文探讨了使用回溯法和分支界限法解决01背包问题和旅行售货员问题。回溯法通过暴力枚举找到最优解,而分支界限法则通过构建可行解树并利用剪枝策略高效求解。示例代码分别用Java和C++展示了两种方法的实现,其中分支界限法在处理大量剪枝场景时表现出优越性。
摘要由CSDN通过智能技术生成

1.利用回溯法求解01背包问题。

对于N个物品,在背包的总体积的限制下,有些物品肯定是该舍弃的。
对于每件物品都有取和不取俩种情况。
我们把所有的情况遍历一次,最后取最优的结果。
总体上来就是暴力枚举求最优解。
为了更快地求出最优解,有些情况枚举到一半可以立马舍弃。
如果拿了当前物品,背包总体积大小会溢出,那么这种情况到这一步就会立马放弃接下来的枚举。

代码

在这里插入图片描述

java

public class Main {
    static int N=7;//物品总的个数
    static int maxW=25;//背包容量
    static int maxV=0;//当前已经装了的最大价值
    static int w[]={0,11,7,9,12,3,10};//体积
    static int v[]= {0,10,5,7,9,2,12};//价值
    static int vis[]=new int[N];//用于标记哪些物品被选了
    public static void main(String[] args) {
        dfs(1,0,0,new int[N]);
        int ans=0;//记录选择物品的价值
        System.out.print("选择的物品是:");
        for(int i=1;i<N;i++){

            if(vis[i]==1) {
                System.out.print(i + ",");
                ans+=v[i];
            }
        }
        System.out.println();
        System.out.println("此时价值最大,即"+ans);

    }
    public static void dfs(int idx,int curV,int curW,int curVis[]){//第idx个物品,已经装了curV价值,curW重量,curVis用于标记选了哪些物品
        if(idx==N) {//物品搜索完毕
            if(curV>maxV){
                maxV=curV;//更新已知的可装价值最大值
                //vis=curVis;
                for(int i=0;i<N;i++){//重新赋值记录选中的物品个数
                    vis[i]=curVis[i];
                }
            }

            return;
        }
        curVis[idx]=0;//当前物品不取
        dfs(idx+1,curV,curW,curVis);
        if(curW+w[idx]<=maxW){//如果当前物品装得下(剪枝)
            //这个物品装了
            curVis[idx]=1;//标记装入
            dfs(idx+1,curV+v[idx],curW+w[idx],curVis);
        }
    }
}

c++

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N=7;//物品总的个数

int maxW=25;//背包容量
int maxV=0;//当前已经装了的最大价值
int w[]={0,11,7,9,12,3,10};//体积
int v[]= {0,10,5,7,9,2,12};//价值
int vis[N];//用于标记哪些物品被选了

void dfs(int idx,int curV,int curW,int curVis[]){//第idx个物品,已经装了curV价值,curW重量,curVis用于标记选了哪些物品
        if(idx==N) {//物品搜索完毕
            if(curV>maxV){
                maxV=curV;//更新已知的可装价值最大值
                //vis=curVis;
                for(int i=0;i<N;i++){//重新赋值记录选中的物品个数
                    vis[i]=curVis[i];
                }
            }

            return;
        }
        curVis[idx]=0;//当前物品不取
        dfs(idx+1,curV,curW,curVis);
        if(curW+w[idx]<=maxW){//如果当前物品装得下(剪枝)
            //这个物品装了
            curVis[idx]=1;//标记装入
            dfs(idx+1,curV+v[idx],curW+w[idx],curVis);
        }
}
    
int main(){
    dfs(1,0,0,new int[N]);
        int ans=0;//记录选择物品的价值
        cout<<"选择的物品是:"<<endl;
        for(int i=1;i<N;i++){

            if(vis[i]==1) {
                cout<<i << ",";
                ans+=v[i];
            }
        }
        cout<<endl;
        cout<<"此时价值最大,即"<<ans<<endl;
}

结果
在这里插入图片描述

2.利用分支界限法求解旅行售货员问题(只有java代码)

下面代码采用的是一般的dfs解法(为什么会列出这个解法呢,因为我了解分支界限法之前只会这个解法,咳咳)
求出的是满足条件的所有解,然后才选取其中一种(dfs)。
而分支界限法是直接求出一个最优解,下面最后介绍(bfs)。

public class Main {
    static int N=5;//城市个数
    static int g[][]=new int[N][N];
    static int ne[]=new int[N];//用于存当前点的下一个城市编号
    static int vis[]=new int[N];//用于记录已经走过的城市
    static int minV=Integer.MAX_VALUE;//用于存当前最小的路径花费
    public static void main(String[] args) {
        g[1][2]=30;g[2][1]=30;
        g[1][4]=4;g[4][1]=4;
        g[1][3]=6;g[3][1]=6;
        g[2][3]=5;g[3][2]=5;
        g[2][4]=10;g[4][2]=10;
        g[3][4]=20;g[4][3]=20;
        dfs(1,1,0,new int[N]);
        System.out.println("最短回路长为:"+minV);
        System.out.print("最短回路为:");
        int t=1;//旅游开始城市
        System.out.print("1 ");
        for(int i=1;i<N-1;i++){
            System.out.print(ne[t]+" ");
            t=ne[t];
        }
    }
    public static void dfs(int idx,int n,int curV,int curN[]){//第idx层,n号城市,当前路径花费curV,curN当前路径记录
        if(idx==N-1){//所有城市走完后,选择最优解
            if(curV+g[n][1]<minV){
                minV=curV+g[n][1];
                for(int i=1;i<N-1;i++){
                    ne[i]=curN[i];
                }
                ne[n]=1;//最后一个城市就是起点城市1
            }
            return;
        }
        vis[n]=1;//将当前城市标记已经走过
        for(int i=1;i<N;i++){//遍历下一个城市
            if(vis[i]==0&&g[n][i]!=Integer.MAX_VALUE){//如果下一个城市没有走过并且可以走通
                //剪枝
                if(curV+g[n][i]<minV){//当前的路径花费小于历史最优花费才有可能产生最优解
                    //走
                    vis[i]=1;//标记当前城市已经走
                    curN[n]=i;//记录当前路径
                    dfs(idx+1,i,curV+g[n][i],curN);
                    vis[i]=0;//还原现场
                }
            }
        }
    }
}

运行结果
在这里插入图片描述

进入正题,分支界限法,这个听着很厉害,有利于大量的需要剪枝的场景(剪枝极快)。
算法的核心是构建可行解树。采用bfs队列或者优先队列实现。
但是代码实现却有点难度

在这里插入图片描述

import java.util.Collections;
import java.util.LinkedList;
public class Main{
    static int N=5;
    static int g[][]=new int[N][N];
    public static void main(String[] args){
        g[1][2]=30;g[2][1]=30;
        g[1][4]=4;g[4][1]=4;
        g[1][3]=6;g[3][1]=6;
        g[2][3]=5;g[3][2]=5;
        g[2][4]=10;g[4][2]=10;
        g[3][4]=20;g[4][3]=20;
        int v[]={1,2,3,4,5};
        BBTSP bbtsp = new BBTSP(g);
        int ans = bbtsp.bbTsp(v);
        System.out.println("最短回路长为:"+ans);
        System.out.print("最短回路为:");
        for(int i=1;i<N;i++){
            System.out.print(v[i]+" ");
        }
        
    }
}
class BBTSP {
    int[][] a;//图G的邻接矩阵

    public BBTSP(int[][] a) {
        this.a = a;
    }

    public static class HeapNode implements Comparable {
        int lcost;//子树费用的下界
        int cc;//当前费用
        int rcost;//x[s:n-1]中顶点最小出边费用和
        int s;//根节点到当前节点的路径为x[0:s]
        int[] x;//需要进一步搜索的顶点是x[s+1:n-1]

        //构造方法
        public HeapNode(int lc, int ccc, int rc, int ss, int[] xx) {
            lcost = lc;
            cc = ccc;
            s = ss;
            x = xx;
        }

        public int compareTo(Object x) {
            int xlc = ((HeapNode) x).lcost;
            if (lcost < xlc) return -1;
            if (lcost == xlc) return 0;
            return 1;
        }
    }

    public int bbTsp(int[] v) {//解决旅行售货员问题的优先队列分支界限法
        int n = v.length - 1;//节点数
        //MinHeap heap=new MinHeap();
        LinkedList<HeapNode> heap = new LinkedList<HeapNode>();
        //minOut[i]=i的最小出边费用
        int[] minOut = new int[n + 1];
        int minSum = 0;//最小出边费用和
        for (int i = 1; i <= n; i++) {//针对每个节点,找到最小出边
            //计算minOut[i]和minSum
            int min = Integer.MAX_VALUE;
            for (int j = 1; j <= n; j++)
                if (a[i][j] < Integer.MAX_VALUE && a[i][j] < min)
                    min = a[i][j];

            if (min == Integer.MAX_VALUE)
                return Integer.MAX_VALUE;//没有回路
            minOut[i] = min;
            minSum += min;
        }

        //初始化
        int[] x = new int[n];
        for (int i = 0; i < n; i++)
            x[i] = i + 1;
        HeapNode enode = new HeapNode(0, 0, minSum, 0, x);
        Integer bestc = Integer.MAX_VALUE;

        //搜索排列空间树
        while (enode != null && enode.s < n - 1) {
            //非叶节点
            x = enode.x;
            if (enode.s == n - 2) {
                //当前扩展结点是叶节点的父节点
                //再加两条边构成回路
                //所构成回路是否优于当前最优解
                if (a[x[n - 2]][x[n - 1]] != -1 && a[x[n - 1]][1] != -1 && enode.cc + a[x[n - 2]][x[n - 1]] + a[x[n - 1]][1] < bestc) {
                    //找到费用更小的回路
                    bestc = enode.cc + a[x[n - 2]][x[n - 1]] + a[x[n - 1]][1];
                    enode.cc = bestc;
                    enode.lcost = bestc;
                    enode.s++;
                    heap.add(enode);
                    Collections.sort(heap);
                }
            } else {//内部结点
                //产生当前扩展结点的儿子结点
                for (int i = enode.s + 1; i < n; i++) {
                    if (a[x[enode.s]][x[i]] != -1) {
                        //可行儿子结点
                        int cc = enode.cc + a[x[enode.s]][x[i]];
                        int rcost = enode.rcost = minOut[x[enode.s]];
                        int b = cc + rcost;//下界
                        if (b < bestc) {
                            //子树可能含有最优解,结点插入最小堆
                            int[] xx = new int[n];
                            for (int j = 0; j < n; j++)
                                xx[j] = x[j];
                            xx[enode.s + 1] = x[i];
                            xx[i] = x[enode.s + 1];
                            HeapNode node = new HeapNode(b, cc, rcost, enode.s + 1, xx);
                            heap.add(node);
                            Collections.sort(heap);
                        }
                    }
                }

            }

            //取下一个扩展结点
            enode = heap.poll();
        }
        //将最优解复制到v[1...n]
        for (int i = 0; i < n; i++)
            v[i + 1] = enode.x[i];
        return bestc;
    }
}

运行结果
在这里插入图片描述

总结:承认别人比自己优秀,自己是个普普通通的凡人不难。第二个分支界限法核心代码是摘自网络上别人写的。
我经过一个小时成功大致搞懂了分支界限法的理论,只是理论!!而代码现在却无法成功实现。
另外,有个运行时间,可以看到同样的数据内容,分支界限法用了83ms,而我自己写的dfs只用了76ms(咳咳,给自己个台阶下~)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值