今天是释然发题解的第十七天,以后每一天都会和大家分享学习路上的心得,希望和大家一起进步,一起享受coding的乐趣。
本文约2500字,预计阅读8分钟
昨天我们学习了动态规划,忘记的小伙伴们可以看一下哦:
动态规划
今天我们来聊一聊动态规划的背包型动态规划,明天和大家分享动态规划之线性动态规划的相关内容:
01 背包问题
有n个重量和价值分别为 Wi,Vi,的物品。从这些物品中挑选出总重量不超过 W的物品,求所有挑选方案中价值总和的最大值。
限制条件
• 1 <= n <=100
. 1 <= Wi,Vi <=100
. 1 <= W <=10000
我们在不知道背包型动态规划之前呢,我们考虑装n个物体,如果使用搜索时间复杂度就是2^n,
//明白dfs的含义是:搜索到第i个位置时,剩余空间为left时
//可以装的物品的最大价值,那么我们就可以不需要通过传入其它参数去不停地递归调用:
int dfs(int i,int left)
{
if(i>=n+1) return 0;//回溯条件
int dfs1=-INF,dfs2=-INF;
dfs1=dfs(i+1,left));
if(left>=w[i])
{
dfs2= dfs(i+1,left-w[i])+v[i];
}
return max(dfs1,dfs2);
}
总感觉这个搜索,会有多次的调用,没错,当它的参数一样的时候,那么它的值永远是固定的,那么此时我们通过记录每一次调用过后的值,就可以减少递归的次数
因此优化一下:
//我们同暂时数组来存储递归调用的值
int tem[MAX_N][MAX_N];
int dfs(int i,int left)
{
if( tem[i][tleft] != -1 )
return mem[i][tleft];
if(i>=n+1) return tem[i][left]=0;//回溯条件
int dfs1=-INF,dfs2=-INF;
dfs1=dfs(i+1,left));
if(left>=w[i])
{
dfs2= dfs(i+1,left-w[i])+v[i];
}
return tem[i][left]=max(dfs1,dfs2);
}
那么这个时候看起来就会好很多,减少了所有的重复运算,并且没有额外定义的变量作为函数的参数,
自己写着写着,记忆化搜索就出来了,真有意思是不是!
就是我们要有足够的信心相信dp数组它能够解决问题,在背包问题中呢,dfs的返回值就是我们想要的最大价值,那么不停地调用一定能够得到我们想要的解
判断搜索终止的条件,每一次搜索过了之后保存过值以后避免重复搜索,并且如果向dfs传入相同的参数之后,返回值应该是相同的,因此我们就可以使用记忆化搜索
1.根据记忆化搜索的参数可以直接得到dp的状态,反之亦然
2.根据记忆化搜索的递归关系可以写出状态转移方程,这个方程可以直接写出循环式的dp,反之亦然(但应该相反)
3.大部分记忆化搜索时空复杂度与 ** 不加优化的 ** dp 完全相同
4.两者的核心思想均为:利用对于相同参数答案相同的特性,对于相同的参数(循环式的dp体现为数组下标),记录其答案,免去重复计算,从而起到优化时间复杂度的作用
// 输入
int n,W;
int w[MAX_N], v[MAX_N];
// 从第i个物品开始挑选总重小于j的部分
int dfs(int i, int j)
{
// 已经没有剩余物品了
// 无法挑选这个物品
value = dfs(i + 1, j);
// 挑选和不挑选的两种情况都尝试一下
value= max((dfs(i + 1,j),dfs(i + 1, j - w[i]) + v[i]));
return value;
}
递推方程这里就不给了,就是分装和不装两种情况考虑
前i个物品中选取总重不超过j时的状态”
向“前i+1个物品中选取总重不超过j
和前i+1个物品中选取总重不超过j+w[i]时的状态”的转移
第一个状态:
dp[i + 1][j] = max(dp[i + 1][j], dp[i][j]);
第2个状态:
if (j + w[i] <= W)
{
dp[i + 1][j + w[i]] = max(dp[i + 1][j + w[i]]# dp[i][j] + v[i]);
}
01背包的问题
int dp[MAX_W + 1]; // DP数组
for (int i = 0; i < n; i++) {
for (int j = W; j >= w[i]; j--) {
dp[j] = max(dp[j], dp[j - w[i]] + v[i])
printf("%d\n" , dp[W]);
int dp[MAX_W + 1]; // DP数组
for (int i = 0; i =w[i]; j >= w[i]; j--)
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
printf("%d\n", dp[W])
这个代码i都是从0到n-1,指的是从放0个物体到放n个物体考虑是否需要放的问题,而差距就在j的循环上面,对于01背包问题呢,我们是需要通过之前的数据来存放这个物体是否需要放进的问题的,因此呢不能提前覆盖掉上一次的值的,但是呢对于完全背包的问题呢,因为每种物品都可以采集多次,因此不需要管之前的数据,所以递推方向刚好相反,不知道你们能不能理解,自己去推一遍,理解了其实也挺容易的
最后输出dp[i][j]就行啦!为什么是这个呢?
我们知道dp[i][j]表示的就是当物品数量为i时,空间大小为j的最大价值嘛?
题目所求的不就是这个嘛?
好了,今天的动态规划的背包型动态规划就说到这里。
释然每天发布一点自己学习的知识,希望2年后我们也能在ACM的赛场上见面,一起去追寻自己的程序猿之路吧!
后期也会和大家一起分享学习心得和学习经验呢,明天我们不见不散哦!
下期预告:
如果大家有什么建议或者要求请后台留言,释然也想和大家一起进步呀!
联系方式:[email protected]