动态规划01背包问题

声明:对xiaowei_cqu大佬的博客 算法设计_背包问题 的学习记录以及部分延申,欢迎讨论

完全背包

目标函数 m a x ∑ j = 1 n v j x j max\sum_{j=1}^{n}v_jx_j maxj=1nvjxj v j v_j vj表示第j件物品的价值 x j x_j xj表示第j件物品的数量

约束条件 ∑ j = 1 n w j x j < = b \sum_{j=1}^{n}w_jx_j<=b j=1nwjxj<=b w j w_j wj表示第j件物品的重量

F k ( y ) F_k(y) Fk(y) 表示只允许装前k 种物品,背包总重不超过y 时背包的最大价值。

F k ( y ) F_k(y) Fk(y) 有两种情况:不装第k种物品或至少装1件第k种物品。
①不装第k种物品,那么只能用前k-1种物品装入背包,背包的限制重量仍为y,所以最大价值是 F k − 1 ( y ) F_{k-1}(y) Fk1(y)
②装1件第k种物品,那么装入的第k种物品价值为 v k v_k vk,重量为 w k w_k wk,剩下的物品仍要在前k种里选择。于是问题规约为背包限制重量 y − w k y-w_k ywk的情况下前k种物品取得最大价值,即 F k ( y − w k ) + v k F_k(y-w_k)+v_k Fk(ywk)+vk

递推方程:
F k ( y ) = m a x ( F k − 1 ( y ) , F k ( y − w k ) + v k ) F_k(y)=max(F_{k-1}(y),F_k(y-w_k)+v_k) Fk(y)=max(Fk1(y),Fk(ywk)+vk)

标记函数:
i k ( y ) = { i k − 1 ( y ) F k − 1 ( y ) > F k ( y − w k ) + v k k F k − 1 ( y ) < = F k ( y − w k ) + v k i_k(y)=\begin{cases}i_{k-1}(y)\quad\quad F_{k-1}(y)>F_k(y-w_k)+v_k\\k\quad\quad\quad\quad F_{k-1}(y)<=F_k(y-w_k)+v_k\end{cases} ik(y)={ik1(y)Fk1(y)>Fk(ywk)+vkkFk1(y)<=Fk(ywk)+vk

void dpbag(int v[N],int w[N],int F[][B+1],int tagi[][B+1]){
	for(int k=0;k<=N;k++){
		F[k][0]=0;//前k种物品总重不超过0时价值
		tagi[k][0]=0;//前k种物品总重不超过0时标记
	}
	for(int y=0;y<=B;y++){
		F[0][y]=0;
		F[1][y]=(int)(y/w[0])*v[0];//只装第一种物品时价值
		tagi[0][y]=0;
	}
 
	for(int k=1;k<=N;k++){
		for(int y=1;y<=B;y++){
			if(y-w[k-1]<0){
				F[k][y]=F[k-1][y];
				tagi[k][y]=tagi[k-1][y]; 
			}
			else{
				//允许装入k种物品,价值的两种情况:
				//不装第k种物品或至少装1件第k种物品
				F[k][y]=F[k-1][y]>F[k][y-w[k-1]]+v[k-1] ? F[k-1][y]:(F[k][y-w[k-1]]+v[k-1]);
				tagi[k][y]=F[k-1][y]>F[k][y-w[k-1]]+v[k-1]?tagi[k-1][y]:k;
			}
		}
	}
}

w 0 = 2 , w 1 = 4 , w 2 = 5 , w 3 = 5 , w 4 = 6. w_0=2,w_1=4,w_2=5,w_3=5,w_4=6. w0=2,w1=4,w2=5,w3=5,w4=6.

v 0 = 1 , v 1 = 2 , v 2 = 4 , v 3 = 3 , v 4 = 6. v_0=1,v_1=2,v_2=4,v_3=3,v_4=6. v0=1,v1=2,v2=4,v3=3,v4=6.

F k ( y ) F_k(y) Fk(y)

k\Y12345678910
10112233445
20112233445
30112445568
40112445568
50112466778

i k ( y ) i_k(y) ik(y)

k\y12345678910
10111111111
20112222222
30112333333
40112333333
50112355555

void TrackSolution(int v[N],int w[N],int tagi[][B+1]){
	//x[i-1]标记第i种物品的件数
	int x[N];
	for(int i=0;i<N;i++)
		x[i]=0;
	int y=B,j=tagi[N][B];
	while (tagi[j][y]!=0){
		 j=tagi[j][y];
		//标记函数尾ik(y)标记的物品取一件
		x[j-1]=1; 
		y=y-w[j-1];
		while (tagi[j][y]==j){
			y=y-w[j];
			x[j-1]=x[j-1]+1;
		}
	}
}

计算选取方案

观察标记函数右下角开始

i 5 ( 10 ) = 5 i_5(10)=5 i5(10)=5

第5种物品确定选取(先按1件处理) 继续背包剩余重量 10 − w 4 10-w_4 10w4

i 5 ( 10 − w 4 ) = i 5 ( 4 ) = 2 i_5(10-w_4)=i_5(4)=2 i5(10w4)=i5(4)=2

第2种物品确定选取 (先按1件处理) 并确定上一步第5种物品只选了1件 继续背包剩余重量 4 − w 1 4-w_1 4w1

i 5 ( 4 − w 1 ) = i 5 ( 0 ) = 0 i_5(4-w_1)=i_5(0)=0 i5(4w1)=i5(0)=0

确定上一步第2种物品只选了1件 结束

总终选取情况应为 01001

运行结果



背包问题变种:硬币找零问题


目标函数 m i n ∑ j = 1 n w j x j min\sum_{j=1}^nw_jx_j minj=1nwjxj

约束条件 ∑ j = 1 n v j x j = V \sum_{j=1}^nv_jx_j=V j=1nvjxj=V

F k ( y ) F_k(y) Fk(y) 表示只允许装前k 种硬币,硬币总价值等于y 时包的最小重量。

F k ( y ) F_k(y) Fk(y) 有两种情况:不装第k种硬币或至少装1件第k种硬币。
①不装第k种硬币,那么只能用前k-1种硬币装入包,包的限制价值仍为y,所以最小重量是 F k − 1 ( y ) F_{k-1}(y) Fk1(y)
②装1枚第k种硬币,那么装入的第k种硬币价值为 v k v_k vk,重量为 w k w_k wk,剩下的硬币仍要在前k种里选择。于是问题规约为包限制价值 y − v k y-v_k yvk的情况下前k种硬币取得最小重量,即 F k ( y − v k ) + w k F_k(y-v_k)+w_k Fk(yvk)+wk

递推方程: F k ( y ) = m i n ( F k − 1 ( y ) , F k ( y − v k ) + w k ) F_k(y)=min(F_{k-1}(y),F_k(y-v_k)+w_k) Fk(y)=min(Fk1(y),Fk(yvk)+wk)

标记函数: i k ( y ) = { i k − 1 ( y ) F k − 1 ( y ) < F k ( y − v k ) + w k k F k − 1 ( y ) > = F k ( y − v k ) + w k i_k(y)=\begin{cases}i_{k-1}(y)\quad\quad F_{k-1}(y)<F_k(y-v_k)+w_k\\k\quad\quad\quad\quad F_{k-1}(y)>=F_k(y-v_k)+w_k\end{cases} ik(y)={ik1(y)Fk1(y)<Fk(yvk)+wkkFk1(y)>=Fk(yvk)+wk

void dpbag(int v[N],int w[N],int F[][V+1],int tagi[][V+1]){
	for(int k=0;k<=N;k++){
		F[k][0]=0;//前k种硬币总价值不超过0时重量
		tagi[k][0]=0;//前k种硬币总价值不超过0时标记
	}
	for(int y=0;y<=V;y++){
		F[0][y]=0;
		F[1][y]=y*w[0];//只装第一种硬币时重量
		tagi[1][y]=1;
	}
 
	for(int k=1;k<=N;k++){
		for(int y=1;y<=V;y++){
            
			if(y-v[k-1]<0){
				F[k][y]=F[k-1][y];
				tagi[k][y]=tagi[k-1][y]; 
			}
			else{
				//允许装入k种硬币,重量的两种情况:
				//不装第k种硬币或至少装1枚第k种硬币
				F[k][y]=F[k-1][y]>F[k][y-v[k-1]]+w[k-1] ? (F[k][y-v[k-1]]+w[k-1]):F[k-1][y];
				tagi[k][y]=F[k-1][y]>F[k][y-v[k-1]]+w[k-1]?k:tagi[k-1][y];
			}
		}
	}
}

w 0 = 1 , w 1 = 2 , w 2 = 4 , w 3 = 6. w_0=1,w_1=2,w_2=4,w_3=6. w0=1,w1=2,w2=4,w3=6.

v 0 = 1 , v 1 = 4 , v 2 = 6 , v 3 = 8. v_0=1,v_1=4,v_2=6,v_3=8. v0=1,v1=4,v2=6,v3=8.

F k ( y ) F_k(y) Fk(y)

k\Y123456789101112
1123456789101112
2123234545676
3123234545676
4123234545676

i k ( y ) i_k(y) ik(y)

k\y123456789101112
1111111111111
2111222222222
3111223322332
4111223322332

void TrackSolution(int v[N], int w[N], int tagi[][V + 1]) {
	//x[i-1]标记第i种硬币的枚数
	int x[N];
	for (int i = 0; i < N; i++)
		x[i] = 0;
	int y = V, j = tagi[N][V];
	while (tagi[j][y] != 0) {
		j = tagi[j][y];
		//标记函数最下角ik(y)标记的硬币取一枚
		x[j - 1] = 1;
		y = y - v[j - 1];
		while (tagi[j][y] == j) {
			y = y - v[j-1];
			x[j - 1] = x[j - 1] + 1;
		}
	}
	printf_s("硬币数量:\n");
	for (int k = 0; k < N; k++)
	{
		printf_s(" %d", x[k]);
	}
}

计算选取方案

观察标记函数右下角开始

i 4 ( 12 ) = 2 i_4(12)=2 i4(12)=2

第2种硬币确定选取(先按1枚处理) 继续总额剩余价值 12 − v 2 12-v_2 12v2

i 2 ( 12 − v 2 ) = i 2 ( 8 ) = 2 i_2(12-v_2)=i_2(8)=2 i2(12v2)=i2(8)=2

第2种物品确定选取 (先按2件处理) 继续总额剩余价值 8 − v 2 8-v_2 8v2

i 2 ( 8 − v 2 ) = i 2 ( 4 ) = 2 i_2(8-v_2)=i_2(4)=2 i2(8v2)=i2(4)=2

第2种物品确定选取 (先按3件处理) 继续总额剩余价值 4 − v 2 4-v_2 4v2

i 2 ( 4 − v 2 ) = i 2 ( 0 ) = 0 i_2(4-v_2)=i_2(0)=0 i2(4v2)=i2(0)=0

确定上一步第2种物品只选了3件 结束

总终选取情况应为 0300

运行结果


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值