动态规划
主要内容
- 动态规划的基本性质和步骤
- 最大子数组问题
- 0-1背包问题
- 旅行商问题
- 状态压缩动态规划
引例:斐波那契数列
从图片中不难看出,递归算法自顶向下的计算思路中,存在大量重复计算,当n的取值过大时,递归需要占据大量的时间和空间,需要优化。
解决方法 采用自底向上的计算方法,从第一个元素开始计算,从而消除重复计算。
基本性质
- 和分治类似:动态规划也是将原问题分解为子问题求解。
- 和分治不同:不是通过递归的方式,而是自底向上的求解问题。
机器人行走
还记得吗?这个题目我们在分治一章中讨论过,只能向右或向上行走,则到达终点的路径条数path(i,j) = path(i-1,j) + path(i,j-1).
- 分治需要计算多少次?55次。
- 动态规划需要的计算次数:12次。
动态规划适用的场景:
- 子问题并不独立,即子问题是可能重复的。
- 主要用于优化问题(求最优解),且问题具有最优子结构性质。
什么是最优子结构性质?
最优子结构性质指原问题的最优解一定包含了子问题的最优解。例:最短路径问题具有最优子结构性质,最长路径问题则不具备。
基本步骤
- 定义子问题,并分析最优解的结构性质:分治通常是将原问题对半分,而动态规划是将n规模的问题分解成n-1规模的问题。
- 找出最优解对应的最优值,并递归地定义最优值。
- 以自底向上的方式计算出最优值
- 根据计算最优值得到的信息,构造最优解。
最大子数组问题
0-1背包问题
0-1背包问题:给定n种物品和一个背包,物品i的重量是wi,其价值为vi,背包的容量为C(总承重为C),问应如何选择装入背包的物品,使得装入背包中的物品的总价值V最大化。
分析0-1背包问题的最优解的结构特征
- 一种情况是第n个物品不包括在最优解里:设x* = (x1,x2,...,xn-1,xn),则(x1,x2,...,xn-1)必为n-1个物品(剔除第n个物品),且背包容量为C情况下的最优解。
- 第二种情况是第n个物品包括在最优解里面:则(x1,x2,...,xn-1)必为n-1个物品,背包容量为C-wn情况下的最优解。
找出0-1背包问题最优解对应的最优值,并递归地定义最优值
在n个物品里,背包容量为C情况下的总价值,我们用m(n,C)表示最优值。
自底向上地求解最优值
根据m值矩阵得出最优解
所以最优解为{a,b,c},最优值为16。
0-1背包最优解
for i=1 to n do
if m(i,c)==m(i-1,c) then
x[i]=0
else
x[i]=1
c=c-w[i]
end if
end for
return x
算法复杂度分析: 从m(i,j)的递归式容易看出,算法需要O(nc)的计算时间。当背包容量C很大时,算法需要的计算时间较多。例如,当C>2^n时,算法需要Ω(n2^n)计算时间。
旅行商问题
旅行商问题最优解的结构特征
- 最优子结构性质:假设路径c1c2...cn-1cnc1是城市{c1,c2,...,cn-1,cn}的最短环路,取这个路径的任何一个子路径,如c1c2...ci必然是城市{c1,c2,...,ci}中从c1到ci经过其他城市一次且仅一次的最短路径。
- 旅行商问题的子问题是显然重叠的:如下两个路径c1c2c3c4...cn和c1c3c2c4...cn都具有相同的子路径c4...cn-1cn。
旅行商问题最优解对应的最优值,并递归地定义最优值
自底向上求解最优值
在此算法中,因TSP(c1,C,ci)中的c1可忽略,我们用一个二维数组TP[C][ci]存储TSP(c1,C,ci)的值,算法总复杂度为O(n^2 * 2^n)
构造最优解
while循环(语句4-18)依次往最短路径添加城市,每次添加实际上就是找出下一层哪一个TSP和当前城市c^形成了最短回路。
最长公共子序列
最长公共子序列的结构
子问题的递归结构
自底向上计算c[i][j]
计算最优值
由于在所考虑的子问题空间中,总共有Θ(mn)个不同的子问题,因此,用动态规划算法自底向上地计算最优值能提高算法的效率。
构造最长子序列
状态压缩动态规划
- 集合状态压缩:用二进制表示集合,之后用整型表示二进制,如旅行商问题中的TP数组。
- 空间状态压缩:自底向上的方法求解最优值的过程中,压缩最优值的存储空间。
集合状态压缩
整数的一些基本位操作:
- 判断第i比特是否为0:D&(L<<i)==0,若真,则为0,如假,则为1,其中L<<i表示将L左移i位;
- 将第i比特设置为1:D|(L<<i);
- 将第i比特设置为0:D&(~(L<<i);
- 将第i比特取反:D⊕(L<<i);
- 取出第i比特:D&(L<<i);
需要判断一下:j所代表的城市集合C是否包含了i所代表的城市,如果不包含,就无需做任何处理,如包含,则依次比较j集合所有下一层的TP值。