迪克斯特拉算法初探——圖解算法
迪克斯特拉算法的大致思想是這樣:求出起始頂點到各個后繼頂點的最短通路,直到所求頂點為止。
由於直接從抽象的代碼分析比較復雜(筆者很菜 零零碎碎花了好幾天才搞懂),我們可從實際的例子
來感受該算法的思想,這樣也符合由一般到抽象的認知過程(突然哲學)
首先來看一個直觀的例子吧(看圖說話)
 
標號是核心(L(v)實際上就是點v到a的某條通路的長度(為什么不說是最短路長度呢 ? 這個我后面會提到))
1.首先初始化標記 即:把a標號為0;其余點標號為無窮;
2.再定義一個空集S。

step 1:
 
看看圖中發生了什么變化?
1.我們將標號最小的a(L(a)=0)納入集合S中(圖中用圈圈出); 即 S={a}
2.更新所有不屬於S的頂點的標記 具體就是對不在集合S中的點v,其標號為min{ L(a)+w(a,v), L(v) } L(b)=4; L(c)=2;
顯然 我們只能更新那些與a相鄰的元素的標記。 # a-v之間如果不連通則w(a,v)=∞
step 2:
 
這下 上次的標號就派上用場了呢:
我們把不在S中的頂點標號最小的頂點納入到集合S中,即 c 得到 S={a,c};
對所有不屬於S的頂點v,更新它們的標號 具體做法 L(v)= min{ L(c)+w(c,v), L(v) } L( b)=3;L(d)=10;L(e)=12;
注意:已經更新過的元素也可能會再次改變~b就是如此
標號再次改變說明存在更短的路徑
step 3:
 
這次L(b)為最小標號 所以把b添加到集合S中 S={a,c,b} # 至此 b加入到集合S中,所求的標號L(b)才是最短路的長度!
更新標號(以新加入S的頂點b為基准): L(v)= min{ L(b)+w(b,v), L(v) } L(d)=8;
大家發現沒有,這個算法的簡化之處在於,尋找新的點不需要求S中所有點和不在S中所有點的標號,
也就是說,只需要更新所有與新加入點相鄰的點的標號,這一點需要慢慢體會,我就是卡在這兒出不去!
step 4:
 
                     S={a,c,b,d} ;                 L(e)=10   L(f)=14
step 5:
                S={a,c,b,d,e};               L(f)=13;
step 6 :
                      S={a,c,b,d,e};     L(f)=13       至此   該算法結束
下面上點理論知識應該就可以接受了吧:
迪克斯特拉算法如下進行:求出a到第一個頂點的最短通路的長度,從a到第二個頂點的最短通路的長度,依次類推,直到求除從a到z的最短通路的長度為止。還有一個便利之處是,很容易擴展這個算法,求出從a到不只是z的所有頂點的最短通路的長度。
這個算法依賴於一系列的迭代。通過在每次迭代中添加一個頂點來構造特殊頂點的集合。在每次迭代中完成以個標記過程。在這個標記過程中,用只包含特殊頂點集合中的頂點的從a到w的最短通路來標記w.添加到特殊頂點中的頂點是尚在集合之外的那些頂點中帶有最小標記的頂點。
首先用0標記a而用∞標記其余頂點。用記號L0(a)=0和L0(v)=∞來表示在沒有發生任何迭代之前的這些標記(下標0表示“第0次”迭代)。這些標記是從a到這些頂點的最短通路的長度,其中這些通路只包含頂點a.(因為不存在a到其他頂點的這種通路,所以∞是a與這樣的頂點之間的最短通路的長度。)
迪克斯特拉算法是通過形成特殊頂點的結合來進行的。設Sk表示在標記過程k次迭代之后的特殊頂點集。首先令S0=Ø。集合Sk通過把不屬於Sk-1的帶最小標記的頂點u添加到Sk-1形成的。
一旦把u添加到Sk中,就更新所有不屬於Sk的頂點的標記,使得頂點v在第k個階段的標記Lk(v)是只包含Sk中頂點(Sk+u)的從a到v的最短通路的長度。注意:在每一步中選擇添加到Sk中的頂點u都是一個最優選擇,使之成為貪婪算法(並且是最優解)。
設v是不屬於Sk中的頂點。更新v的標記,注意Lk(v)是只包含Sk中頂點的從a到v的最短通路的長度。
注意:只包含Sk中頂點的最短通路,要么是只包含Sk-1中頂點的從a到v的最短通路,要么是在第k-1個階段加上變{u,v}的從a到u的最短通路。 即 Lk(a,v)=min{Lk-1(a,v),Lk-1(a,u)+w(u,v)} w(u,v)為邊的長度(權)
下面給出算法的偽代碼:
procedure Dijkstra(G:所有權為正數的帶權連通簡單圖) {G所有頂點a=v0,v1,v2,……vn=z和權w(vi,vj), 若vi,vj無邊則權w(vi,vj)為∞} for i:=1 to n L(vi):=∞ L(a):=0 S:=Ø {現在初始化標記,使得a的標記為0 而所有其余標記為∞,S是空集} while z∉S u := a∉S的L(u)最小的一個頂點 S := S U {u} for 所有不屬於S的L(u)最小的一個頂點 if L(u)+w(u,v)<L(v) then L(v):=L(u)+w(u,v) {這樣就給S中添加帶最小標記的頂點,並且更新不在S中的頂點的標記 } return L(z)
我們現在回過頭來看這個算法,其實有兩條線:
一條線是集合,這個集合中每次新添加一個頂點元素(這個頂點其實就是在集合S外 距離a最短的頂點!)直到所求的頂點納入這個集合為止 (這個是算法停止的標志)
另一條線是標號,每次循環改變其余頂點的標號,標號就是該頂點到起始頂點的某通路的長度!不過,需要注意的是: 只有當該頂點包含到集合里面時,標號才代表最短通路的長度!
他們之間是怎么聯系的呢,不難發現 我們把標號作為尋找集合S外到a最近的頂點的量度,而集合S提供了計算標號的划分范圍!
實際上,正是這種標號的引入降低了計算的復雜度,使其從O(n^3)-->O(n^2)
至此 ,算法的思路也就明朗了!
具體實現的話后期更新給出,離散好迷~
參考書目:《離散數學及其應用》
