前景提要:
(1条消息) 动态规划(01背包问题)_物联网土猫的博客-CSDN博客
写完了最基础的01背包,那我们就要开是考虑优化问题了,现在让我们重新回到我们的代码看看有哪些地方可以让我们优化;
首先我们发现,我们计算属性的时候开的是二维数组,而且每次只是取到了前一个状态的数据,那么我们可能会想到,每次只保留前一次的数据,接下来只需要调用前一次的数据即可完成对下一组数的更新,其中一种方法就是滚动数组;
但是我这里给大家提供更简单的办法(绝对不是本人还没写过滚动数组并且想要直接给大家看最简单的方法)
首先我们先压缩二维数组变成一维;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
g[i][j]=g[i-1][j];//无论我们是否取第i件物品,这个继承要有
if(j>=v[i])//特判,如果可以取第i件物品,我们就会在两种选择中取其最优解
g[i][j]=max(g[i-1][j],g[i-1][j-v[i]]+w[i]);
}
}
这样的话,语句中的的g[i][j]=g[i-1][j]就缺少了横轴,那么相当于这部分的继承直接不存在了,所以可以直接删掉,也可以理解为,删掉了压缩掉的横轴后,代码变成了:g[j]=g[j],也就可以直接删除了;
接下来我们看判断部分,我们发先,我们删除了横轴后,也就没有了取前i个物品的概念,也就是说,我们把循环里的j初始值改为v[i],这样的话我们的判断也就可以去掉了;
for(int i=1;i<=n;i++)
{
for(int j=v[i];j<=m;j++)
{
g[j]=max(g[j],g[j-v[i]]+w[i]);
// g[i][j]=max(g[i-1][j],g[i-1][j-v[i]]+w[i]);
}
}
这个时候我们发现,循环里的两行代码代表的意义应该是相同的,但是既然我问出来了,他一定相同吗?
相同吗?答案肯定是不相同的!
我们的g[i-1][j-v[i]]是继承我们上一行的i,但是在这里使用的时候,因为是二维数组,所以上一行的i的一行的值是没有改变过的,但是我们发现,如果缩成一维的话,假设j=5的时候,最大值是g[j-v[i]]+w[i],这个时候我们发现,g[j-v[i]]肯定是位于j=5的前面的,如果这个值之前被替换过,那他就不等于我们原来二维数组的时候,相同位置的值。
那么你写的这个代码就是有问题的,我要求是没改变的值,你却把改变了的值带入,那不是乱套了吗?于是我们想出了另一个办法:
g[j-v[i]]肯定是在g[j]的前面的,也就是说,我们得保证,用到g[j-v[i]]的时候要保证他之前的数据是没有改变过的,那么我们可以把循环的顺序变一下,我们让j倒着遍历
代码:
for(int i=1;i<=n;i++)
{
for(int j=m;j>=v[i];j--)
{
g[j]=max(g[j],g[j-v[i]]+w[i]);
// g[i][j]=max(g[i-1][j],g[i-1][j-v[i]]+w[i]);
}
}
如此一来,即便是g[j-v[i]]被改变了,我们也不影响后续遍历。
这个时候有人要问了,诶呀,你之前的代码中不是也直接把g[i][j]=g[i-1][j]中的i-1部分直接删掉了吗?为什么就不见错误呢?
原因是,第二步的删除我们发现他会调用前一步相同位置之前的数据,但是你循环的时候不保证数据一定不变,但是第一步的继承我们是相同位置直接继承,也就是说,当前j位置数值的改变其实他本身的改变,又不用到之前的数据,也不考虑可能被改变过,所以就可以直接删除了!
至此,我们的代码优化完成,完结撒花