动态规划(DP)算法初识

先用大白话来说一下几种经典算法大概的意思:

  1. 贪心算法:我就一次机会,为了达到目的,其他的我啥也不考虑
  2. 回溯算法:我有无数次机会,还能怕我有达不到目的的时候?
  3. 动态规划算法:我能随意在任何时候进行存档读档,用最华丽的方法走到终点。

接下来的几篇文章可能都会围绕dp来说,从白话和较多篇幅去解释dp算法的内在

简述

动态规划求解出最优策略的原理是因为显著的降低了时间复杂度,提高了代码的运行效率,但动态规划也确实很难学,不过,一旦我们知道了解决思路,那么对于很多动态规划的问题,也都可以用相似的思路去解决。

将会用实际的问题,更好的去去理解动态规划

0-1 背包问题

在上一期的回溯的相关算法中,我们知道了如何采用递归的思想穷举所有解法,选出最优,然后解决问题,还是这道题,如果我们要对于一组不同重量、不可分割的物品,选择一些装入背包,在满足最大重量限制的前提下,如何最贴近最大重量。

我们首先用递归树看一下回溯的思路:

首先假设每个物品重量是 2 2 4 6 3,我们假设 i 表示将要决策第几个物品是否装入背包,cw表示当前背包中物品的总重量,然后用(i,cw)。比如,(2,2)表示将要决策第2个物品是否要装入背包,决策前,背包的总重为2.

我们可以从递归树明显的看出,有些子问题的求解是重复的,重复的计算必然会造成性能的丢失,如果我们能保留之前的结果,计算之前先检索一下,如果有计算过,那么直接拿过来用,肯定会提升一下性能。

动态规划(DP)算法初识_第1张图片

事实上,动态规划的思路已经和这相差无几,但还是让我们来看看动态规划的思路:

  1. 首先将整体求解分为n个阶段,每个阶段会决策一个物品是否放到包里。
  2. 不论放入还是不放入,背包的重量假设都造成了变化,而这些不同的变化,也就对应着递归树的节点
  3. 合并重复的节点,避免节点指数级增长
  4. 基于这一层的节点,推导出下一层的状态集合

然后用上面的思路,代入到0-1背包问题中做一个思路引导:

  1. 首先我们创建一个布尔类型的二维数组states[n][w],记录每个节点的不同状态。n代表第n个物品,w代表背包能承受的最大质量。
  2. 第0个(假设物品序列从0开始计算)物品的重量是2,放入,就是states[0][2],不放入,就是states[0][0],但是因为最大重量w是9,所以上面两个表达式的结果都是true。
  3. 然后是第一个物品,重量也是2,那么造成的结果就是,states[1][4],states[1][2],states[1][2],states[1][0]
  4. 然后我们发现,有两个表达式重复了,所以合并掉,其他三个因为重量小于9,所以也是true
  5. 不断继续,最后只需要选择最接近9还是true的就行了。

用false代表0,用true代表1,效果图如下:
动态规划(DP)算法初识_第2张图片

然后再用代码表达一下:

动态规划(DP)算法初识_第3张图片
这就是动态规划总体的思路,而且时间复杂度比起回溯的n²也有了很大的改善,为wn,n表示物品个数,而w表示可以承载的总重。

但是为了能够解决所有的情况,我们在存档的时候还是很占用空间的,那么有什么办法降低空间消耗吗?

实际上,我们可以将二维数组转换成一维数组,在动态转移的过程中都可以依靠这个一维数组来完成,下面来展示一下代码:

动态规划(DP)算法初识_第4张图片

0-1背包问题升级版

如果我们再将这些物品赋予价值概念的话,该如何让背包最大可承受重量一定的时候让价值更高。

首先,这个问题也是可以通过回溯的办法解决的:

动态规划(DP)算法初识_第5张图片
还是针对上面的代码画出递归树,和之前的思路相同,只不过加入了一个新的值:value

动态规划(DP)算法初识_第6张图片
我们发现,在很多情况下,重量相同但是价值会有区别,这也就是我们这个题目的目标,舍弃掉相对价值低的,保留相对价值更高的,保留递归的思路,其他状态就不考虑了。

于是我们继续采用动态规划思想。

设一个二维数组states[n][w+1],来记录每层都可以达到的不同状态,不过这次不再是布尔类型了,而是int类型,用来记录当前状态的最大价值。然后按照之前的思路,根据当前阶段,推导出后续状态。

然后翻译成代码:

动态规划(DP)算法初识_第7张图片
其实思路都是和之前相同的,但是在这里引入了第三个变量,也就是value,和之前的例子大同小异,时间复杂度也仍然是wn。

你可能感兴趣的:(算法,数据结构与算法)