参考书籍:算法设计与分析——C++语言描述(第二版)
动态规划算法是另一种求解最优化问题的重要算法设计策略。对于一个问题,如果能从较小规模的子问题的最优解求得较大规模同类子问题的最优解,最终得到给定问题的最优解,这就是最优解的最优子结构特性。最优子结构特性使动态规划算法可以采用自底向上的方式进行计算,如果能在求解中保存已计算的子问题的最有解,当这些子最优解被重复引用时,则无须再次计算,从而节省了大量的计算时间。
动态规划算法的实质也是将较大的问题分解为较小的同类子问题,在这一点上它与分治法和贪心法类似。但动态规划法有自己的特点。分治法的子问题相互独立,相同的子问题被重复计算,而动态规划法解决了这种子问题重叠现象。贪心法要求针对问题设计最优量度标准,但这在很多情况下并不容易做到,而动态规划法利用最优子结构,自底向上从子问题的最优解逐步构造出整个问题的最优解,动态规划法可以处理不具备贪心准则的问题。
贪心算法在求解问题的每一步上根据最优量度标准做出某种决策,产生n-元组的一个分量。用于决策的贪心准则仅依赖于局部和以前做出的选择,但决不依赖将来的选择,也不依赖于子问题的解。
动态规划法每一步的决策依赖于子问题的解。为了在某一步上做出选择,需要先求解若干个子问题,再根据子问题的解做出决策,这就使得动态规划法求解问题的方法使自底向上(bottom-up)的。
当一个问题的最优解中包含了子问题的最优解时,则称该问题具有最优子结构特性(optimal substructure)。最优子结构特性是动态规划算法求解问题的必要条件,最优子结构特性也叫做最优性原理(principle of optimality)。
按照多步决策的方法,一个问题的活动过程可以分为若干个阶段(stage),每一个阶段可包含一个或多个状态(state)。也就是说,一个活动过程进展到某一阶段时,可以处于该阶段的某一个状态。多步决策求解方法是从初始阶段的初始状态出发,做出每一个阶段的决策(decision),形成一个决策序列(sequence of decision),该决策序列也称为一策略(policy)。对于每一个决策序列,可以用一个数值函数(即目标函数)衡量该决策的优劣。问题求解的目标是获取导致问题最优解的最优决策序列(optimal sequence of decision),也称最优策略。
最优性原理指出,一个最优策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,其余决策必定构成最优策略。这便是最优决策序列的最优子结构特性。
分治法将问题分解成若干个相互独立的子问题,但一个子问题分解所得到的子问题并不总是完全相互独立的,他们可能共享更小的子问题,被称为重叠子问题(overlapping subproblem)。动态规划法实施自底向上计算,并保存子问题的解,从而可以避免重复计算这些重叠子问题。
设计一个动态规划算法,通常可以按照以下4个步骤进行:
第1至3步是动态规划算法的基本步骤。最优解值是最优解的目标函数的值。
一个最优化多步决策问题是否适合用动态规划法求解有两个要素:最优子结构特性和重叠子问题。
最优子结构特性: 即问题的最优解包含了其子问题的最优解。
分析(证明)问题的最优子结构特性时:首先假设由问题的最优解导出的子问题的解不是最优的,然后再设法说明在这个假设下可构造出比原问题最优解更好的解,从而导致矛盾。
利用问题的最优子结构特性,以自底向上的方式递推地从子问题的最优解逐步构造出整个问题的最优解。最优子结构是问题能用动态规划算法求解的前提。
子问题重叠特性:递归算法求解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。
动态规划算法,对每一个子问题只解一次,而后将其解保存在一个表格(通常用二维数组)中,当再次需要解此子问题时,只是简单地用常数时间查看一下结果。
通常不同的子问题个数随问题的大小呈多项式增长。因此用动态规划算法只需要多项式时间,从而获得较高的解题效率。
多段图 G=(V,E) 是一个带权有向图,它具有如下特征:图中的结点被划分成 k≥2 个互不相交的子集 Vi,1≤i≤k 。其中 V1 和 Vk 分别只有一个结点, V1 包含源点(source)s, Vk 包含汇点(sink)t。对所有边 <u,v>∈E ,多段图要求若 u∈Vi ,则 v∈Vi+1(1≤i<k) ,边上的权值为 w(u,v) 。从s到t的路径长度是这条路径上边的权值之和。多段图问题(multistage graph problem)是求从s到t的一条长度最短的路径。
下图是一个5段图,共分为5个阶段,每个阶段包含多个状态,每一个结点代表一个状态。每一条从s到t的路径都可以看作是由每一个阶段做出的决策组成的决策序列所产生的结果。每条路径上的所有边的权值之和称为路径长度,这是问题的目标函数值。
对于上述多段图,可以证明它的最优解满足最优子结构特性。如果 (s,v2,v3,⋯,vk−1,t) 是一条从s到t的最短路径,那么 (v2,v3,⋯,vk−1,t) 必定是一条从 v2 到t的最短路径。(若不然,另有 (v2,q3,⋯,vk−1,t) 是一条从 v2 到t的最短路径,那么路径 (s,v2,q3,⋯,vk−1,t) 显然比 (s,v2,v3,⋯,vk−1,t) 更短,与假设矛盾。)这就说明对于多段图问题,最优性原理成立。
由于多段图问题具有最优子结构特性,启发我们从最后阶段开始,采用逐步向前递推的方式,由子问题的最优解来计算原问题的最优解。由于动态规划法的递推关系是建立在最优子结构的基础上的,相应的算法容易用归纳法证明其正确性。
多段图问题的向前递推式:
以上图的5段图,使用递推式,由后向前计算最优解值的步骤如下:
为得到具体的路径,我们可以定义 d(i,j) 来记录从第i阶段结点j到t的最短路径上该结点的下一个结点编号。很容易从d的值确定最短路径上的点。 (0,d(1,0)=1,d(2,1)=6,d(3,6)=9,d(4,9)=11,11)
//多段图的向前推进算法
template<class T>
T Graph::FMultiGraph(int k,int *p)
{
//采用邻接表存储图G
Tc *cost=new float[n];
int q, *d=new int[n];
cost[n-1]=0,d[n-1]=-1;//设置向前推进的初始值
for(int j=n-2;j>=0;j--){
//按n-2,...,0的次序计算cost和d
float min=INFTY;
//计算最小值的cost[j]
for(ENode *r=a[j];r;r=r->nextArc){
int v=r->adjVex;
if(r->w+cost[v]w+cost[v];
q=v;
}
}
cost[j]=min;
d[j]=q;//q是j再最短子路径上的后继结点
}
p[0]=0;//p[0]和p[n-1]是源点和汇点
p[k-1]=n-1;
c=cost[0];
for(j=1;j<=k-2;j++){
p[j]=d[p[j-1]];
//p[i]是最短路径上第i阶段的点
}
delete []cost;
delete []d;
return c;
}
该算法,使用cost[j]保存结点j到t的最短路径,数组p保存对应于cost[0]的最短路径上的结点,它是问题的最优解。算法的时间复杂度为 Θ(n+e)
k段图问题也可以向后地推求解,其递推公式如下:
式中, Bcost(i,j) 是源点到第i阶段状态j的最短路径的长度,i是阶段编号,j是第i阶段的一个状态(结点)编号。
共同点:
将待求解的问题分解成若干子问题,先求解子问题,然后再从这些子问题的解得到原问题的解
不同点:
共同点:
都是求解最优化问题;都要求问题具有最优子结构性质。
不同点:
动态规划法是求解最优化问题的一种重要的算法设计技术。动态规划法的基本要素是最优子结构和重叠子问题。一个问题的最优解具有最优子结构,便可用动态规划法求解。动态规划法同样运用问题分解思想,但与分治法不同的是它采用自底向上计算方式,共享子问题无须重复计算。备忘录方法是动态规划法的一个变种,它采用自顶向下的方式计算最优解,但与分治法不同的是,备忘录方法为每个已经计算的子问题建立备忘录,从而避免了相同子问题的重复求解。