1. 程式人生 > 其它 >【淺談】 單元最短路徑涉及演算法 & 在路由選擇中的應用(只有Dijkstra,剩餘3/4有待更新)

【淺談】 單元最短路徑涉及演算法 & 在路由選擇中的應用(只有Dijkstra,剩餘3/4有待更新)

  • 寫在前面:因為能力和記憶有限,為方便以後查閱,特寫看上去 “不太正經” 的隨筆。隨筆有 “三” 隨:隨便寫寫;隨時看看;隨意理解。

注:本篇文章涉及資料結構(圖)離散數學演算法計算機網路相關知識,但都只為加深印象淺層剖析,讀者可根據自身情況選擇閱讀,若求甚解,勿往下讀,以免浪費時間

不知道讀者聽說過的是哪個版本:單源最短路徑,最短道路樹,兩結點間的最短路徑;總的來說,沒什麼區別,注意與最小生成樹 (也稱最小支撐樹或最小生成/支撐子圖) 區分即可。

幾種演算法:

1.迪傑斯特拉 / 迪科斯徹(Dijkstra)演算法:

步驟:

1)選擇一個頂點作為起(源)點,用陣列 dis[ ] 記錄源點到其餘各點的最短距離(distance),增加一個已訪問(visited)點集 vis[ ],並將起點加入 vis[ ] 集合中;

2)由當前訪問結點開始,更新由該點到其餘未訪問點的距離,若訪問點與某點之間的道路不存在,則距離記為 +∞ ,只有當新距離原來距離時,才能改變相應距離的值dis[ ]

設當前訪問結點為 a,對於未訪問結點 b 的距離更新,應判斷 dis[a] + ab間的道路長度( 記為Edge(a,b) ) < dis[b] 是否成立,成立說明經過a再到b的道路更短,故可將dis[b]的值更新為 dis[a]+Edge(a,b);

3)在未訪問點中選擇 dis[] 值最小的一個結點,作為下一個訪問點並加入vis[ ] 集合中;(可以看到每次選擇的點的 dis[ ] 值已經固定)

4)回到步驟2,當所有結點已訪問 (即所有點加入vis[ ]陣列) 時,跳出迴圈,演算法結束,此時 dis[ ] 陣列儲存的值即為源點到其它所有點的最短距離。

舉個栗子:

我們以結點a作為起始點,運用上述方法:

步驟 訪問結點 vis點集 dis點集(加粗表示已訪問) 準備加入的邊
0(初始化) / /

dis[a]=0,dis[b]=∞

dis[c]=∞,dis[d]=∞

dis[e]=∞,dis[f]=∞

/
1 a a

dis[a]=0,dis[b]=5(new)

dis[c]=3(new),dis[d]=∞

dis[e]=∞,dis[f]=∞

ac
2 c a,c

dis[a]=0,dis[b]=4(new)

dis[c]=3,dis[d]=5

dis[e]=8(new),dis[f]=∞

cb
3 b a,c,b

dis[a]=0,dis[b]=4

dis[c]=3,dis[d]=5

dis[e]=8,dis[f]=∞

cd
4 d a,c,b,d

dis[a]=0,dis[b]=4

dis[c]=3,dis[d]=5

dis[e]=8,dis[f]=9(new)

ce
5 e a,c,b,d,e

dis[a]=0,dis[b]=4

dis[c]=3,dis[d]=5

dis[e]=8,dis[f]=9

df
6 f a,c,b,d,e,f(結束end)

不難發現每步選擇訪問的點是訪問結點中 dis[ ] 值最小的,

對於加入邊的繪製,卻依然要看圖分析,這很明顯不利於機器輸出,這裡提供一種解決方案:

加入一個新的陣列集合 path[ ] ,記錄相應結點的前驅即可,這樣對於每個訪問點,呼叫一次path可找到相應加入的邊;此外,不斷呼叫path找到前驅,直到path值為起點,就可以描繪出兩點間的最短路徑。

對於 path[ ] 相關值的更新也是比較巧妙的,表格上dis值中標有new(紅色突出)的結點,說明經由當前訪問結點後再到這些點的距離更小,進行資料更新,那麼其前驅必定是當前訪問結點,隨之更新即可;

簡而言之,初始將所有結點path值設為自身(∀m∈Graph,path[m]=m),設 k 為當前訪問結點,對於未曾訪問的結點 n,當 dis[k]+Edge(k,n)<dis[n] 成立 (為true) 時,dis[n]=dis[k]+Edge(k,n),path[n]=k;

例中最短道路樹為 (見下圖紅色標註):

該方法繪製出的是無向樹的結構,是連通且無環的圖,屬於的一種特殊形態。

=============相信讀到這裡的你對dijkstra演算法核心部分的程式碼實現已經有了基本框架============

演算法分析:

Dijkstra演算法每一步選擇dis值最小的點作為區域性最優解,不考慮子問題的解(貪心與動態規劃的區別在此),屬於貪心演算法。

因此對於(所有)貪心演算法,我們可以採用以下格式分析,框架部分就不列舉了,直接拿該例項品嚐:

①貪心策略:對於帶權圖,規定Edge(i,j)表示 i 到 j 的距離,即邊權,且所有權重非負,dis[ ]表示只經過vis(已訪問)點集,各點相對於起點的最短路徑長度,short[ ]表示完整圖上各點相對起點的最短距離。設 s 為起始點。

②命題:當演算法進行至第 k 步時,對於vis中每個結點 i,有dis[ i ] = short[ i ]。

③對命題的證明:採用步數歸納法。

當k=1時,vis中只有點s,dis[s]=short[s]=0;

設演算法進行至第 k 步時,命題成立,即dis[k]=short[k],驗證k+1步:

如圖所示,vis點集為圈內所示,虛線為目前vis陣列選擇點組成的最短道路樹。假設用該演算法選擇的是v點,對應最後一次出vis的點是u,可知dis[v]在vis外為最小,但是k+1步演算法正確性不可知,不能確定選擇v點最優,此類證明中大致都套用反證的思想,不妨假設存在一條新的到v點的道路(綠色箭頭標出),使得dis[v]取得最小值,那麼此時k+1步選擇的不是v,我們假設為y點,對應最後一次出vis點也會隨之變化,也不妨假設為x點,由前述條件顯然dis[y]>dis[v],那麼dis[y]+Edge(y,v)>dis[v]同樣成立,此時不能保證dis[v]最小,故第k+1步選擇的必定是v點,證畢。

④時間複雜度分析:初始化dis陣列花費O(n),對於當前訪問結點與所有未訪問點間的比較(更新dis值的那部分),vis每次加入一個點,共比較(n-1)+(n-2)+...+2+1=O(n2),綜合兩部分,時間複雜度為O(n2)。

=============2022-02-07,21:22:57,才疏學淺,多有疏漏,隨緣更新============