【算法】 动态规划算法

问题

包问题:有一个背包,容量为4磅 , 现有如下物品:

物品

重量

价格

吉他(G)

1

1500

音响(S)

4

3000

电脑(L)

3

2000

1)求达到的目标为装入的背包的总价值最大,并且重量不超

2)求装入的物品不能重复

思想

动态规划的核心思想是把原来的问题分解成子问题进行求解,也就是分治的思想。但是与分治法不同的地方在于,分治法在子问题和子子问题上被重复计算了很多次,而动态规划则具有记忆性,通过填写表把所有已经解决的子问题答案纪录下来,在新问题里需要用到的子问题可以直接提取,避免了重复计算,从而节约了时间。

思路

背包问题主要是指一个给定容量的背包、若干具有一定价值和重量的物品,如何选择物品放入背包使物品的价值最大。其中又分01背包完全背包(完全背包指的是:种物品都有无限件可用),上面这套题指出装入的物品不能重复,属于01背包。

设w[i]为第i个商品的重量,v[i]为第i个商品的价值,C为背包的容量,物品的总个数为n。每次遍历到的第i个物品,根据w[i]和v[i]来确定是否应该将物品放入到背包当中。令v[i][j]表示在前i个物品中能够装入容量为 的背包中的最大价值。

填表过程

假设存在背包容量大小分为1,2,3,4的各种容量的背包(分配容量的规则为最小重量的整数倍):

物品

0

1

2

3

4

 

0

0

0

0

0

吉他(G)

0

1500(G)

1500(G)

1500(G)

1500(G)

音响(S)

0

1500(G)

1500(G)

1500(G)

3000(S)

电脑(L)

0

1500(G)

1500(G)

2000(L)

3500(LG)

1.第一行是什么商品都没有,因此不管背包的容量有多大,价值都为0。第一列全为0的原因是如果背包的容量是0的话,那么背包里面就装不下任何的东西。

2.假如现在只有吉他,这时不管背包容量多大,只能放入一把吉他。这样第二行就可以都填入1500。

3.假如有吉他和音箱,背包小于4磅是放不下音箱的,所以前3个只能只放吉他,背包容量是4时只放音箱的价值最大。

4.电脑的重量是3,那么背包容量小于3时只能放把吉他,容量是3时只放电脑的价值最高,容量为4时可以只放音箱,价值为3000,或者放一个电脑加把吉他,价值3500,所以这个价值最高。

公式

  • v[i][0] = v[0][i] = 0          //表示表的第一行和第一列
  • 当w[i] > j 时,v[i][j] = v[i-1][j]     //当准备加入新增的商品容量大于当前背包的容量时,就直接使用上一个单元格的装入策略
  • 当 j >=w[i] 时,v[i][j] = max{ v[i-1][ j ] , v[ i-1 ][ j-w[ i ]] + v[ i ] }  //当准备加入的新增的商品的容量小于等于当前背包的容量,就需要考虑如何实现装入的方式实现最大价值:
  • 如果不拿第i个物品的话,结果v[i-1][j]就是上一个单元格的装入的最大值 。
  • 如果拿的话,就是v[ i-1 ] [ j - w[ i ]]+v[ i ] ,其中 v[i] 表示当前商品的价值, v[i-1][j-w[i]] : 装入i1商品,到剩余空间 j-w[i],

    既然把i件物品装进背包,那么1,2,3,4.....i-1物品只能占用 j - w[ i ]这么多重量了。这个时候,之前的1,2,3,4......i-1物品在背包容量为(j - w[ i ])下的最大值为v[ i-1 ] [ j - w[ i ]]。此时背包的最大值就是 第i件物品的价值v[i]加上前1,2,3,4....i-1件物品在背包容量为下的最大值v[ i-1 ] [ j - w[ i ]],也就是v[ i-1 ] [ j - w[ i ]]+v[ i ] 。最后选择那个就是取决于拿或者不拿哪一个的价值更高。

验证 i = 1, j = 1 :

w[i] = w[1] = 1

w [1] = 1  j = 1   v[i][j]=max{v[i-1][j], v[i]+v[i-1][j-w[i]]} :

v[1][1] = max {v[0][1], v[1] + v[0][1-1]} = max{0, 1500 + 0} = 1500

验证 i = 3;j = 4 :

w[i] = w[3] =3 j = 4

j = 4 >= w[i] = 3 => 4 >= 3

 v[3][4] = max {v[2][4], v[3] + v[2][1]} = max{3000, 2000+1500} = 2000+1500

代码实现


public class Packet {

	public static void main(String[] args) {
	
		int[] w = {1 , 4, 3};  //物品的重量
		int[] val = {1500,3000,2000}; //物品的价值
		int m = 4; //背包的容量
		int n = val.length; //物品的个数
		
		//创建二维数组,表示在前i个物品中能够装入容量为 j 的背包中的最大价值
		int[][] v = new int[n+1][m+1];
		
		//第一行和第一列设置为0
		for(int i=0; i < v.length; i++) {
			v[i][0] = 0; //将第一列设置为0
		}
		for(int i=0; i< v[0].length; i++) {
			v[0][i] = 0;
		}
		
		//为了记录放入商品的情况,定义一个二维数组
		int[][] path = new int[n+1][m+1]; 
		
		//动态规划
		for(int i=1; i< v.length; i++) {  //不需要再处理第一行,i是从1开始的
			for(int j = 1;j < v[0].length; j++) {  //不处理第一列
				if(w[i-1]>j) { //因为程序是从i开始的,因此原来公式中的w[i]改为w[i-1]
					v[i][j] = v[i-1][j];				
				}else {
					//因为i是从1开始的,所以公式调整
					//v[i][j] = Math.max(v[i-1][j], val[i-1]+v[i-1][j-w[i-1]]);
					//为了记录商品存放到背包的情况,不能简单的直接使用上面的公式
					//需要使用if...else...体现
					if(v[i-1][j] < val[i-1]+v[i-1][j-w[i-1]]) {
						v[i][j] = val[i-1]+v[i-1][j-w[i-1]];
						//把当前的情况记录到path
						path[i][j] = 1;
					}else {
						v[i][j] = v[i-1][j];
					}
				}
			}
		}
		
		//输出当前数组的值
		for(int i=0; i < v.length; i++)
		{
			for(int j=0; j< v[i].length; j++) {
				System.out.print(v[i][j]+" ");
			}
			System.out.println();
		}
		
		int i = path.length - 1; //行的最大下标
		int j = path[0].length -1; //列的最大下标
		while (i>0 && j>0) { //从path的最大下标开始找
			if(path[i][j] == 1) {
				System.out.printf("第%d个商品放入背包\n",i);
				j = j - w[i-1];
			}
			i--;
		}

	}

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程芝士

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值