动态规划及动态规划的应用

前言

相比于其他算法思想,动态规划的难度较大。同时,动态规划也是最强力的一种算法思想。有很多问题,用贪婪思想或者分而治之无法简洁而高效的解决,但是用动态规划就可以。

动态规划

在学习动态规划前,我们先与之前学过的算法思想对比一下。

  1. 贪婪算法:没有全局策略,每一步都基于局部最优解。在有些问题中,无法取得最优解。
  2. 分而治之:将问题分解为许多独立的子问题,并解决每一个子问题,再将每个子问题的解组合起来,形成最初问题的解。
  3. 动态规划:将问题分解为一系列重叠的子问题,组合较小子问题的解决方案以形成较大子问题的解决方案。

动态规划的演变

我们从斐波那契数列入手:

递归式处理:
斐波那契数列是学习递归时的经典案例,按照一般递归方法,我们这样处理。

/**伪代码*/
F(n):
	if(n==0) return 0;
	if(n==1) return 1;
	return F(n-1)+F(n-2);

这种递归式处理的一大弊端就是冗余式计算,如果我们要计算F(6),那么递归栈长这样:
在这里插入图片描述
其中从F(4),F(3),F(2),F(1)都被计算了多次。

为了删除冗余计算,我们可以使用两种方法。

  • 第一种:带有记忆的递归。将之前计算的值都保存在一个数组内,如果数组内已经包含所需要的值,直接从数组中提取元素,不需要再进行递归。
/**伪代码*/
TOP-DOWN(n):
Initialize M[0...n] with -1
M[0] = 0; M[1} = 1;
return F(n);

F(n):
if(M[n]==-1):
	M[n] = F(n-1)+F(n-2);
return M[n];
  • 第二种:自底向上计算,先计算子问题,在组合起来成最初的问题。
/**伪代码*/
BOTTOM-UP(n):
Initialize M[0...n] with -1
M[0] = 0; M[1} = 1;
For i = 2 to n:
	M[i] = M[i-1]+M[i-2];

其中,第二种方法已经包含了我们要学的动态规划算法思想,即动态规划也是自底向上的并且也是迭代的。或者说,这便是斐波那契问题的动态规划解法。

我们再看一个更近一步的例子:可以有负边但无环的有向加权图单源最短路径问题

这个算法要和dijkstra算法区分,dijkstra算法适合没有负边可以有环的有向加权图的单源最短路径问题。

这个算法的基础是无环,便可以写出一拓扑序列。根据拓扑序列自底向上进行迭代
在这里插入图片描述
如上图所示,上图带有负边的图,可以经过对边的拉伸或者缩短,可以化为下图,下图中的节点按照拓扑顺序排序。

自底向上+迭代算法(动态规划思想)

/**伪代码*/
DAG-SHORTEST-PATHS(V,E,l,s)
对图G中的V进行拓扑排序;
FOREACH node v IN V:
	M[v] = inf;
M[s] = 0;
FOREACH node IN V:
	FOREACH edge(u,v) IN E:
		M[v] = min{
    
    M[v],M[u]+l(u,v)}

在这里,我们需要注意的关键点为 M [ v ] = m i n ( M [ v ] , M [ u ] + l ( u , v ) ) M[v] = min{(M[v],M[u]+l(u,v))} M[v]=min(M[v],M[u]+l(u,v)),按照拓扑序列排序后,后面节点的最短路径蕴藏于它的前驱节点最短路径它与前驱节点边的权值中。这便是上述中,相互重叠的子问题的表现形式。

动态规划求解问题的一般步骤:

  • 问题可以分解为一系列重叠的子问题,并且对最优原则是适用的。
  • 最优原则:无论过程过去的状态和决策如何,对前面的决策所形成的状态而言,余下的决策必须构成最优策略。
  • 建立动态规划的递归方程(Bellman equation)
  • 求解动态规划的递归方程式以获得最优解
  • 沿着最优解的生成过程进行回溯

背包问题

动态规划的经典应用。

问题描述

  • n n n件物品,编号为 1 , 2 , 3 , 4... n 1,2,3,4...n 1,2,3,4...n,每个物品有价值 v i v_i vi和重量 w i w_i wi( v , w v,w v,w都为整数)
  • 有一个能承重 W W W的背包。
  • 目标:在不超过背包承重的情况下,装入物品的价值最大。

算法思想

定义 O P T ( i , w ) OPT(i,w) OPT(i,w)为在编号为 1 , 2 , 3... i 1,2,3...i 1,2,3...i中选取物品,并使总重量不超过 w w w的价值最大集合。

目标 O P T ( n , W ) OPT(n,W) OPT(n,W)便为问题的解(在编号为 1 , 2 , 3... n 1,2,3...n 1,2,3...n中选取物品,并使总重量不超过 W W W的价值最大集合)。

递归方程
O P T ( i , w ) = { 0 i = 0 O P T ( i − 1 , w ) w i > w m a x [ O P T ( i − 1 , w ) , O P T ( i − 1 , w − w i ) + v i ] o t h e r w i s e OPT(i,w)=\left\{ \begin{array}{lcl} 0 & {i = 0}\\ OPT(i-1,w) & {w_i>w}\\ max{[OPT(i-1,w),OPT(i-1,w-w_i)+v_i]} & {otherwise}\\ \end{array} \right. OPT(i,w)=0OPT(i1,w)max[OPT(i1,w),OPT(i1,wwi)+vi]i=0wi>wotherwise

伪代码

KNAPSACK(n,W,w1,w2...,wn,v1,v2,v3...,vn):
FOR w = 0 TO W
	M(0,w) = 0;
FOR i = 1 TO n:
	FOR w = 0 TO W:
		IF(wi > w): OPT(i,w) = OPT(i-1,w)
		ELSE: OPT(i,w) = max{
    
    OPT(i-1,w), OPT(i-1,w-wi)+vi}
RETURN M(n,W)

时间复杂度:Θ(nW)
空间复杂度:Θ(nW)

有环且有负边单源最短路径问题(Bellman-Ford)

问题描述

有向加权图,有负边,有环,甚至有负环的单源最短路径问题(刚刚我们讨论的利用拓扑自底向上的算法没有环, 注意区分这三个单源最短路径算法,这个是最强大的)。

算法思想

定义: O P T ( i , v ) OPT(i, v) OPT(i,v)为从源点 s s s到顶点 v v v的边的个数不超过 i i i的最短路径。
目标 O P T ( n − 1 , v ) OPT(n-1,v) OPT(n1,v)(即从源点 s s s到顶点 v v v的边的个数不超过 n − 1 n-1 n1的最短路径,超过n-1要么成环,要么不是简单路径。)
递归方程
O P T ( i , v ) = { 0 i = 0 , s = v + ∞ i = 0 , s ≠ v m i n [ O P T ( i − 1 , v ) , min ⁡ ( u , v ) ∈ E [ O P T ( i − 1 , u ) + l u v ] ] o t h e r w i s e OPT(i,v)=\left\{ \begin{array}{lcl} 0 & {i = 0,s=v}\\ +\infty & {i=0,s \not= v}\\ min{[OPT(i-1,v),\min \limits_{(u,v)\in E}[OPT(i-1,u)+l_{uv}]]} & {otherwise} \end{array} \right. OPT(i,v)=0+min[OPT(i1,v),(u,v)Emin[OPT(i1,u)+luv]]i=0s=vi=0s=votherwise

伪代码

BELLMAN-FORD(V,E,l,s):
FOREACH node v IN V:
	d[v] = inf
	predecessor[v] = null
d[s] = 0
FOR i = 1 TO n-1:
	FOREACH node v IN V:
		FOREACH edge(u,v) IN E:
			IF(d[v] > d[u] + l(u,v))
				d[v] = d[u] + l(u,v)
				predecessor[v] = u
FOREACH edge(u,v) IN E:
	IF (d[v] > d[u]+l(u,v))
		RETURN FALSE;
RETURN TURE

上述伪代码中,含有几处优化的地方

  1. 使用两个数组来代替二维数组,减少了空间复杂度
  2. 对于最外层的for循环,第一次是找边数为1的路径,第二次是边数为2的路径,第n-1次边是边数为(n-1)的路径。
  3. 最后的return false是检测是否含有负环,如果图中有加权值为负的环,那么不存在最短路径。

时间复杂度: O ( m n ) O(mn) O(mn)
空间复杂度: O ( n ) O(n) O(n)

所有顶点对之间的最短路径(Floyd-Warshall)

问题描述

在一个没有负环的有向加权图中,找出所有顶点对之间的最短路径。

算法思想

如果利用Bellman-Ford算法计算,需要的时间复杂度为 O ( m n 2 ) O(mn^2) O(mn2)。现在我们考虑一种更快的算法。

定义 O P T ( i , j , k ) OPT(i,j,k) OPT(i,j,k)为从 i i i j j j中间顶点的编号不超过 k k k的最短路径。该定义包含两种可能:如果该路径中不包含顶点 k k k,那么最短路径的长度应为 O P T ( i , j , k − 1 ) OPT(i,j,k-1) OPT(i,j,k1);如果该路径包含顶点 k k k,那么最短路径长度为 O P T ( i , k , k − 1 ) + O P T ( k , j , k − 1 ) OPT(i,k,k-1)+OPT(k,j,k-1) OPT(i,k,k1)+OPT(k,j,k1)
目标 O P T ( i , j , n ) OPT(i,j,n) OPT(i,j,n)
递归方程
O P T ( i , j , k ) = { w i j k = 0 m i n [ O P T ( i , j , k − 1 ) , O P T ( i , k , k − 1 ) + O P T ( k , j , k − 1 ) ] k > 0 OPT(i,j,k)=\left\{ \begin{array}{lcl} w_{ij} & {k = 0}\\ min[OPT(i,j,k-1),{OPT(i,k,k-1)+OPT(k,j,k-1)}] & {k >0} \end{array} \right. OPT(i,j,k)={ wijmin[OPT(i,j,k1)OPT(i,k,k1)+OPT(k,j,k1)]k=0k>0

伪代码

FLOYD-WARSHALL(W):
n = W.rows
D(0) = W // 存储OPT(i,j,0)的二维数组(矩阵)
FOR k = 1 TO n
	let D(k) = (OPT(i,j,k))be a new matrix
	FOR i = 1 TO n
		FOR j = 1 TO n
			OPT(i,j,k) = min{
    
    OPT(i,j,k-1)OPT(i,k,k-1) + OPT(k,j,k-1)}
RETURN D(n)
		

时间复杂度 O ( n 3 ) O(n^3) O(n3)
空间复杂度 O ( n 2 ) O(n^2) O(n2)

猜你喜欢

转载自blog.csdn.net/qq_41882686/article/details/108044958