沃舍尔算法_[数据结构拾遗]图的最短路径算法
前言
本專題旨在快速了解常見的數(shù)據(jù)結(jié)構(gòu)和算法。
在需要使用到相應(yīng)算法時(shí),能夠幫助你回憶出常用的實(shí)現(xiàn)方案并且知曉其優(yōu)缺點(diǎn)和適用環(huán)境。并不涉及十分具體的實(shí)現(xiàn)細(xì)節(jié)描述。
圖的最短路徑算法
最短路徑問題是圖論研究中的一個(gè)經(jīng)典算法問題,旨在尋找圖(由結(jié)點(diǎn)和路徑組成的)中兩結(jié)點(diǎn)之間的最短路徑。
算法具體的形式包括:確定起點(diǎn)的最短路徑問題:即已知起始結(jié)點(diǎn),求最短路徑的問題。適合使用Dijkstra算法。
確定終點(diǎn)的最短路徑問題:與確定起點(diǎn)的問題相反,該問題是已知終結(jié)結(jié)點(diǎn),求最短路徑的問題。在無向圖中該問題與確定起點(diǎn)的問題完全等同,在有向圖中該問題等同于把所有路徑方向反轉(zhuǎn)的確定起點(diǎn)的問題。
確定起點(diǎn)終點(diǎn)的最短路徑問題:即已知起點(diǎn)和終點(diǎn),求兩結(jié)點(diǎn)之間的最短路徑。
全局最短路徑問題:求圖中所有的最短路徑。適合使用Floyd-Warshall算法。
主要介紹以下幾種算法:Dijkstra最短路算法(單源最短路)
Bellman–Ford算法(解決負(fù)權(quán)邊問題)
SPFA算法(Bellman-Ford算法改進(jìn)版本)
Floyd最短路算法(全局/多源最短路)
常用算法
Dijkstra最短路算法(單源最短路)
算法介紹:
迪科斯徹算法使用了廣度優(yōu)先搜索解決賦權(quán)有向圖或者無向圖的單源最短路徑問題,算法最終得到一個(gè)最短路徑樹。該算法常用于路由算法或者作為其他圖算法的一個(gè)子模塊。
指定一個(gè)起始點(diǎn)(源點(diǎn))到其余各個(gè)頂點(diǎn)的最短路徑,也叫做“單源最短路徑”。例如求下圖中的1號頂點(diǎn)到2、3、4、5、6號頂點(diǎn)的最短路徑。
使用二維數(shù)組e來存儲頂點(diǎn)之間邊的關(guān)系,初始值如下。
我們還需要用一個(gè)一維數(shù)組dis來存儲1號頂點(diǎn)到其余各個(gè)頂點(diǎn)的初始路程,如下。
將此時(shí)dis數(shù)組中的值稱為最短路的“估計(jì)值”。
既然是求1號頂點(diǎn)到其余各個(gè)頂點(diǎn)的最短路程,那就先找一個(gè)離1號頂點(diǎn)最近的頂點(diǎn)。通過數(shù)組dis可知當(dāng)前離1號頂點(diǎn)最近是2號頂點(diǎn)。當(dāng)選擇了2號頂點(diǎn)后,dis[2]的值就已經(jīng)從“估計(jì)值”變?yōu)榱恕按_定值”,即1號頂點(diǎn)到2號頂點(diǎn)的最短路程就是當(dāng)前dis[2]值。
既然選了2號頂點(diǎn),接下來再來看2號頂點(diǎn)有哪些出邊呢。有2->3和2->4這兩條邊。先討論通過2->3這條邊能否讓1號頂點(diǎn)到3號頂點(diǎn)的路程變短。也就是說現(xiàn)在來比較dis[3]和dis[2]+e[2][3]的大小。其中dis[3]表示1號頂點(diǎn)到3號頂點(diǎn)的路程。dis[2]+e[2][3]中dis[2]表示1號頂點(diǎn)到2號頂點(diǎn)的路程,e[2][3]表示2->3這條邊。所以dis[2]+e[2][3]就表示從1號頂點(diǎn)先到2號頂點(diǎn),再通過2->3這條邊,到達(dá)3號頂點(diǎn)的路程。
這個(gè)過程有個(gè)專業(yè)術(shù)語叫做“松弛”。松弛完畢之后dis數(shù)組為:
接下來,繼續(xù)在剩下的3、4、5和6號頂點(diǎn)中,選出離1號頂點(diǎn)最近的頂點(diǎn)4,變?yōu)榇_定值,以此類推。
最終dis數(shù)組如下,這便是1號頂點(diǎn)到其余各個(gè)頂點(diǎn)的最短路徑。
核心代碼:
//Dijkstra算法核心語句
for(i=1;i<=n-1;i++)
{
//找到離1號頂點(diǎn)最近的頂點(diǎn)
min=inf;
for(j=1;j<=n;j++)
{
if(book[j]==0 && dis[j]
{
min=dis[j];
u=j;
}
}
book[u]=1;
for(v=1;v<=n;v++)
{
if(e[u][v]
{
if(dis[v]>dis[u]+e[u][v])
dis[v]=dis[u]+e[u][v];
}
}
}
關(guān)于復(fù)雜度:M:邊的數(shù)量
N:節(jié)點(diǎn)數(shù)量
通過上面的代碼我們可以看出,我們實(shí)現(xiàn)的Dijkstra最短路算法的時(shí)間復(fù)雜度是O(N^2)。其中每次找到離1號頂點(diǎn)最近的頂點(diǎn)的時(shí)間復(fù)雜度是O(N)。
優(yōu)化:這里我們可以用“堆”(以后再說)來優(yōu)化,使得這一部分的時(shí)間復(fù)雜度降低到O(logN)。
另外對于邊數(shù)M少于N^2的稀疏圖來說(我們把M遠(yuǎn)小于N^2的圖稱為稀疏圖,而M相對較大的圖稱為稠密圖),我們可以用鄰接表來代替鄰接矩陣,使得整個(gè)時(shí)間復(fù)雜度優(yōu)化到O((M+N)logN)。
請注意!在最壞的情況下M就是N^2,這樣的話MlogN要比N^2還要大。但是大多數(shù)情況下并不會有那么多邊,因此(M+N)logN要比N^2小很多。
Dijkstra思想總結(jié):
dijkstra算法本質(zhì)上算是貪心的思想,每次在剩余節(jié)點(diǎn)中找到離起點(diǎn)最近的節(jié)點(diǎn)放到隊(duì)列中,并用來更新剩下的節(jié)點(diǎn)的距離,再將它標(biāo)記上表示已經(jīng)找到到它的最短路徑,以后不用更新它了。這樣做的原因是到一個(gè)節(jié)點(diǎn)的最短路徑必然會經(jīng)過比它離起點(diǎn)更近的節(jié)點(diǎn),而如果一個(gè)節(jié)點(diǎn)的當(dāng)前距離值比任何剩余節(jié)點(diǎn)都小,那么當(dāng)前的距離值一定是最小的。(剩余節(jié)點(diǎn)的距離值只能用當(dāng)前剩余節(jié)點(diǎn)來更新,因?yàn)榍蟪隽俗疃搪返墓?jié)點(diǎn)之前已經(jīng)更新過了)
dijkstra就是這樣不斷從剩余節(jié)點(diǎn)中拿出一個(gè)可以確定最短路徑的節(jié)點(diǎn)最終求得從起點(diǎn)到每個(gè)節(jié)點(diǎn)的最短距離。
用鄰接表代替鄰接矩陣存儲
總結(jié)如下:
可以發(fā)現(xiàn)使用鄰接表來存儲圖的時(shí)間空間復(fù)雜度是O(M),遍歷每一條邊的時(shí)間復(fù)雜度是也是O(M)。如果一個(gè)圖是稀疏圖的話,M要遠(yuǎn)小于N^2。因此稀疏圖選用鄰接表來存儲要比鄰接矩陣來存儲要好很多。
Bellman–Ford算法(解決負(fù)權(quán)邊問題)
思想:
bellman-ford算法進(jìn)行n-1次更新(一次更新是指用所有節(jié)點(diǎn)進(jìn)行一次松弛操作)來找到到所有節(jié)點(diǎn)的單源最短路。
bellman-ford算法和dijkstra其實(shí)有點(diǎn)相似,該算法能夠保證每更新一次都能確定一個(gè)節(jié)點(diǎn)的最短路,但與dijkstra不同的是,并不知道是那個(gè)節(jié)點(diǎn)的最短路被確定了,只是知道比上次多確定一個(gè),這樣進(jìn)行n-1次更新后所有節(jié)點(diǎn)的最短路都確定了(源點(diǎn)的距離本來就是確定的)。
現(xiàn)在來說明為什么每次更新都能多找到一個(gè)能確定最短路的節(jié)點(diǎn):
1.將所有節(jié)點(diǎn)分為兩類:已知最短距離的節(jié)點(diǎn)和剩余節(jié)點(diǎn)。
2.這兩類節(jié)點(diǎn)滿足這樣的性質(zhì):已知最短距離的節(jié)點(diǎn)的最短距離值都比剩余節(jié)點(diǎn)的最短路值小。(這一點(diǎn)也和dijkstra一樣)
3.有了上面兩點(diǎn)說明,易知到剩余節(jié)點(diǎn)的路徑一定會經(jīng)過已知節(jié)點(diǎn)
4.而從已知節(jié)點(diǎn)連到剩余節(jié)點(diǎn)的所有邊中的最小的那個(gè)邊,這條邊所更新后的剩余節(jié)點(diǎn)就一定是確定的最短距離,從而就多找到了一個(gè)能確定最短距離的節(jié)點(diǎn),不用知道它到底是哪個(gè)節(jié)點(diǎn)。bellman-ford的一個(gè)優(yōu)勢是可以用來判斷是否存在負(fù)環(huán),在不存在負(fù)環(huán)的情況下,進(jìn)行了n-1次所有邊的更新操作后每個(gè)節(jié)點(diǎn)的最短距離都確定了,再用所有邊去更新一次不會改變結(jié)果。而如果存在負(fù)環(huán),最后再更新一次會改變結(jié)果。原因是之前是假定了起點(diǎn)的最短距離是確定的并且是最短的,而又負(fù)環(huán)的情況下這個(gè)假設(shè)不再成立。
Bellman-Ford 算法描述:創(chuàng)建源頂點(diǎn) v 到圖中所有頂點(diǎn)的距離的集合 distSet,為圖中的所有頂點(diǎn)指定一個(gè)距離值,初始均為 Infinite,源頂點(diǎn)距離為 0;
計(jì)算最短路徑,執(zhí)行 V - 1 次遍歷;對于圖中的每條邊:如果起點(diǎn) u 的距離 d 加上邊的權(quán)值 w 小于終點(diǎn) v 的距離 d,則更新終點(diǎn) v 的距離值 d;
檢測圖中是否有負(fù)權(quán)邊形成了環(huán),遍歷圖中的所有邊,計(jì)算 u 至 v 的距離,如果對于 v 存在更小的距離,則說明存在環(huán);
偽代碼:
BELLMAN-FORD(G, w, s)
INITIALIZE-SINGLE-SOURCE(G, s)
for i 1 to |V[G]| - 1
do for each edge (u, v) E[G]
do RELAX(u, v, w)
for each edge (u, v) E[G]
do if d[v] > d[u] + w(u, v)
then return FALSE
return TRUE
SPFA(Bellman-Ford算法改進(jìn)版本)SPFA算法是1994年西安交通大學(xué)段凡丁提出
spfa可以看成是bellman-ford的隊(duì)列優(yōu)化版本,正如在前面講到的,bellman每一輪用所有邊來進(jìn)行松弛操作可以多確定一個(gè)點(diǎn)的最短路徑,但是用每次都把所有邊拿來松弛太浪費(fèi)了,不難發(fā)現(xiàn),只有那些已經(jīng)確定了最短路徑的點(diǎn)所連出去的邊才是有效的,因?yàn)樾麓_定的點(diǎn)一定要先通過已知(最短路徑的)節(jié)點(diǎn)。
所以我們只需要把已知節(jié)點(diǎn)連出去的邊用來松弛就行了,但是問題是我們并不知道哪些點(diǎn)是已知節(jié)點(diǎn),不過我們可以放寬一下條件,找哪些可能是已知節(jié)點(diǎn)的點(diǎn),也就是之前松弛后更新的點(diǎn),已知節(jié)點(diǎn)必然在這些點(diǎn)中。
所以spfa的做法就是把每次更新了的點(diǎn)放到隊(duì)列中記錄下來。
偽代碼:
ProcedureSPFA;
Begin
initialize-single-source(G,s);
initialize-queue(Q);
enqueue(Q,s);
while not empty(Q) do begin
u:=dequeue(Q);
for each v∈adj[u] do begin
tmp:=d[v];
relax(u,v);
if(tmp<>d[v])and(not v in Q)then enqueue(Q,v);
end;
end;
End;
如何看待 SPFA 算法已死這種說法?
在非負(fù)邊權(quán)的圖中,隨手卡 SPFA 已是業(yè)界常識。在負(fù)邊權(quán)的圖中,不把 SPFA 卡到最慢就設(shè)定時(shí)限是非常不負(fù)責(zé)任的行為,而卡到最慢就意味著 SPFA 和傳統(tǒng) Bellman Ford 算法的時(shí)間效率類似,而后者的實(shí)現(xiàn)難度遠(yuǎn)低于前者。
Floyd最短路算法(全局/多源最短路)此算法由Robert W. Floyd(羅伯特·弗洛伊德)于1962年發(fā)表在“Communications of the ACM”上。同年Stephen Warshall(史蒂芬·沃舍爾)也獨(dú)立發(fā)表了這個(gè)算法。Robert W.Floyd這個(gè)牛人是朵奇葩,他原本在芝加哥大學(xué)讀的文學(xué),但是因?yàn)楫?dāng)時(shí)美國經(jīng)濟(jì)不太景氣,找工作比較困難,無奈之下到西屋電氣公司當(dāng)了一名計(jì)算機(jī)操作員,在IBM650機(jī)房值夜班,并由此開始了他的計(jì)算機(jī)生涯。此外他還和J.W.J. Williams(威廉姆斯)于1964年共同發(fā)明了著名的堆排序算法HEAPSORT。
算法介紹:
上圖中有4個(gè)城市8條公路,公路上的數(shù)字表示這條公路的長短。請注意這些公路是單向的。我們現(xiàn)在需要求任意兩個(gè)城市之間的最短路程,也就是求任意兩個(gè)點(diǎn)之間的最短路徑。這個(gè)問題這也被稱為“多源最短路徑”問題。
現(xiàn)在需要一個(gè)數(shù)據(jù)結(jié)構(gòu)來存儲圖的信息,我們?nèi)匀豢梢杂靡粋€(gè)4*4的矩陣(二維數(shù)組e)來存儲。
核心代碼:
for(k=1;k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(e[i][j]>e[i][k]+e[k][j])
e[i][j]=e[i][k]+e[k][j];
這段代碼的基本思想就是:
最開始只允許經(jīng)過1號頂點(diǎn)進(jìn)行中轉(zhuǎn),接下來只允許經(jīng)過1和2號頂點(diǎn)進(jìn)行中轉(zhuǎn)……允許經(jīng)過1~n號所有頂點(diǎn)進(jìn)行中轉(zhuǎn),求任意兩點(diǎn)之間的最短路程。一旦發(fā)現(xiàn)比之前矩陣內(nèi)存儲的距離短,就用它覆蓋原來保存的距離。
用一句話概括就是:從i號頂點(diǎn)到j(luò)號頂點(diǎn)只經(jīng)過前k號點(diǎn)的最短路程。另外需要注意的是:Floyd-Warshall算法不能解決帶有“負(fù)權(quán)回路”(或者叫“負(fù)權(quán)環(huán)”)的圖,因?yàn)閹в小柏?fù)權(quán)回路”的圖沒有最短路。例如下面這個(gè)圖就不存在1號頂點(diǎn)到3號頂點(diǎn)的最短路徑。因?yàn)?->2->3->1->2->3->…->1->2->3這樣路徑中,每繞一次1->-2>3這樣的環(huán),最短路就會減少1,永遠(yuǎn)找不到最短路。其實(shí)如果一個(gè)圖中帶有“負(fù)權(quán)回路”那么這個(gè)圖則沒有最短路。
代碼實(shí)現(xiàn):
#include
int main()
{
int e[10][10],k,i,j,n,m,t1,t2,t3;
int inf=99999999; //用inf(infinity的縮寫)存儲一個(gè)我們認(rèn)為的正無窮值
//讀入n和m,n表示頂點(diǎn)個(gè)數(shù),m表示邊的條數(shù)
scanf("%d %d",&n,&m);
//初始化
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(i==j) e[i][j]=0;
else e[i][j]=inf;
//讀入邊
for(i=1;i<=m;i++)
{
scanf("%d %d %d",&t1,&t2,&t3);
e[t1][t2]=t3;
}
//Floyd-Warshall算法核心語句
for(k=1;k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(e[i][j]>e[i][k]+e[k][j] )
e[i][j]=e[i][k]+e[k][j];
//輸出最終的結(jié)果
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
printf("%10d",e[i][j]);
}
printf("\n");
}
return 0;
}
總結(jié)
關(guān)于BellmanFord和SPFA再說兩句
SPFA只是BellmanFord的一種優(yōu)化,其復(fù)雜度是O(kE),SPFA的提出者認(rèn)為k很小,可以看作是常數(shù),但事實(shí)上這一說法十分不嚴(yán)謹(jǐn)(原論文的“證明”竟然是靠編程驗(yàn)證,甚至沒有說明編程驗(yàn)證使用的數(shù)據(jù)是如何生成的),如其他答案所說的,在一些數(shù)據(jù)中,這個(gè)k可能會很大。而Dijkstra算法在使用斐波那契堆優(yōu)化的情況下復(fù)雜度是O(E+VlogV)。SPFA,或者說BellmanFord及其各種優(yōu)化(姜碧野的國家集訓(xùn)隊(duì)論文就提到了一種棧的優(yōu)化)的優(yōu)勢更主要體現(xiàn)在能夠處理負(fù)權(quán)和判斷負(fù)環(huán)吧(BellmanFord可以找到負(fù)環(huán),但SPFA只能判斷負(fù)環(huán)是否存在)。
補(bǔ)充算法
還有一些最短路算法的優(yōu)化或者引申方法,感興趣可以谷歌一下:Johnson算法
Bi-Direction BFS算法
…
參考
關(guān)注我
我目前是一名后端開發(fā)工程師。技術(shù)領(lǐng)域主要關(guān)注后端開發(fā),數(shù)據(jù)安全,爬蟲,5G物聯(lián)網(wǎng)等方向。
微信:yangzd1102
個(gè)人博客:
原創(chuàng)博客主要內(nèi)容Java知識點(diǎn)復(fù)習(xí)全手冊
Leetcode算法題解析
劍指offer算法題解析
SpringCloud菜鳥入門實(shí)戰(zhàn)系列
SpringBoot菜鳥入門實(shí)戰(zhàn)系列
Python爬蟲相關(guān)技術(shù)文章
后端開發(fā)相關(guān)技術(shù)文章
個(gè)人公眾號:Rude3Knife
如果文章對你有幫助,不妨收藏起來并轉(zhuǎn)發(fā)給您的朋友們~
總結(jié)
以上是生活随笔為你收集整理的沃舍尔算法_[数据结构拾遗]图的最短路径算法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 访问云服务器储存的mp4_服务器如何存储
- 下一篇: jquery检索name_jquery怎