文章目录
- 引言
- 三、最短路问题
- 3.1 最短路问题定义
- 3.2 Dijkstra 算法
- 3.2.1 算法基本依据
- 3.2.2 算法基本思想与步骤
- 3.3 逐次逼近算法(Bellman-Ford 算法)
- 3.4 Floyd 算法
- 写在最后
引言
承接前文,我们来学习图论中另一个经典问题 —— 最短路问题。
三、最短路问题
3.1 最短路问题定义
给定一个赋权有向图 G = ( V , A , W ) G=(V,A,W) G=(V,A,W) ,对每一个弧 a i j = ( v i , v j ) a_{ij}=(v_i,v_j) aij=(vi,vj) ,相应地有权 w ( a i j ) = w i j w(a_{ij})=w_{ij} w(aij)=wij ,又给定 G G G 中两个顶点 v s , v t v_s,v_t vs,vt 。设 P P P 是 G G G 中 v s → v t v_s \to v_t vs→vt 的一条路,定义路 P P P 的权是路中所有弧权重之和,记为 W ( P ) W(P) W(P) 。最短路问题就是要在所有 v s → v t v_s \to v_t vs→vt 的路中,求取一条权最小的路 P ∗ P^* P∗ ,称其为 v s → v t v_s \to v_t vs→vt 的最短路,记为 P ( v s , v t ) P(v_s,v_t) P(vs,vt) 。路 P ∗ P^* P∗ 的权称为 v s → v t v_s \to v_t vs→vt 的距离,记为 d ( v s , v t ) d(v_s,v_t) d(vs,vt) 。
显然, d ( v s , v t ) d(v_s,v_t) d(vs,vt) 不一定等于 d ( v t , v s ) d(v_t,v_s) d(vt,vs) 。我们主要研究有向网络,对于无向网络,每条边可视为双向弧。
3.2 Dijkstra 算法
Dijkstra 算法是 1959 年提出的用于解决非负权网络中寻找一个指定顶点到其他顶点的最短路的最好算法之一。
3.2.1 算法基本依据
Dijkstra 算法的基本思想基于以下三个出发点。
第一,最短路的子路还是最短路。
定理 1 —— 对于弧的权大于 0 的有向网络 G = ( V , A , W ) G=(V,A,W) G=(V,A,W) ,若 P P P 是 G G G 中的一条最短路,则 P P P 的子路也是最短路。
第二,设非负权网络 G = ( V , A , W ) G=(V,A,W) G=(V,A,W) 中, v s v_s vs 到所有其他顶点的最短路长度按大小排列为 d 0 ≤ d 1 ≤ ⋯ ≤ d n d_0 \leq d_1 \leq \dots \leq d_n d0≤d1≤⋯≤dn 。假设 d 1 , d 2 , … , d k d_1,d_2,\dots,d_k d1,d2,…,dk 为已求得对应的最短路径分别为 P 1 = P ( v s , v 1 ) , P 2 = ( v s , v 2 ) , … , P k = ( v s , v k ) P_1=P(v_s,v_1),P_2=(v_s,v_2),\dots,P_k=(v_s,v_k) P1=P(vs,v1),P2=(vs,v2),…,Pk=(vs,vk) ,并记 S k = { v s , v 1 , … , v k } S_k=\{v_s,v_1,\dots,v_k\} Sk={vs,v1,…,vk} ,则 P k P_k Pk 中弧的数目不大于 k k k 。
第三,最短路的迭代计算公式。
讲实话,硬看公式很头疼,最好结合算例来看,我也就不展示了,后面算法步骤中也会体现。
3.2.2 算法基本思想与步骤
Dijkstra 算法采用标号法,每个顶点有两个标号,一个用于标记路长,用 d ( v i ) d(v_i) d(vi) 表示;另一个用于标记从起点到终点路径的最后一条弧的起始点号(其实就是最短路中终点前一个点)。
网络顶点的标号分两类,一类是永久标号,一类是临时标号。当迭代到第 k k k 步时,获得永久标号的点意味着已经找到 v s v_s vs 到该点的最短路的路长和路径。
将获得永久标号的点放在 S k S_k Sk 集合中,获得永久标号的 d d d 值和 λ \lambda λ 值不再修改。获得临时标号的点(集合 T T T)意味着还没找到最短路。
算法的步骤如下:
第一步: 初始化,令 k = 0 , S 0 = { v s } , T 0 = { v 1 , v 2 , … , v n } , d 0 ( v s ) = 0 , d 0 ( v i ) = ∞ k=0,S^0=\{v_s\},T^0=\{v_1,v_2,\dots,v_n\},d^0(v_s)=0,d^0(v_i)=\infty k=0,S0={vs},T0={v1,v2,…,vn},d0(vs)=0,d0(vi)=∞ ,为临时标号值。 λ ( v i ) = v s \lambda(v_i)=v_s λ(vi)=vs ,表示最短路中点 v i v_i vi 的前一个点。 r e s e n t = v s resent=v_s resent=vs ,表示最新获得永久标号的点。
第二步: k = k + 1 k=k+1 k=k+1 ,对于所有临时标号 v l ∈ T k − 1 v_l \in T^{k-1} vl∈Tk−1 ,计算: d k ( v l ) = m i n { d k − 1 ( v l ) , d ∗ ( r e s e n t ) + w ( r e s e n t , v l ) } d^k(v_l)=min\{d^{k-1}(v_l),d^*(resent)+w(resent,v_l)\} dk(vl)=min{dk−1(vl),d∗(resent)+w(resent,vl)} 如果 d k ( v l ) < d k − 1 ( v l ) d^k(v_l)<d^{k-1}(v_l) dk(vl)<dk−1(vl) ,则 λ k ( v l ) = r e s e n t \lambda^k(v_l)=resent λk(vl)=resent ,否则, λ k ( v l ) = λ k − 1 ( v l ) \lambda^k(v_l)=\lambda^{k-1}(v_l) λk(vl)=λk−1(vl) 。
第三步: 若 v m v_m vm 满足是所有 T T T 标号中最小的,则 r e s e n t = v m , S k = S k − 1 ⋃ { v m } , T k = T k − 1 − { v m } resent=v_m,S^{k}=S^{k-1} \bigcup\{v_m\},T^k=T^{k-1}-\{v_m\} resent=vm,Sk=Sk−1⋃{vm},Tk=Tk−1−{vm} 。若 k = n k=n k=n ,算法结束;否则,转第二步。
Dijkstra 算法结束后, d ∗ d^* d∗ 值即为某点到起点的最短距离,通过每次迭代的 λ ∗ \lambda^* λ∗ 值进行回溯,可得到所有其他点到起点的最短路径。
3.3 逐次逼近算法(Bellman-Ford 算法)
北交大的书上第二个方法写的是 PDM 算法,以前从来没听过,还好看了一下去年大纲,没提到这个,我就不去看了。大纲里还说了一个叫 Ford 算法,网上查了查应该是 Bellman-Ford 算法,但是书上往后面却找不到 Ford 相关字眼,只有一个逐次逼近算法倒还有可能。
于是网上又一顿搜索逐次逼近算法,刚开始也是一点字眼都看不到,后来耐心性子先看了下这个算法的内容,在知乎的一篇文章中看到 Bellman-Ford 算法是基于逐次逼近的思想。两者比对一下,应该北交大书上的逐次逼近算法就是 Bellman-Ford 算法无疑了。
Dijkstra 算法的一个缺点就是,不适用于有负权重的网络,而逐次逼近算法便可以解决这个问题,它适用于有负权但不含负回路的有向赋权图。
逐次逼近算法的基本步骤与思路如下。
第一步:(赋初值) k = 1 k=1 k=1( k k k 为迭代步骤)。 d 1 j 1 = { 0 , v 1 = v j w 1 j , ( v 1 , v j ) ∈ A ∞ , ( v 1 , v j ) ∉ A d^1_{1j}=\begin{cases} 0, & v_1=v_j \\ w_{1j},& (v_1,v_j) \in A\\ \infty,&(v_1,v_j) \notin A \end{cases} d1j1=⎩ ⎨ ⎧0,w1j,∞,v1=vj(v1,vj)∈A(v1,vj)∈/A 对于赋权有向网络 G = ( V , A , W ) G=(V,A,W) G=(V,A,W) , v 1 v_1 v1 是指定的起点, d 1 j 1 d^1_{1j} d1j1 的含义是从 v 1 v_1 v1 点到 v j v_j vj 点最多含有一个弧的最短路的路长。
λ 1 j 1 \lambda_{1j}^1 λ1j1 为从 v 1 v_1 v1 点到 v j v_j vj 点最多含有一个弧的最短路的终点 v j v_j vj 的前一个点号。
第二步:递推,第三步:判断。
不是我省懒,实在是要通过具体算例才能看明白,我就不细说了。
而且这个方法有点像是遍历法了,我有点回忆起来当初上系统分析时也讲过这个,不过不是叫这个名。
3.4 Floyd 算法
当我们的需求是任意两点之间的最短路,而不是一点到其他所有点的最短路时,只能考虑重复使用 Dijkstra 算法,依次改变起点。如果此时网络中有负权,可以重复使用逐次逼近法来实现。显然,这样是比较繁琐的,而 Floyd 算法便是可以直接求出任意两点之间的最短路的一种算法,它也适用于含有负权的网络。
具体算法步骤是真不好用文字表达,至少我现在表达不出来,下次找机会出点视频更方便些。
写在最后
算法学习果然是艰辛和耗时的,难怪计算机的出现让图论发展迅速。不过也只有正确掌握算法的基本思想才能正确地用计算机进行辅助计算。
书上的数学描述对于我来说是很抽象的,基本上只能靠算例来加深理解,所以我目前给不了大家一些很通俗的解释,只能到这个水平了。