前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >动态规划_01背包_一维数组_路径记录

动态规划_01背包_一维数组_路径记录

作者头像
yifei_
发布2022-11-14 14:12:16
2470
发布2022-11-14 14:12:16
举报
文章被收录于专栏:yifei的专栏

前言   之前对0-1背包就理解的不是很好,并且时间长了会忘的。   这次又重新复习一下,理解了好几个以前没理解的点。

代码语言:javascript
复制
题目
  现有n件物品,每一件的重量是w[i],价值是v[i]。用一个容量为c的背包来装这些东西,
问如何选择物品才能使装的物品价值最大?(每件物品只能放一次)
思路
  我们会想该放哪i件物品到容量c的背包中呢。
  我们可以用dp(i,j)来表示前i件物品放入容量j的背包中地最大价值。针对第i件物品,我们要先考虑
  背包容量是否大于物品容量:
    如果小于:那就不放,dp(i,j)=dp(i-1,j)。
    如果大于:再考虑是否要放入:
        这个时候要从放或不放第i件物品中选择一个价值更大的:dp(i,j)=max{ dp(i-1,j) , dp(i-1,j-w(i))+v(i) }
	另外,起始值dp[0][j]=dp[i][0]=0;
算法实现
  首先用二维数组dp(i,j)变成dp[i][j]。
  dp[i][j]有好多的状态啊,能有n*c个,这么多状态按照怎样的顺序来计算呢,所以需要找出前后关系来。
  可以看出每一个状态都是跟上一个状态有关的,要求装i件时的价值需要知道装i-1件时候的最大价值,所以得有个外层循环i:0.....n
  每一次都是只需要dp[i-1][1,2,3.....]中的值,所以对于j来说呢好像顺序无所谓了。
  由此可以写出代码:
	-----------------------------------------
	#include<cstdio>
	#include <iostream>
	#include <cstring>

	using namespace std;

	int dp[1005][1005];

	int main(){
		int T;
		int M;
		int maxx=0;

		int t[1111];
		int p[1111];
		while(scanf("%d %d",&T,&M)!=EOF){
			maxx=0;
			memset(dp,0,sizeof(dp));
			memset(path,0,sizeof(path));
			for(int i=1;i<=M;i++){
				scanf("%d %d",&t[i],&p[i]);
			}
			for(int i=1;i<=M;i++){
				for(int j=1;j<=T;j++){  //因为j的顺序无所谓,for(int j=T;j>=1;j--)也可以
					if(j>=t[i]){
						dp[i][j]=max(dp[i-1][j],dp[i-1][j-t[i]]+p[i]);       
					}else{
						dp[i][j]=dp[i-1][j];
					}
				}
			}
			printf("%d\n",dp[M][T]);
		}
		return 0;
	}
	-----------------------------------------
打印状态矩阵dp
	样例:
		10 5//容量 物品数量
		2 6//第一件物品的 重量 价值
		2 3
		6 5
		5 4
		4 6

	  | 	1    2   3    4    5    6    7    8    9    10
	--+----------------------------------------------------
	1 |	0    6    6    6    6    6    6    6    6    6
	2 |	0    6    6    9    9    9    9    9    9    9
	3 |	0    6    6    9    9    9    9    11   11   14
	4 |	0    6    6    9    9    9    10   11   13   14
	5 |	0    6    6    9    9    12   12   15   15   15

	照着这个矩阵手动推算一遍会加深理解,可以按照i:0...n,j:0...c的顺序。
	也可以按照i:0...n,j:c...0的顺序,(按照这种顺序推算,也许就可以发现后面讲的优化为一维数组的方法。)

路径记录
	之前不是说每一件物品都有放或不放两种选择吗,想要记录路径就得在放的时候给标记一下。
	关键代码如下:
	----------------------
	for(int i=1;i<=M;i++){
		//for(int j=1;j<=T;j++){
		for(int j=T;j>=1;j--){
			if(j>=t[i]){
				dp[i][j]=max(dp[i-1][j],dp[i-1][j-t[i]]+p[i]);
				if(dp[i-1][j]<(dp[i-1][j-t[i]]+p[i])){//记录路径
					path[i][j]=1;//记录路径
				}//记录路径
			}else{
				dp[i][j]=dp[i-1][j];
			}
		}
	}
	----------------------
路径输出
	下面是路径矩阵
	0 1 1 1 1 1 1 1 1 1
	0 0 0 1 1 1 1 1 1 1
	0 0 0 0 0 0 0 1 1 1
	0 0 0 0 0 0 1 0 1 0
	0 0 0 0 0 1 1 1 1 1
	我们需要从这个矩阵中找出选取的哪些物品,应当从最后一个状态往前找。
	比如说有多条从一个点出发的路径,你想要找到达顶点a的路径,肯定是从a往前这一条路找过去就行了。
	代码如下:
	----------------------
	int i=M,j=T;//物品数量 背包容量
	while(i&&j){
		if(path[i][j]==1){
			printf("%d ",i);
			j-=t[i];
		}
		i--;
	}
	---------------------

一维数组优化
	还是以上面的数据为例:
	  | 	1    2   3    4    5    6    7    8    9    10
	--+----------------------------------------------------
	1 |	0    6    6    6    6    6    6    6    6    6
	2 |	0    6    6    9    9    9    9    9    9    9
	3 |	0    6    6    9    9    9    9    11   11   14
	4 |	0    6    6    9    9    9    10   11   13   14
	5 |	0    6    6    9    9    12   12   15   15   15
	观察这个矩阵,会发现某一个状态dp[i][j]跟两个元素有关,一个是它的正上方的元素,另一个是它的上一行的左边的某一个元素
	上面不是说过内层循环j可以从T....0吗,也就是j的顺序是无所谓的。
	那么我们可以用这种顺序来观察上面的矩阵,
	当我们计算dp[2][10]的时候,dp[2][10]=max{dp[1][10],dp[1][10-2]+3},结果是9,这时候可以不用把9写在第二行里,
	我们可以把它写在dp[1][10]的位置(其实这个位置原来的数据已经没用了,因为后面任何一个状态都不会再用刀这个值了)
	同样一次类推,把数据都写在第一行。
	就这样每一行从后往前计算,只用一个一维数组就够了。
	(如果每一行从前往后计算是不可以用一维数组的,比如在计算完dp[2][4]的时候,如果覆盖掉dp[1][4]的值,
	但是再计算后面的dp[2][*]的时候是会用到那个值的)
	最终代码:
------------------------------------------------
#include<cstdio>
#include <iostream>
#include <cstring>

using namespace std;

int dp[1005];
int path[1005][1005];

int main(){
    int T;
    int M;
    int t[1111];
    int p[1111];
    while(scanf("%d %d",&T,&M)!=EOF){
        memset(dp,0,sizeof(dp));
        memset(path,0,sizeof(path));
        for(int i=1;i<=M;i++){
            scanf("%d %d",&t[i],&p[i]);
        }
        for(int i=1;i<=M;i++){
            for(int j=T;j>=1;j--){
                if(j>=t[i]&&dp[j]<dp[j-t[i]]+p[i]){
                    path[i][j]=1;
                    dp[j]=dp[j-t[i]]+p[i];
                }
            }
        }
        printf("%d\n",dp[T]);

        for(int i=1;i<=M;i++){
            for(int j=1;j<=T;j++){
                printf("%d ",path[i][j]);
            }
            printf("\n");
        }

        int i=M,j=T;
        while(i&&j){
            if(path[i][j]==1){
                printf("%d ",i);
                j-=t[i];
            }
            i--;
        }
    }
    return 0;
}
/*
10 5
2 6
2 3
6 5
5 4
4 6
*/
---------------------------------------------------------

欢迎与我分享你的经验和见解。 转载请注明出处:http://taowusheng.cn/

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-10-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档