java 0-1背包问题 动态规划、回溯法、分支限界

java 0-1背包问题 动态规划、回溯法、分支限界

1.什么是0-1背包问题
有n个物品,它们有各自的体积和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?
:与普通背包问题不同,0-1背包问题中,物品以整体的形式出现,只能选择整体放入背包或整体不放入背包。
2.实验数据
在这里插入图片描述

3.解决思路
(1)动态规划
算法思想
动态规划是解决0-1背包问题常用的解决办法,时间复杂度为o(c*n),相对于回溯法大大减少了时间复杂度,可以用于背包空间较大和物品数量较多的情况。
动态规划与分治法类似,都是把大问题拆分成小问题,通过寻找大问题与小问题的递推关系,解决一个个小问题,最终达到解决原问题的效果。但不同的是,分治法在子问题和子子问题等上被重复计算了很多次,而动态规划则具有记忆性,通过填写表把所有已经解决的子问题答案记录下来,在新问题里需要用到的子问题可以直接提取,避免了重复计算,从而节约了时间,所以在问题满足最优性原理之后,用动态规划解决问题的核心就在于填表,表填写完毕,最优解也就找到。
最优性原理是动态规划的基础,最优性原理是指“多阶段决策过程的最优决策序列具有这样的性质:不论初始状态和初始决策如何,对于前面决策所造成的某一状态而言,其后各阶段的决策序列必须构成最优策略”。
背包问题的解决过程
在解决问题之前,为描述方便,首先定义一些变量:Vi表示第 i 个物品的价值,Wi表示第 i 个物品的体积,定义V(i,j):当前背包容量 j,前 i 个物品最佳组合对应的价值,同时背包问题抽象化(X1,X2,…,Xn,其中 Xi 取0或1,表示第 i 个物品选或不选)。
(1)建立模型,即求max(V1X1+V2X2+…+VnXn);
(2)寻找约束条件,W1X1+W2X2+…+WnXn<capacity;
(3)寻找递推关系式,面对当前商品有两种可能性:

包的容量比该商品体积小,装不下,此时的价值与前i-1个的价值是一样的,即V(i,j)=V(i-1,j);
还有足够的容量可以装该商品,但装了也不一定达到当前最优价值,所以在装与不装之间选择最优的一个,即V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}。
其中V(i-1,j)表示不装,V(i-1,j-w(i))+v(i) 表示装了第i个商品,背包容量减少w(i),但价值增加了v(i);
由此可以得出递推关系式:
1、j<w(i) V(i,j)=V(i-1,j)
2、j>=w(i) V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)
代码实现

//动态规划
	public static void method1(int[] w,int[] v,int m,int n) {
//最大价值数组为maxvalue[N+1][maxWeight+1],从0开始保存
		int[][] maxvalue = new int[n+1][m + 1];
		int[] result=new int[n+1];
//重量和物品为0时,价值为0
		for (int i = 0; i < m + 1; i++) {
			maxvalue[0][i] = 0;
			
		}
		for (int i = 0; i < n + 1; i++) {
			maxvalue[i][0] = 0;
			result[i]=0;
		}		
//i:只拿前i件物品(这里的i因为取了0,所以对应到weight和value里面都是i-1号位置)      
//j:假设能取的总重量为j
//n是物品件数
		for (int i = 1; i <= n ; i++) {
			for (int j = 1; j <= m; j++) {
//当前最大价值等于放上一件的最大价值
				maxvalue[i][j] = maxvalue[i-1][j];
//如果当前件的重量小于总重量,可以放进去或者拿出别的东西再放进去
				if (w[i-1] <= j) {
//比较(不放这个物品的价值)和
//(这个物品的价值 加上 当前能放的总重量减去当前物品重量时取前i-1个物品时的对应重量时候的最高价值)
					if(maxvalue[i-1][j - w[i-1]] + v[i-1]>maxvalue[i-1][j]) {
						maxvalue[i][j] = maxvalue[i-1][j - w[i-1]] + v[i-1];						
					}
				}
			}
		}		 
		System.out.println("背包最大价值为:"+maxvalue[n][m]);		
	}

实验效果
在这里插入图片描述
(2)回溯法
算法思想:回溯法解决0-1背包问题并不是一个比较好的办法,它的时间复杂度为O(2n),复杂度为指数级,这种方法在物品数量多,背包空间大的情况下,求解时间很长。这种方法采取的思想是一件件的选择放入还是不放入,然后放到不能再放为止,计算此时的总价值,之后再回退到之前的状态,更改刚才放入的操作。下面我写的是没有剪枝操作的回溯法,相当于全排列。
代码实现

//回溯算法	
    static class back {      
		public int[] weight;
	    public int[] value;
	    public int[] take; 
	    int curWeight = 0;
	    int curValue = 0; 
	    int bestValue = 0;
	    int[] bestChoice;	 
	    int maxWeight = 0;	 
	    int count;
	    public back(int[] weight, int[] value, int maxWeight,int n) {	
	        this.value = value;
	        this.weight = weight;
	        this.maxWeight = maxWeight;
	        this.count=n;
	        take = new int[n];
	        bestChoice = new int[n];
	    } 
	    public int[] backtrack(int x) {
	        //终止条件
	        if (x >count - 1) {
	            //更新最优解
	            if (curValue > bestValue) {
	                bestValue = curValue;
	                for (int i = 0; i < take.length; i++) {
	                    bestChoice[i] = take[i];
	                }
	            }
	        } 
	        else { //遍历当前节点(物品)的子节点:0 不放入背包 1:放入背包
	            for (int i = 0; i < 2; i++) {
	                take[x] = i;
	                if (i == 0) {
	                    //不放入背包,接着往下走
	                	backtrack(x + 1);//递归下一个物品
	                } else {
	                    //约束条件,如果小于背包容量
	                    if (curWeight + weight[x] <= maxWeight) {
	                        //更新当前重量和价值
	                        curWeight += weight[x];
	                        curValue += value[x];
	                        //继续向下深入
	                        backtrack(x + 1);
	                        //回溯
	                        curWeight -= weight[x];
	                        curValue -= value[x];
	                    }
	                }
	            }
	        }
	        return bestChoice;
	    }
	    
	   
	}
public static void method2(int[] w,int[] v,int m,int n) {
		
		back bt=new back(w,v,m,n);
		int[] result = bt.backtrack(0);
        System.out.print("最佳选择为:[");
        for(int i=0;i<bt.bestChoice.length;i++) {
        	if(i==bt.bestChoice.length-1) {
        		System.out.print(bt.bestChoice[i]+"]");
        	}else {
            	System.out.print(bt.bestChoice[i]+","); 
        	}
        }
        System.out.print("\n此时价值最大,即"+bt.bestValue+"\n");

	}

实验效果
在这里插入图片描述
(3)分支限界法
算法思路
1.先求出每个物品的单位重量的价值,并按降序排序;按此顺序取物品,取或不取的时候都计算出上界:ub=cv+(bagw-cw)(value[i]/weight[i]),即(当前价值+最佳回报*剩余重量);
2. 比较两种情况下的上界ub,选择界限大的方式。前提是选取该物品后总重不会超过背包容量:bagw;
3. 一个物品要么选,要么不选,选了就往选了的方式下继续分支,重复2,3,没选就往没选的方式下分支。
代码实现

//分支限界
static class FZXJProblem {

    // 准备放入背包中的物品
	public int[] weight;
    public int[] value;
    public float[] ave;
    // 背包的总承重
    private int totalWeight;
    // 给定的物品数
    private int n;
    // 物品放入背包可以获得的最大价值
    private int bestValue;

    public FZXJProblem(int[] w,int[] v,int n, int totalWeight) {
        super();
        this.weight=w;
        this.value=v;
        this.totalWeight = totalWeight;
        this.n = n;
        this.ave=new float[n];
        // 物品依据单位重量价值进行排序
        float[] ave1=new float[n];
        for(int i=0;i<n;i++) {
        	ave1[i]=v[i]/w[i];
        }
        Arrays.sort(ave1);
        int j=n-1;
        for(int i=0;i<n;i++) {
        	this.ave[i]=ave1[j--];
        }
    }

    // 队列式分支限界法
    public void solve() {
        LinkedList<Node> nodeList = new LinkedList<Node>();
        // 起始节点当前重量和当期价值均为0
        nodeList.add(new Node(0, 0, 0));
        while (!nodeList.isEmpty()) {
            // 取出放入队列中的第一个节点
            Node node = nodeList.pop();
            if (node.upboundValue >= bestValue && node.index < n) {
                // 左节点:该节点代表物品放入背包中,上个节点的价值+本次物品的价值为当前价值
                int leftWeight = node.currWeight + weight[node.index];
                int leftValue = node.currValue + value[node.index];
                Node left = new Node(leftWeight, leftValue, node.index + 1);
                // 放入当前物品后可以获得的价值上限
                left.upboundValue = getUpboundValue(left);
                // 当物品放入背包中左节点的判断条件为保证不超过背包的总承重
                if (left.currWeight <= totalWeight
                        && left.upboundValue > bestValue) {
                    // 将左节点添加到队列中
                    nodeList.add(left);
                    if (left.currValue > bestValue) {
                        // 物品放入背包不超重,且当前价值更大,则当前价值为最大价值
                        bestValue = left.currValue;
                    }
                }
                // 右节点:该节点表示物品不放入背包中,上个节点的价值为当前价值
                Node right = new Node(node.currWeight, node.currValue,
                        node.index + 1);
                // 不放入当前物品后可以获得的价值上限
                right.upboundValue = getUpboundValue(right);

                if (right.upboundValue >= bestValue) {
                    // 将右节点添加到队列中
                    nodeList.add(right);
                }
            }
        }
    }

    // 当前操作的节点,放入物品或不放入物品
    class Node {
        // 当前放入物品的重量
        private int currWeight;
        // 当前放入物品的价值
        private int currValue;
        // 不放入当前物品可能得到的价值上限
        private int upboundValue;
        // 当前操作的索引
        private int index;

        public Node(int currWeight, int currValue, int index) {
            this.currWeight = currWeight;
            this.currValue = currValue;
            this.index = index;
        }
    }

    // 价值上限=节点现有价值+背包剩余容量*剩余物品的最大单位重量价值
    // 当物品由单位重量的价值从大到小排列时,计算出的价值上限大于所有物品的总重量
    private int getUpboundValue(Node n) {

        // 获取背包剩余容量
        int surplusWeight = totalWeight - n.currWeight;
        int value1 = n.currValue;
        int i = n.index;

        while (i < this.n && weight[i] <= surplusWeight) {
            surplusWeight -= weight[i];
            value1 += value[i];
            i++;
        }

        // 当物品超重无法放入背包中时,可以通过背包剩余容量*下个物品单位重量的价值计算出物品的价值上限
        if (i < this.n) {
            value1 += ave[i] * surplusWeight;
        }

        return value1;
    }

    public int getBestValue() {
        return bestValue;
    }

}
public static void method3(int[] w,int[] v,int m,int n) {
	FZXJProblem pro= new FZXJProblem(w,v,n,m);
	pro.solve();
	System.out.println("背包最大价值为:"+pro.getBestValue());
}

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

  • 5
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值