算法导论——lec 11 动态规划及应用

时间:2021-12-26 09:32:16

和分治法一样,动态规划也是通过组合子问题的解而解决整个问题的。分治法是指将问题划分为一个一个独立的子问题,递归地求解各个子问题然后合并子问题的解而得到原问题的解。与此不同,动态规划适用于子问题不是相互独立的情况。即各个子问题包括公共的子子问题。在这样的情况下。假设用分治法会多做很多不必要的工作,反复求解同样的子子问题。

而动态规划将每一个子问题的解求解的结果放在一张表中,避免了反复求解。

一、 动态规划介绍

1、 动态规划方法介绍: 动态规划主要应用于最优化问题, 而这些问题通常有非常多可行解。而我们希望从中找出具有最优值的解,即最优解。

2、 设计一个动态规划程序算法的过程:

a、 刻画最优解的结构;

b、 递归定义最优解的值;

c、 按自底向上的方式计算最优解的值;

d、 由计算结果构造一个最优解。

二、 装配线调度问题

算法导论——lec 11 动态规划及应用

问题: 已知两条装配线如上图所看到的。每一个装配站所用时间为aij。要求输出装配一辆汽车所需的最短时间。

分析: 给定一个解。我们能够在Θ(n)时间内计算出所须要的时间。可是这种解总共同拥有2^n个。穷举全部的解来找出最优解是不明智的。

我们用动态规划来解决这个问题。

步骤一:描写叙述最优解的结构的特征

假设通过装配站S1j的最快路线经过了S1(j-1),那么这条路线必然利用了从開始点到S1(j-1)的最快路线。否则假设有更快的路线到达S1(j-1),那么我们就能找到到达S1j的更快的路线。同理。假设通过装配站S1j的最快路线经过了S2(j-1),那么这条路线也必然是利用了从開始点到S2(j-1)的最快路线。

更一般地:假设一个问题的最优解包括了子问题的一个最优解,那我们称这个性质为最优子结构。

有了最优子结构的性质。我们就能够利用自问题的最优解构造原问题的最优解。

由于通过装配站S1j的最快路线。必然要经过第1或第2条路线的第j-1个网站。因此,通过装配站S1j的最快路线必然利用了从開始点到S1(j-1)然后直接到S1j,或者从開始点到S2(j-1)然后从装配线2到装配线1而到达S1j。同理,到达S2j的最快路线也是类似的过程。所以对于装配线问题,通过建立子问题的最优解,就能够建立原问题某一个实例的最优解。

步骤二:利用子问题的最优解递归定义一个最优解的值

我们选择在两条装配线上。通过装配站 j 的最快路线问题做为子问题,j = 1,2,...,n。令f i[j] 表示一个底盘从起点到装配站Sij的最快可能时间。我们终于的目标是确定底盘通过工厂的全部路线的最快时间 f* 。底盘必须一路经由装配线1或2通过装配站n,然后到达工厂出口。因为这些路线的最快者就是通过整个工厂的最快路线。有 f* = min(f1[n] + x1, f2[n] + x2)。而f11 = e1 + a11, f21
= e2 + a21,

f1[j] = min { f1[j-1] + a1j, f2[j-1] + t2(j-1) + a1j }

f2[j] = min { f1[j-1] + t1(j-1) + a2j, f2[j-1] + a2j }

合并上面的公式,得到:

算法导论——lec 11 动态规划及应用

步骤三: 依照自底向上的方式计算最优解的值

依据上面的公式,我们逐步求得子问题的最优解例如以下:

算法导论——lec 11 动态规划及应用

步骤四: 由计算结果构造一个最优解

由上面求得的fi[j] 和 li[j],我们能够构造出一个最优解。

Fastest-Way(a, t, e, x, n)
1 f1[1] <-- e1 + a11
2 f2[1] <-- e2 + a21
3 for j <-- 2 to n
4 do if f1[j-1] + a1j <= f2[j-1] + t2(j-1) + a1j
5 then f1[j] <-- f1[j-1] + a1j
6 I1[j] <-- 1
7 else f1[j] <-- f2[j-1] + t2(j-1) + a1j
8 I1[j] <-- 2
9 if f2[j-1] + a2j <= f1[j-1] + t1(j-1) + a2j
10 then f2[j] <-- f2[j-1] + a2j
11 I2[j] <-- 2
12 else f2[j] <-- f1[j-1] + t1(j-1) + a2j
13 I2[j] <-- 1
14if f1[n] + x1 <= f2[n] + x2
15 then f* <-- f1[n] + x1
16 <span style="white-space:pre"> </span>I* <-- 1
17 else f* <-- f2[n] + x2
18 I* <-- 2

计算出f和I以后。须要构造在通过工厂的最快的路线中使用的装配站的序列,以下过程以序号递减的顺序输出各个装配站:

Print-Station(I, n)
1 i <-- I*
2 print "line " i ", station " n
3 for j <-- n downto 2
4 do i <-- Ii[j]
5 print "line " i ", station " j-1

顺着计算倒着找。

三、 矩阵链乘法

矩阵连乘积的最优计算次序问题:对于给定的n个矩阵{A1 ,A2 ,…,An }(当中Ai的维数为pi-1×pi 。i=1,2,…,n),怎样确定计算矩阵连乘积A1 A2 …An的一个计算次序。使得依此次序计算矩阵连乘积须要的数乘次数最少。

解决问题最easy想到的是穷举搜索,列出全部可能的次序计算乘积次数,选出次数最小的方案。其实,对于n个矩阵的连乘积,设有P(n)个不同的计算次序。那么P(n)满足例如以下的递推关系式:

算法导论——lec 11 动态规划及应用

P(n)实际上是Catalan数, 即P(n)=C(n-1) :(C(n)定义例如以下)

算法导论——lec 11 动态规划及应用

能够证明,P(n)的下界为2^n。非常明显,穷举的方法并不可取。

以下我们用动态规划的方法来解这个问题:

步骤一: 寻找最优的子结构

问题的一个关键特征是:计算A1...n的一个最优加括号把乘积在Ak和Ak+1之间断开。则前缀子链A1...k的加括号也是最优的。相同后缀子链Ak+1...n的加括号也是最优的。

步骤二: 利用子问题的最优解递归定义一个解的值

设Ai…j ,1≤i≤j≤n。所需的最少标量乘法次数为m[i,j]。原问题的最优值为m[1,n]。m[i,j]能够递归地定义为:

算法导论——lec 11 动态规划及应用

步骤三: 依照自底向上的方式计算最优解的值

依据m[i,j]的递归定义。easy写一个递归程序来计算m[1,n]。简单地递归计算将耗费指数计算时间。然而, 我们注意到,在递归计算过程中,不同的子问 题个数仅仅有θ(n^2)个。

由此可见。在递归计算时。很多子问题被反复计算多次。这也是该问题可用动态规划算 法求解的又一显著特征。

用动态规划算法解此问题,能够用自底向上的方式进行计算。在计算过程中。保存已解决的 子问题答案,每一个子问题仅仅计算一次。而在后 面须要时仅仅要简单查一下。从而避免大量的重 复计算,终于得到多项式时间的算法。

该算法填表m的方式相应于求解按长度递增的矩阵链上的所有括号问题。

算法导论——lec 11 动态规划及应用

步骤四: 由计算结果构造一个最优解

m[i,j]给出了最优值,即计算Ai…j所需的最少数乘次数。同一时候还确定了计算Ai…j的最优次序中的断开位置k,也就是说,对于这个k有m[i,j] = m[i,k] + m[k+1,j] + pi-1 pk pj 。

若将相应于m[i,j]的断开位置k记录在s[i,j]中。则相应的最优解便可递归地构造出来。

Matrix-Chain-Order(p)
1 n<--length[p] - 1
2 for i <-- 1 to n
3 do m[i, i]<--0
4 for l <-- 2 to n
5 do for i <-- 1 to n
6 <span style="white-space:pre"> </span>do j <-- i+l-1
7 <span style="white-space:pre"> </span>m[i, j]<--INF
8 for k<--i to j-1
9 do q<--m[i,k] + m[k+1,j] + p[i-1]*p[k]*p[j]
10 if q < m[i, j]
11 then m[i, j]<--q
12 s[i, j]<--k
13 return m and s

分析:MATRIX-CHAIN-ORDER具有嵌套循环结构, 执行时间为O(n3)。算法还须要Θ(n2) 空间存放m和s。

MATRIX_CHAIN_ORDER己记录了构造一个最优解所须要的所有信息。

Print-Optimal-Parents(s, i, j)
1 if i = j
2 do print "Ai"
3 else print "("
4 Print-Optimal-Parents(s, i, s[i,j])
5 Print-Optimal-Parents(s, s[i, j]+1, j]
6 Print ")"

四、 动态规划问题的关键特征

上面我们介绍了怎样用动态规划的方法来解决一些问题,那么当我们遇到一个问题的时候,我们怎样推断能否够用动态规划的方法来求解呢?这里我们介绍使用动态规划程序设计方法的最优化问题的两个要素:最优子结构和重叠子问题。

我们还要讨论备忘录方法来充分利用重叠子结构的性质。

1、 设计动态规划算法的第1步一般是要刻划最优解的结构。当问题的最优解包括了其子问题的最优解时,称该 问题具有最优子结构性质。问题的最优子结构性质提供了该问题可用动态规划算法求解的重要线索。

2、 子问题空间: 为了描写叙述子问题空间。能够遵循这样一条有效的经验规则。就是尽量保持这个空间简单,然 后在须要时再扩充它。

3、 最优子结构在问题域中以两种方式变化:有多少个子问题被使用在原问题的一个最优解中; 在决定一个最优解使用哪些子问题时有多少种选择。

4、 一个动态规划算法的执行时间依赖于上面讨论的两个因素的乘积:子问题的总个数和每个 子问题中有多少选择。

a、 在装配线调度中,共同拥有Θ(n)个子问题,而且仅仅有两个选择来检查每一个子问题,所以运行时间为Θ(n)。

b、 对于矩阵乘法。共同拥有Θ(n^2)个子问题,在每一个子问题中又至多有n-1个选择,因此运行时间为O(n^3)

5、 动态规划以自底向上的方式来利用最优子结构:首先找到子问题最优解。然后找到问题的 一个最优解。问题解的代价往往是子问题的代 价加上选择本身带来的代价。

6、 要注意在不能使用最优子结构的时候,就一定不能如果它可以使用。考虑以下两个问题:已 知一个有向图G=(V,E)和结点u, v∈V

a、 无权最短路径:找出一条从u到v的包括最少边数的路径。这种一条路径必须是简单路径,由于从路 径中去掉一个回路后,会产生边数更少的路径。

--最优子结构

b、 无权最长简单路径:找出一条从u到v的包括最多边数的简单路径。

我们须要增加简单性的要求。否 则,就能够随意地遍历一个回路随意多次。来得到 有随意多的边数的路径。--非最优子结构(其实,这个问题不能用动态规划解。是NP全然的)

根本的差别在于:a的子问题之间是相互独立的,b的子问题是不独立的。

7、 子问题独立:子问题的解不会影响同一问题中还有一个子问题的解。

8、可用动态规划算法求解的问题应具备的还有一基本要素是子问题的重叠性质

也就是说,在用递归算法自顶向下解此问题时,每次产生的子问题并不总是新问题,有些 子问题被重复计算多次。

动态规划算法正是利用了这样的子问题的重叠性质,对每个子问题仅仅解一次,而后将其解保存在一个表格中, 当再次须要解此子问题时,仅仅是简单地用常数时间查看 一下结果。通常,不同的子问题的个数随输人问题的大 小呈多项式增长。因此,用动态规划算法通常仅仅须要多 项式时间,从而获得较高的解题效率。

当解某一问题的直接递归算法所产生的递归树中。同样的子问题重复出现。而且不同子问题的 个数又相对较少时。用动态规划算法是有效的

9、 动态规划要求其子问题既要独立又要重叠: 假设同一问题的两个子问题不共享资源,则它们就是独立的; 对两个子问题来说。假设他们确实是同样的子问题。仅仅是作为不同问题的子问题出现的 话。是重叠的,则它们是重叠的。

10、动态规划算法的一个变形是备忘录方法:

a、与动态规划算法一样,备忘录方法用一个表格来保存已解决的子问题的答案,在再碰到该子 问题时。仅仅要简单地查看该子问题的解答,而 不必又一次求解。

b、 不同的是。备忘录方法採用的是自顶向下的递归方式,而动态规划算法採用的是自底向上的
非递归方式。

c、 备忘录方法的控制结构与直接递 归方法的控制结构同样。差别仅在于备忘录方 法为每一个解过的子问题建立了备忘录以备须要 时查看,避免了同样子问题的反复求解。

11、 备忘录方法:备忘录方法为每一个子问题建立一个记录项,初始化时,该记录项存入一个特殊的值,表示该 子问题尚末求解。

a、 在求解过程中,对碰到的每一个子问题,首先查看其对应的记录项。若记录项中存储的是初始化时存入 的特殊值,则表示该子问题是第一次遇到。此时需 要对该子问题进行求解,并把得到的解保存在其相 应的记录项中。以备以后查看。

b、 若记录项中存储的不是初始化时存入的特殊值,则表示该子问题己被求解过,仅仅要从记录项中取出 读子问题的解答就可以。

Memoized-Matrix-Chain(p)
1 n <-- length[p]-1
2 for i<--1 to n
3 do for j<--i to n
4 do m[i, j]<--INF
5 return Lookup-Chain(p, 1, n) Lookup-Chain(p, i, j)
1 if m[i,j] < INF
2 then return m[i,j]
3 if i = j
4 then m[i,j]<--0
5 else for k<--i to j-1
6 do q<--Lookup-Chain(p, i, k) + Lookup-Chain(p, k+1, j) + Pi-1*Pk*Pj
7 if q < m[i,j]
8 then m[i,j]<--q
9 return m[i, j]

与动态规划算法MATRIX_CHAIN_ORDER一样, 备忘录算法MEMOIZED_ MATRIX_CHAIN用数组m[1…n,1…n]的单元m[i,j]来记录解子问题Ai…j的最优计算量。MEMOIZED_MATRIX_CHAIN耗时O(n3)。

综上所述,矩阵连乘积的最优计算次序问题可用自顶向下的备忘录算法或自底向上的动态规划算法在O(n3)时间内求解。

这两个算法都利用了子问题重叠性质。

总共同拥有θ(n2)个不同的子问题。

对每一个子问题,两种方法都仅仅解一次,并 记录答案。再碰到该问题时,不又一次求解而简单地取用已 得到的答案。因此。节省了计算量,提高了算法的效率。

12、 动态规划和备忘录方法:

a、 一般地讲,当一个问题的全部子问题都至少要解一次时, 用动态规划算法解比用备忘录方法好。

此时。动态规划算 法没有不论什么多余的计算。

b、 当子问题空间中的部分子问题可不必求解时,用备忘录方法则较有利,由于从其控制结构能够看出,该方法仅仅解那 些确实须要求解的子问题。

五、 最长公共子序列

问题: 最长公共子序列(LCS)问题:给定两个序列X=<x1 , x2 , …, xm >和Y=<y1 , y2 , … , yn >。要求找出X和Y的一个最长公共子序列。

1、 最长公共子序列的结构: LCS的最优子结构性质

设序列X=<x1 , x2 , …, xm >和Y=<y1 , y2 , …, yn >的一个最长公共子序列Z=<z1 , z2 , …, zk >

a、 
若xm = yn,则zk = xm = yn。且Zk-1是Xm-1和Yn-1的一个LCS;

b、 若xm <> yn。则zk <> xm蕴含Z是Xm-1和Y的一个LCS;

c、 若xm <> yn,则zk <> yn 蕴含Z是X和Yn-1的一个LCS。

2、 一个递归解:由最长公共子序列问题的最优子结构性质可知,要找出X=<x1 , x2 , …, xm >和Y=<y1 , y2 , …, yn >的最长公共子序列,可按下面方式递归地进行

a、 当xm =yn 时。找出Xm-1 和Yn-1 的最长公共子序列。然后在其尾部加上xm (=yn )就可以得X和Y的一个最长公共子序列。

b、 当xm ≠yn 时。必须解两个子问题。即找出Xm-1 和Y的一个最长公共子序列及X和Yn-1 的一个最长公共子序列。这两个公共子序列中较长者即为X和Y的一个最长公共子序列。

由此递归结构easy看到最长公共子序列问题具有子问题重叠性质。比如,在计算X和Y的最长公共子序列时,可能要计算出X和Yn-1及Xm-1和Y的最长公共子序列。

而这两个子问题都包括一个公共子问题,即计算Xm-1和Yn-1的最长公共子序列。

以c(i, j)表示Xi和Yj的最长公共子序列的长度。

算法导论——lec 11 动态规划及应用

3、 依照自底向上的方式计算最优解的值:

LCS_LENGTH(X,Y)以序列X=<x1 , x2 , …, xm >和Y=<y1 , y2 , …, yn >作为输入。输出两个数组c[0..m ,0..n]和b[1..m ,1..n]。当中c[i,j]存储Xi与Yj的最长公共子序列的长度。b[i,j]记录指示c[i,j]的值是由哪一个子问题的解达到的。这在构造最长公共子序列时要用到。

最后,X和Y的最长公共子序列的长度记录于c[m,n]中。

因为c[i,j]须要用到c[i-1,j-1], c[i-1,j]和c[i, j-1]的值。所以我们能够一行一行地计算c中元素的值。

LCS-Length(X, Y)
1 m<--length[X]
2 n<--length[Y]
3 for i<--1 to m
4 do c[i, 0]<--0
5 for j<--1 to n
6 do c[0, j]<--0
7 for i<--1 to m
8 do for j<--1 to n
9 do if xi = yj
10 then c[i, j]<--c[i-1, j-1] + 1
11 b[i,j]<--"\"
12 else if c[i-1, j] > c[i, j-1]
13 c[i,j]<--c[i-1,j]
14 b[i,j]<--"|"
15 else c[i,j]<--c[i,j-1]
16 b[i,j]<--"——"
17return c and b

算法的复杂性为O(mn)

4、 由计算结果构造一个最优解

由算法LCS_LENGTH计算得到的数组b可用于高速构造序列X=<x1 , x2 , …, xm >和Y=<y1 , y2 , …, yn >的最长公共子序列。首先从b[m,n]開始,沿着当中的箭头所指的方向在数组b中搜索。当b[i,j]中遇到"↖"时。表示Xi与Yj的最长公共子序列是由Xi-1与Yj-1的最长公共子序列在尾部加上xi 得到的子序列;当b[i,j]中遇到"↑"时,表示Xi与Yj的最长公共子序列和Xi-1与Yj的最长公共子序列同样;当b[i,j]中遇到"←"时。表示Xi与Yj的最长公共子序列和Xi与Yj-1的最长公共子序列同样。

Print-LCS(b, X, i, j)
1 if i = j
2 do return
3 if b[i, j] = "\"
4 do Print-LCS(b, X, i-1, j-1)
5 print xi
6 else if b[i, j] = "|"
7 Print-LCS(b, X, i-1, j)
8 else if b[i,j] = "——"
9 Print-LCS(b, X, i, j-1)

在算法LCS中。每一次的递归调用使i或j减1。因此算法的计算时间为O(m+n) 。

5、 算法改进:

a、  在算法LCS_LENGTH和LCS中,可进一步将数组b省去。其实,数组元素c[i,j]的值仅由c[i-1,j-1],c[i- 1,j]和c[i,j-1]三个值之中的一个确定,而数组元素b[i,j]也仅仅是用来指示c[i,j]到底由哪个值确定。因此。在算法LCS中, 我们能够不借助于数组b而借助于数组c本身暂时推断c[i,j]的值是由c[i-1,j-1],c[i-1,j]和c[i,j-1]中哪一个数值元素所确定,代价是Ο(1)时间。既然b对于算法LCS不是必要的,那么算法LCS_LENGTH便不必保存它。这一来。可节省θ(mn)的空间,而LCS_LENGTH和LCS所须要的时间分别仍然是Ο(mn)和Ο(m+n)。只是。因为数组c仍须要Ο(mn)的空间,因此这里所作的改进。仅仅是在空间复杂性的常数因子上的改进。

b、 假设仅仅须要计算最长公共子序列的长度,则算法的空间需求还可大大降低。

其实。在计算c[i,j]时,仅仅用到数组c的第i行和第i-1行。因此,仅仅要用2行的数组空间就能够计算出最长公共子序列的长度。更进一步的 分析还可将空间需求减至min(m, n)。

六、 最优二叉查找树

问题:给定一个由n个互异的keyword组成的序列K=<k1 , k2 , ..., kn >。且keyword有序。我们从这些keyword中构造出一颗二叉查找树。对每一个关 键字ki,一次搜索为ki的概率是pi。

某些搜索的值可能不在K内,因此还有n+1个“虚拟键”d0 , d1 ,
d2 , ..., dn代表不在K内的值。详细地。d0代表全部小于k1的值,dn 表示全部大于kn的值,而对于i = 1, 2, ..., n -1,虚拟键di 代表ki和ki+1之间的值。对每一个虚拟键di , 一次搜索相应于di的概率是qi。

能够看到n个节点的二叉树共同拥有:Ω(4^n/n^1.5)

1、 最优子结构:假设一棵最优二叉查找树T有一颗包括keywordki , ..., kj的子树T'。那么这棵子树T' 对于keywordki , ..., kj 和虚拟键di-1 , ..., dj的问题也必然是最优的。

2、 递归解:

给定keywordki , ..., kj,当中之中的一个如果是kr (i≤r≤j),将是包括这些键的一棵最优子树的根。根kr 的左子树包括keywordki , ..., kr-1 (和虚拟键di-1 ,...,dr-1 ),右子树包括keywordkr+1 , ..., kj (和虚拟键dr , ..., dj ). 仅仅要我们检查全部的根kr。当中i ≤r ≤j。并且确定全部包括keywordki, ..., kr-1和kr+1, ..., kj的最优二叉查找树。就能够保证找到一棵最优的二叉查找树。

选取子问题域为找一个包括keywordki , ..., kj的最优二叉查找树,当中i ≥1, j ≤n, 且j ≥i –1, j=i-1意味着没有真实的keyword。仅仅有虚拟键di-1。

定义e[i, j] 为搜索一棵包括keywordki , ..., kj的最优二叉查找树的代价。终于,我们希望计算e[1,n]。

假设kr 是一颗包括keywordki , ..., kj的最优子树的根,则有

算法导论——lec 11 动态规划及应用

将e[i,j]重写:

算法导论——lec 11 动态规划及应用

我们选择有最低期望搜索总代价的结点作为 根,从而得到终于的递归公式:

算法导论——lec 11 动态规划及应用

3、计算 最优解的值:

把e[i,j]保存在表e[1 ‥n + 1, 0 ‥n]中。第一维的下标须要达到n+1而不是n。原因是为了有一个仅仅包括虚拟键dn。 须要计算和保存e[n + 1, n]。

使用j ≥i -1的表项e[i, j], 表root[i, j]来记录包括keywordki , ..., kj的子树的根。这个表仅仅使用1≤i≤j≤n的表项。

为了提高效率,还须要一个表格。不是每当计算e[i,j]时都从头计算w(i,j)(这将用Θ(j - i)步加法) ,而是把这些值保存在w[1‥n+1, 0‥n] 中。对基础情况,w[i, i-1]=qi-1,当中1≤i≤n。对j ≥i,计算

算法导论——lec 11 动态规划及应用

能够计算出Θ(n2)个w[i, j]。每一个须要Θ(1)。

Optimal-BST(p, q, n)
1 for i<--1 to n+1
2 do e[i, i-1]<--qi-1
3 w[i, i-1]<--qi-1
4 for l<--1 to n
5 do for i<--1 to n-l+1
6 do j<--i+l-1
7 e[i,j]<--INF
8 w[i,j]<--w[i,j-1]+pj+qj
9 for r<--i to j
10 do t<--e[i, r-1] + e[r+1, j] + w[i,j]
11 if t < e[i, j]
12 then e[i, j]<--t
13 root[i,j]<--r
14retrn e and root

分析: 与MATRIX-CHAIN-ORDER一样。for有三层嵌套,每一个循环下标至多n个值。所以有O(n^3)。最优二叉树查找和矩阵链乘积中的循环下标并没有刚好同样的界。可是他们在全部方向上都 至多差1。所以如同矩阵链乘积问题,最优二叉查找树的的下界也为Ω(n^3);故OPTIMAL-BST 过程须要Θ(n^3)的执行时间。

4、 构造最优解

由上述Optimal-BST得到的e和root,我们能够知道最优解的值。由root能够知道怎样构造一个最优解。

七、 每对顶点间的最短路径

问题: 讨论图上每对顶点间的最短路径问题

a、 能够通过单元最短路径算法执行|V|次来解决每对定点见的最短路径问题。

b、 本节要介绍一个动态规划算法,用来解决有向图G=(V,E)上每对顶点间的最短路径问题。动态规划的每一次主循环都将引发一个与矩阵乘法 运算十分相似的操作,因此算法看上去非常像是 反复的矩阵乘法。

算法的执行时间为 Θ(V3lgV)。还有一种动态规划算法Floyd-Warshall算法,该算法的执行时间为Θ(V3)。

算法一:

1、 如果图以邻接矩阵W=(wij )来表示。考察从顶点i到顶点j的一条最短路径p,如果p至多包括m条边。如果途中不存在权值为负的回路。则m必是有限值。

如果i=j,则路径p权值为0并且没有边。若顶点i和顶点j是不同顶点,则把路径p分解为 ,当中路径p'至多包括m-1条边。由引理24.1,p'是从i到k的一条最短路径,因而δ(i, j) = δ(i, k) + wkj。

2、 如今设是从顶点i到顶点j的至多包括m条边的不论什么路径的权值最小值。当m=0时,从i到j存在一条不包括边的最短路径当且仅当i=j。

因此

算法导论——lec 11 动态规划及应用

3、 对m ≥1,先计算L(m-1, i, j)(从i到j的最短路径的权至多包括m-1条边),以及从i到j的最多包括m条边的路径的最小权值,后者是通过计算j的全部可能前趋k而得到的,然后取二者中的最小值作为L(m, i, j),因此我们递归定义

算法导论——lec 11 动态规划及应用

4、 把矩阵W= (wij )作为输入,来计算一组矩阵L(1), L(2),..., L(n-1),最后矩阵L(n-1) 包括实际的最短路径权值。注意对全部的顶点i, j∈V, L(1, i, j)= wij。因此L(1)=W。

5、 算法例如以下所看到的。给定矩阵L(m-1)和W,返回矩阵L(m) 。也就是它把已经计算出来的最短路径延长一条边。

Extend-Shortest-Path(L, W)
1 n-<rows[L]
2 Let L' be an nXn matrix
3 for i<--1 to n
4 do for j<--1 to n
5 do L'(i, j)<--INF
6 for k<--1 to n
7 do L'(i, j)<--min(L'(i, j), L(i,k)+wj)
8 return L'

算法执行时间为:Θ(n^3)。

6、 假设对过程EXTEND-SHORTEST- PATHS进行这种变换。并用0(表示+)替换∞(表示min),得到一个直接的Θ(n3)的矩阵乘法过程:

算法导论——lec 11 动态规划及应用
算法导论——lec 11 动态规划及应用

设A·B 代表过程EXTEND-SHORTEST-PATHS(A, B)返回的矩阵乘积,我们计算n-1个矩阵的序列

算法导论——lec 11 动态规划及应用

与前面的论证一样,矩阵L(n-1) = Wn-1包括最短路径的权值。

7、 以下的过程在Θ(n^4)时间内计算该序列

Slow-All-Pairs-Shortest-Paths(W)
1 n<--rows[W]
2 L(1)<--W
3 for m<--2 to n-1
4 do L(m)<--Extend-Shortest-Path(L(m-1), W)
5 return L(n-1)

算法改进:我们的目标并非计算出所有的L(m)矩阵,我们只须要矩阵L(n-1)。

通过计算下列矩阵序列,仅仅 需计算⌈lg(n -1)⌉个矩阵乘积就能计算出L(n-1)

算法导论——lec 11 动态规划及应用

以下过程利用反复平方技术计算上述矩阵序列:

Fast-All-Pairs-Shortest-Paths(W)
1 n<--rows[W]
2 L(1)<--W
3 m<--1
4 while m < n-1
5 do L(2m)<--Extend-Shortest-Path(L(m), L(m))
6 m<--2*m
7 return L(m)

分析: 由于⌈lg(n -1)⌉个矩阵乘积中的每个都须要Θ(n3) 时间,因此FASTER-ALL-PAIRS-SHORTEST- PATHS的执行时间为Θ(n^3lgn)。算法中不包括复杂数据结构。因此隐含于Θ中的常数非常小。

算法二: Floyd-Warshall算法——算法同意存在负值的边,但我们如果没有权值为负的回路。

1、 设G的顶点为V={1, 2,..., n},对某个k考虑顶点的一个子集{1, 2,..., k}。对随意一对顶点i, j∈V,考虑从i到j且中间顶点皆属于集合{1, 2,..., k}的全部路径。设p是当中的一条最小权值路径。Floyd-Warshall算法利用了路径p与i到j之间的最短路径之间的联系。这一联系依赖于k是否是路径p的一个中间顶点:

a、 假设k不是路径p的中间顶点,则p的全部中间顶点皆在集合{1,2,...,k-1}中。

因此从顶点i到顶点j且满足全部中间顶点皆属于集合{1,2,...,k-1}的一条最短路径。也相同是从i到j且满足全部中间顶点皆属于集合{1,2,...k}的一条最短路径。

b、 假设k是路径p的中间顶点,那么可将p分解为p1(i-->k)和p2(k-->j),由于顶点k不是路径p1上的一个中间顶点。所以p1是从i到k的一条最短路径,且其全部中间顶点皆属于集合{1,2,...,k-1}。

类似的,p2以是从k到j的一条最短路径,且其全部中间顶点皆属于集合{1,2,...,k-1}

2、 依据上述讨论。我们用下式给出一个递归式:

算法导论——lec 11 动态规划及应用

由于对于随意路径,全部的中间顶点都在集合{1,2,...,n}内。矩阵D给出了终于解.

3、 基于递归式。以下的自底向上的过程按k值递增顺序计算最优解的值

Floyd-Warshall(W)
1 n<--rows[W]
2 D(0)<--W
3 for k<--1 to n
4 do for i<--1 to n
5 do for j<--1 to n
6 do D(k,i,j)<--min(D(k-1, i, j), D(k-1, i, k) + D(k-1, k, j))
7 return D(n)

分析:Floyd-Warshall算法的执行时间是由第3-6行的三重嵌套for循环所决定的。每次执行第6行花费O(1)时间,因此算法的执行时间为Θ(n3)。

算法中不包括复杂数据结构。因此隐含于Θ中的常 数非常小。

4、 构造一个最优解:

在Floyd-Warshall算法中存在大量不同的方法来建立最短路径。

一种途径是计算最短路径权值 的矩阵D。然后依据矩阵D构造前驱矩阵Π。这样的方法能够在O(n^3)时间内实现。能够像Floyd-Warshall算法计算矩阵D(k)一样, 计算一个矩阵序列Π(0), Π(1),..., Π(n),当中Π=Π(n) 。并定义Pi(k, i, j)为从i出发的一条最短路径上顶点j的前趋,而这条路径上的中间顶点属于集合{1,
2,..., k}.

对于k=0:

算法导论——lec 11 动态规划及应用

对于k>= 1:

算法导论——lec 11 动态规划及应用