动态规划是一种非常有趣的思想,第一次听说这种思想就是看01背包的解法,网上有非常多的文章讲述状态转移方程,分解子问题,讲的也都很好,只是但对于我这种比较笨的人,当时我一直没有搞明白为什么动态规划可以保证在递推过程中做出的所有判断都是最优的。
后来我见到了一个很有意思的故事
(原文入口)讲的是关于国王分金矿,其实就是披了皮的01背包。他把这个问题用递归分解的方式分解成所谓的子任务,及国王找2个大臣分别给与不同的权限让他们办事,办完之后在比较那个方案更好,这时我觉得问题好像已经变了性质,国王所得到的结果一定是准确的,来辅助他做这个判断,也就是说在递归过程中,问题会被层层分解成更小的问题,而递归的出口其实就是,第一个矿或者最后一个矿挖不挖,在这个过程中所有的子问题都是在当前资源下的最优解,关于未来的问题,应该让国王去考虑,就是这坐金矿要不要挖,而对于国王来说,他得到的信息是当不挖这个矿,所有资源挖别的矿的收益,和挖了这个矿所得收益加剩余资源挖别的矿的收益,他只需要比较就可以的到正解。
上面这个分解任务的过程是一个递归,他会反复调用某些用过的数值,浪费复杂度,记忆化处理后,仍然不是很好用,所以一般的人在初次接触动态规划见到01背包是会发现,大部分答案都是从小到大,递推得到解的,实质上在递推的过程中,dp的状态时刻都是在目前情况下的最优解。而且他也不会影响未来做判断的结果,也就是未来选择与过去无关,我所提供的只是当前状态下所能达到的最优转态。
那我们再来分析一下01背包问题
我们想要知道有限空间所能携带的最大价值。于是我们定义数组dp[i][j]表示前i个物品如果有j空间,我能携带的最大价值,递推关系就是dp[i+1][j]=max(dp[i][j],dp[I][j-w[I]]+v[i])对于数组 存储的每一个值都是前i个物品如果有j空间是背包所能存储的最大价值,随着i的增大我们可以判断加入这个物品和不加的最优解我们比较的两个数组的意义其实就是仍然按没有这个这个物品处理或有这个物品,比较下当前空间减去该物品所需空间剩下的空间分给前i个物品所能得到的价值加该物品的价值和不放入该物品的情况,选出对于现在仍然最多所能存储的价值,他不会因为未来出现一个性价比超高的物品而改变,只要界定最初的转态,我们可以得到所有状态下的最高价值。
代码应该这样实现
for(int i=1;i< =n;i++)
for(int j=0;j<=w;j++)
{
if(j<w[i])dp[i+1][j]=dp[i][j];
else dp[i+1][j]=max(dp[i][j],dp[i][j-w[i]]+v[i];
}
对于这一问题实际上,如果我们只求最终的结果,那么数组开成一维也是一样的,在递归的过程中不同价值得最优局部解任会实时更新。、
在处理这个状态时,我们还有一种处理方式,就是把dp[i][j]定义为前i间物品中价值达到j所需的最小重量
答案就是满足dp[i][j]<=w的最大的j
递推式就演变成dp[i+1][j]=min(dp[i][j],dp[I][j-v[i]]+w[I])
for(int i=0;i<=N;i++)
for(int j=0;j<=sum;j++)
{
if(j<v[i])dp[i+1][j]=dp[i][j];
else dp[i+1][j]=min(dp[i][j],dp[i][j-v[i]]+w[i])
这两种方法都大同小异,真正的关键在于理解动态规划为什么可以得到最优的判断,因为在得到最终结果的过程中,每一步所表示的都是在局部条件下所能得到的最优解,至于未来做出的选择不会受过去的影响,在j逐渐增加的过程中,我们会比较所有情况下的过去最优解会不会因为新物品的出现而改变。
动态规划只是一种思想,面对不同情况问题是要从不同角度出发,关键在于找到问题的状态转移过程,并总结出递推式01背包实际上就是把问题分解成每一个物品要不要拿,在过程中放弃很多不合理的方案,事实上,刚接触动态规划时,很多人会认为我们舍弃这些方案是不是从贪心的角度出发,毕竟他们只是在局部情况下的不合理,但实际上我们在舍弃他们的同时,一定会从存在一个各方面都优于它的局部最优解。在问题的最后一步之前,我们都可以保证最后一步所要比较的只是不选最后一个物品所能获得的最大价值,和选这件物品的价值加前i件中只用j-w[I]的空间所能存储的最大价值。他们在一路的更新过程中,各自都达到了他们的最优解,最后一步的选择也与前面所舍弃的方案无关。
写这篇博客一方面是为了帮一些像我一样,初学时不能理解动态规划为什么可行的人理解dp,也是为了让自己梳理一下对动态规划的认识,写完发现其实还是很乱,希望它能帮助一些人更好的理解动态规划。
水平有限,如有错误,欢迎指正