坐在马桶上看算法:Dijkstra最短路算法
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?【坐在馬桶上看算法】算法7:Dijkstra最短路算法
? ? ? ?上周我們介紹了神奇的只有五行的Floyd最短路算法,它可以方便的求得任意兩點(diǎn)的最短路徑,這稱為“多源最短路”。本周來(lái)來(lái)介紹指定一個(gè)點(diǎn)(源點(diǎn))到其余各個(gè)頂點(diǎn)的最短路徑,也叫做“單源最短路徑”。例如求下圖中的1號(hào)頂點(diǎn)到2、3、4、5、6號(hào)頂點(diǎn)的最短路徑。
? ? ? ?與Floyd-Warshall算法一樣這里仍然使用二維數(shù)組e來(lái)存儲(chǔ)頂點(diǎn)之間邊的關(guān)系,初始值如下。
? ? ? ?我們還需要用一個(gè)一維數(shù)組dis來(lái)存儲(chǔ)1號(hào)頂點(diǎn)到其余各個(gè)頂點(diǎn)的初始路程,如下。
? ? ? ?我們將此時(shí)dis數(shù)組中的值稱為最短路的“估計(jì)值”。
? ? ? ?既然是求1號(hào)頂點(diǎn)到其余各個(gè)頂點(diǎn)的最短路程,那就先找一個(gè)離1號(hào)頂點(diǎn)最近的頂點(diǎn)。通過(guò)數(shù)組dis可知當(dāng)前離1號(hào)頂點(diǎn)最近是2號(hào)頂點(diǎn)。當(dāng)選擇了2號(hào)頂點(diǎn)后,dis[2]的值就已經(jīng)從“估計(jì)值”變?yōu)榱恕按_定值”,即1號(hào)頂點(diǎn)到2號(hào)頂點(diǎn)的最短路程就是當(dāng)前dis[2]值。為什么呢?你想啊,目前離1號(hào)頂點(diǎn)最近的是2號(hào)頂點(diǎn),并且這個(gè)圖所有的邊都是正數(shù),那么肯定不可能通過(guò)第三個(gè)頂點(diǎn)中轉(zhuǎn),使得1號(hào)頂點(diǎn)到2號(hào)頂點(diǎn)的路程進(jìn)一步縮短了。因?yàn)?號(hào)頂點(diǎn)到其它頂點(diǎn)的路程肯定沒有1號(hào)到2號(hào)頂點(diǎn)短,對(duì)吧O(∩_∩)O~
? ? ? ?既然選了2號(hào)頂點(diǎn),接下來(lái)再來(lái)看2號(hào)頂點(diǎn)有哪些出邊呢。有2->3和2->4這兩條邊。先討論通過(guò)2->3這條邊能否讓1號(hào)頂點(diǎn)到3號(hào)頂點(diǎn)的路程變短。也就是說(shuō)現(xiàn)在來(lái)比較dis[3]和dis[2]+e[2][3]的大小。其中dis[3]表示1號(hào)頂點(diǎn)到3號(hào)頂點(diǎn)的路程。dis[2]+e[2][3]中dis[2]表示1號(hào)頂點(diǎn)到2號(hào)頂點(diǎn)的路程,e[2][3]表示2->3這條邊。所以dis[2]+e[2][3]就表示從1號(hào)頂點(diǎn)先到2號(hào)頂點(diǎn),再通過(guò)2->3這條邊,到達(dá)3號(hào)頂點(diǎn)的路程。
? ? ? ?我們發(fā)現(xiàn)dis[3]=12,dis[2]+e[2][3]=1+9=10,dis[3]>dis[2]+e[2][3],因此dis[3]要更新為10。這個(gè)過(guò)程有個(gè)專業(yè)術(shù)語(yǔ)叫做“松弛”。即1號(hào)頂點(diǎn)到3號(hào)頂點(diǎn)的路程即dis[3],通過(guò)2->3這條邊松弛成功。這便是Dijkstra算法的主要思想:通過(guò)“邊”來(lái)松弛1號(hào)頂點(diǎn)到其余各個(gè)頂點(diǎn)的路程。
? ? ? ?同理通過(guò)2->4(e[2][4]),可以將dis[4]的值從∞松弛為4(dis[4]初始為∞,dis[2]+e[2][4]=1+3=4,dis[4]>dis[2]+e[2][4],因此dis[4]要更新為4)。
? ? ? ?剛才我們對(duì)2號(hào)頂點(diǎn)所有的出邊進(jìn)行了松弛。松弛完畢之后dis數(shù)組為:
? ? ? ?接下來(lái),繼續(xù)在剩下的3、4、5和6號(hào)頂點(diǎn)中,選出離1號(hào)頂點(diǎn)最近的頂點(diǎn)。通過(guò)上面更新過(guò)dis數(shù)組,當(dāng)前離1號(hào)頂點(diǎn)最近是4號(hào)頂點(diǎn)。此時(shí),dis[4]的值已經(jīng)從“估計(jì)值”變?yōu)榱恕按_定值”。下面繼續(xù)對(duì)4號(hào)頂點(diǎn)的所有出邊(4->3,4->5和4->6)用剛才的方法進(jìn)行松弛。松弛完畢之后dis數(shù)組為:
? ? ? ?繼續(xù)在剩下的3、5和6號(hào)頂點(diǎn)中,選出離1號(hào)頂點(diǎn)最近的頂點(diǎn),這次選擇3號(hào)頂點(diǎn)。此時(shí),dis[3]的值已經(jīng)從“估計(jì)值”變?yōu)榱恕按_定值”。對(duì)3號(hào)頂點(diǎn)的所有出邊(3->5)進(jìn)行松弛。松弛完畢之后dis數(shù)組為:
? ? ? ?繼續(xù)在剩下的5和6號(hào)頂點(diǎn)中,選出離1號(hào)頂點(diǎn)最近的頂點(diǎn),這次選擇5號(hào)頂點(diǎn)。此時(shí),dis[5]的值已經(jīng)從“估計(jì)值”變?yōu)榱恕按_定值”。對(duì)5號(hào)頂點(diǎn)的所有出邊(5->4)進(jìn)行松弛。松弛完畢之后dis數(shù)組為:
? ? ? ?最后對(duì)6號(hào)頂點(diǎn)所有點(diǎn)出邊進(jìn)行松弛。因?yàn)檫@個(gè)例子中6號(hào)頂點(diǎn)沒有出邊,因此不用處理。到此,dis數(shù)組中所有的值都已經(jīng)從“估計(jì)值”變?yōu)榱恕按_定值”。
? ? ? ?最終dis數(shù)組如下,這便是1號(hào)頂點(diǎn)到其余各個(gè)頂點(diǎn)的最短路徑。
? ? ? ?OK,現(xiàn)在來(lái)總結(jié)一下剛才的算法。算法的基本思想是:每次找到離源點(diǎn)(上面例子的源點(diǎn)就是1號(hào)頂點(diǎn))最近的一個(gè)頂點(diǎn),然后以該頂點(diǎn)為中心進(jìn)行擴(kuò)展,最終得到源點(diǎn)到其余所有點(diǎn)的最短路徑。基本步驟如下:
?
-
將所有的頂點(diǎn)分為兩部分:已知最短路程的頂點(diǎn)集合P和未知最短路徑的頂點(diǎn)集合Q。最開始,已知最短路徑的頂點(diǎn)集合P中只有源點(diǎn)一個(gè)頂點(diǎn)。我們這里用一個(gè)book[ i ]數(shù)組來(lái)記錄哪些點(diǎn)在集合P中。例如對(duì)于某個(gè)頂點(diǎn)i,如果book[ i ]為1則表示這個(gè)頂點(diǎn)在集合P中,如果book[ i ]為0則表示這個(gè)頂點(diǎn)在集合Q中。
-
設(shè)置源點(diǎn)s到自己的最短路徑為0即dis=0。若存在源點(diǎn)有能直接到達(dá)的頂點(diǎn)i,則把dis[ i ]設(shè)為e[s][ i ]。同時(shí)把所有其它(源點(diǎn)不能直接到達(dá)的)頂點(diǎn)的最短路徑為設(shè)為∞。
-
在集合Q的所有頂點(diǎn)中選擇一個(gè)離源點(diǎn)s最近的頂點(diǎn)u(即dis[u]最小)加入到集合P。并考察所有以點(diǎn)u為起點(diǎn)的邊,對(duì)每一條邊進(jìn)行松弛操作。例如存在一條從u到v的邊,那么可以通過(guò)將邊u->v添加到尾部來(lái)拓展一條從s到v的路徑,這條路徑的長(zhǎng)度是dis[u]+e[u][v]。如果這個(gè)值比目前已知的dis[v]的值要小,我們可以用新值來(lái)替代當(dāng)前dis[v]中的值。
-
重復(fù)第3步,如果集合Q為空,算法結(jié)束。最終dis數(shù)組中的值就是源點(diǎn)到所有頂點(diǎn)的最短路徑。
?
? ? ? ?完整的Dijkstra算法代碼如下:
#include <stdio.h> int main() {int e[10][10],dis[10],book[10],i,j,n,m,t1,t2,t3,u,v,min;int inf=99999999; //用inf(infinity的縮寫)存儲(chǔ)一個(gè)我們認(rèn)為的正無(wú)窮值//讀入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;//讀入邊f(xié)or(i=1;i<=m;i++){scanf("%d %d %d",&t1,&t2,&t3);e[t1][t2]=t3;}//初始化dis數(shù)組,這里是1號(hào)頂點(diǎn)到其余各個(gè)頂點(diǎn)的初始路程for(i=1;i<=n;i++)dis[i]=e[1][i];//book數(shù)組初始化for(i=1;i<=n;i++)book[i]=0;book[1]=1;//Dijkstra算法核心語(yǔ)句for(i=1;i<=n-1;i++){//找到離1號(hào)頂點(diǎn)最近的頂點(diǎn)min=inf;for(j=1;j<=n;j++){if(book[j]==0 && dis[j]<min){min=dis[j];u=j;}}book[u]=1;for(v=1;v<=n;v++){if(e[u][v]<inf){if(dis[v]>dis[u]+e[u][v])dis[v]=dis[u]+e[u][v];}}}//輸出最終的結(jié)果for(i=1;i<=n;i++)printf("%d ",dis[i]);getchar();getchar();return 0; }
? ? ? ?可以輸入以下數(shù)據(jù)進(jìn)行驗(yàn)證。第一行兩個(gè)整數(shù)n ?m。n表示頂點(diǎn)個(gè)數(shù)(頂點(diǎn)編號(hào)為1~n),m表示邊的條數(shù)。接下來(lái)m行表示,每行有3個(gè)數(shù)x y z。表示頂點(diǎn)x到頂點(diǎn)y邊的權(quán)值為z。
? ? ? ?運(yùn)行結(jié)果是
0 1 8 4 13 17? ?通過(guò)上面的代碼我們可以看出,這個(gè)算法的時(shí)間復(fù)雜度是O(N2)。其中每次找到離1號(hào)頂點(diǎn)最近的頂點(diǎn)的時(shí)間復(fù)雜度是O(N),這里我們可以用“堆”(以后再說(shuō))來(lái)優(yōu)化,使得這一部分的時(shí)間復(fù)雜度降低到O(logN)。另外對(duì)于邊數(shù)M少于N2的稀疏圖來(lái)說(shuō)(我們把M遠(yuǎn)小于N2的圖稱為稀疏圖,而M相對(duì)較大的圖稱為稠密圖),我們可以用鄰接表(這是個(gè)神馬東西?不要著急,下周再仔細(xì)講解)來(lái)代替鄰接矩陣,使得整個(gè)時(shí)間復(fù)雜度優(yōu)化到O( (M+N)logN )。請(qǐng)注意!在最壞的情況下M就是N2,這樣的話MlogN要比N2還要大。但是大多數(shù)情況下并不會(huì)有那么多邊,因此(M+N)logN要比N2小很多。
歡迎轉(zhuǎn)載,碼字不容易啊,轉(zhuǎn)載麻煩注明出處
【啊哈!算法】系列7:Dijkstra最短路算法
http://ahalei.blog.51cto.com/4767671/1387799
?
?
總結(jié)
以上是生活随笔為你收集整理的坐在马桶上看算法:Dijkstra最短路算法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: vue.js语法和常用指令
- 下一篇: Jeecg 实现RESTful风格