【淺談】 單元最短路徑涉及演算法 & 在路由選擇中的應用(只有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,才疏學淺,多有疏漏,隨緣更新============