最小生成树、最短路径树
一、最小生成樹與最短路徑樹的區別
最小生成樹能夠保證整個拓撲圖的所有路徑之和最小,但不能保證任意兩點之間是最短路徑。
應用如網絡部線,把所有的電腦(服務器?)都連起來用的網線(光纖?)最少,即用最小的代價讓全村人都能上網
最短路徑是從一點出發,到達目的地的路徑最小。
應用如導航,兩個地方怎么走距離最短。可以存在到不了的情況。
二、最小生成樹
在圖論中,無向圖 G 的生成樹(英語:Spanning Tree)是具有 G 的全部頂點,但邊數最少的連通子圖。[1]
一個圖的生成樹可能有多個。
帶權圖的生成樹中,總權重最小的稱為最小生成樹。
它在實際中有什么應用呢?比如說有N個城市需要建立互聯的通信網路,如何使得需要鋪設的通信電纜的總長度最小呢?這就需要用到最小生成樹的思想了。
求取最小生成樹的算法:
2.1 Prim算法原理:
1)以某一個點開始,尋找當前該點可以訪問的所有的邊;
2)在已經尋找的邊中發現最小邊,這個邊必須有一個點還沒有訪問過,將還沒有訪問的點加入我們的集合,記錄添加的邊;
3)尋找當前集合可以訪問的所有邊,重復2的過程,直到沒有新的點可以加入;
4)此時由所有邊構成的樹即為最小生成樹。
參考鏈接:https://wiki.jikexueyuan.com/project/step-by-step-learning-algorithm/prim-algorithm1.html
2.2 Kruskal算法原理:
現在我們假設一個圖有m個節點,n條邊。首先,我們需要把m個節點看成m個獨立的生成樹,并且把n條邊按照從小到大的數據進行排列。在n條邊中,我們依次取出其中的每一條邊,如果發現邊的兩個節點分別位于兩棵樹上,那么把兩棵樹合并成為一顆樹;如果樹的兩個節點位于同一棵樹上,那么忽略這條邊,繼續運行。等到所有的邊都遍歷結束之后,如果所有的生成樹可以合并成一條生成樹,那么它就是我們需要尋找的最小生成樹,反之則沒有最小生成樹。
參考鏈接:https://wiki.jikexueyuan.com/project/step-by-step-learning-algorithm/kruskal-algorithm.html
2.3 兩算法對比
總的來說,Prim算法是以點為對象,挑選與點相連的最短邊來構成最小生成樹。而Kruskal算法是以邊為對象,不斷地加入新的不構成環路的最短邊來構成最小生成樹。
三、最短路徑樹
最短路徑就是從一個指定的頂點出發,計算從該頂點出發到其他所有頂點的最短路徑。
最短路徑樹SPT(Short Path Tree)是網絡的源點到所有結點的最短路徑構成的樹。
常見的最短路徑算法有三種:dijkstra,floyd,Bellman-Ford和SPFA。
Dijkstra:適用于權值為非負的圖的單源最短路徑,用斐波那契堆的復雜度O(E+VlgV)
Floyd:每對節點之間的最短路徑,時間復雜度O(V^3)
BellmanFord:適用于權值有負值的圖的單源最短路徑,并且能夠檢測負圈,復雜度O(VE)
SPFA:適用于權值有負值,且沒有負圈的圖的單源最短路徑,論文中的復雜度O(kE),k為每個節點進入Queue的次數,且k一般<=2,但此處的復雜度證明是有問題的,其實SPFA的最壞情況應該是O(VE).
如果說邊權中有負數怎么定義呢?如果有負邊,就要分兩種情況了。
第一種情況:如果從某點出發,可以到達一個權值和為負數的環,那么這個點到其它點的最短距離就是負無窮了,很明顯,如果有負環,且從某點可以到達這個負環,那么我可以無限得走這個負環,每走一次,距離就小一些,這種情況下,我們可以定義這個點到達其它點的最短距離為負無窮。
第二種情況:如果說不存在一個這樣的負環,那么就和沒有負權邊一樣了,但是還不是完全一樣,接下來我們介紹的四種算法中,有的是可以處理負權邊不能處理負環的,有的是可以處理負環的,有的是既不能處理負權邊也不能處理負環的。
3.1 Dijkstra
- 不能適用于負權圖
- 只適用于單源最短路問題
如果權重是負數,那就直接pass這個算法,在這里原因不表。
單源最短路徑就是指只有一個出發點,到其它點的最短路徑問題。
以上圖G4為例,來對迪杰斯特拉進行算法演示(以第4個頂點D為起點)。以下B節點中23應為13。
即
算法流程:
- S1. 設置dis數組,dis[i]表示起點start到i的距離。
- S2. 從點集V中彈出一個dis值最小且未以該點為起點進行松弛操作的點。
- S3. 從該點松弛與其領接的各個點更新dis數組,返回S2,循環進行。
- 通過優先隊列的操作可以優化S2,之后詳細說明。
舉個例子。這個例子也是洛谷的單源最短路徑的模板題,請求出1到各點的最短路?
很顯然你用肉眼看,1到本身是0,1到2、3、4的最短路分別為2,4,3。那dijkstra的操作流程是什么呢?
首先我們先開一個dis數組,讓數組的值足夠大,dis[i]=0x7fffffff,從1開始出發,令dis[1]=0,發現與1相連的有三個點234,那我們一個個進行松弛操作,比較if (dis[1]+w[i]<dis[i]),w表示各邊的權重,如果小于,那就讓其覆蓋本身的dis值,即dis[i]=dis[1]+w[i],這一波更新完后,234的值分別為2,5,4。
然后,我們需要讓234全部入隊,并選取dis值最小的數即2繼續進行松弛操作,發現連接的是3和4,繼續更新,這波結束,234的值分別為2,4,3。
接著,是上一輪dis值次小的點4,進行操作,但是4沒有出的邊,所以不進行操作。
最后就是剩下的一個3了,3和4還有一條權邊,但是4最小的dis值依舊是3。
下面我們發現算法到這就截止了,為什么呢,因為S2的一句話,未進行松弛的點,早在第一輪234就已經全部進入過隊列并且已經彈出過了,所以之后他們也不會再進入隊列,我們可以設置一個bool類型的vis[i]數組代表第i個點是否被訪問過了,如果訪問過了就結束此循環,或者直接不push進入隊列。
這就是整個dijkstra的算法。
總結:
Dijkstra算法是一種優秀的經典的最短路算法,在優先隊列的優化下,它的時間復雜度也是可接受的,它在計算機網絡等應用中廣泛存在,然而它也有它的局限性,它的局限性甚至比Floyd算法都要大,Floyd算法允許有負權邊,不允許有負權環,Dijkstra算法連負權邊都不允許,因為Dijkstra的正確性基于以下假設:當前未找到最短路的點中,距離源點最短的那個點,無法繼續被松弛,這時候他肯定是已經松弛到最短狀態了。但是這個假設在有負權邊的時候就不成立了,舉一個經典例子:一個具有三個點三條邊的圖,0->1距離為2,0->2距離為3,2->1距離為-2,根據Dijkstra算法,我們第一次肯定會將節點1加入到已經松弛好的點集中,但是這時候是不對的,2并不是0->1最短的路徑,而通過點2松弛過的路徑:0->2->1才是最短的路徑,因為負權邊的存在,我們認為的已經被松弛好的點,在未來還有被松弛的可能。這個就是Dijkstra的局限性,然而這并不影響它的偉大,因為在實際應用中,沒有負權的圖是更為常見的。但是我們仍然需要可以處理負權,甚至負環的算法,這時候Bellman-Ford算法和SPFA算法就派上用場了。
3.2 Floyd
弗洛伊德算法是一種基于動態規劃的多源最短路算法。是解決任意兩點間的最短路徑的一種算法,可以正確處理有向圖或有向圖或負權(但不可存在負權回路)的最短路徑問題,同時也被用于計算有向圖的傳遞閉包。
算法流程:
通過Floyd計算圖G=(V,E)中各個頂點的最短路徑時,需要引入兩個矩陣,矩陣S中的元素a[i][j]表示頂點i(第i個頂點)到頂點j(第j個頂點)的距離。矩陣P中的元素b[i][j],表示頂點i到頂點j經過了b[i][j]記錄的值所表示的頂點。
算法描述:
a. 從任意一條單邊路徑開始。所有兩點之間的距離是邊的權,如果兩點之間沒有邊相連,則權為無窮大。
b. 對于每一對頂點 u 和 v,看看是否存在一個頂點 w 使得從 u 到 w 再到 v 比己知的路徑更短。如果是更新它。
假設圖G中頂點個數為N,則需要對矩陣D和矩陣P進行N次更新。初始時,矩陣D中頂點a[i][j]的距離為頂點i到頂點j的權值;如果i和j不相鄰,則a[i][j]=∞,矩陣P的值為頂點b[i][j]的j的值。 接下來開始,對矩陣D進行N次更新。第1次更新時,如果”a[i][j]的距離” > “a[i][0]+a[0][j]”(a[i][0]+a[0][j]表示”i與j之間經過第1個頂點的距離”),則更新a[i][j]為”a[i][0]+a[0][j]”,更新b[i][j]=b[i][0]。 同理,第k次更新時,如果”a[i][j]的距離” > “a[i][k-1]+a[k-1][j]”,則更新a[i][j]為”a[i][k-1]+a[k-1][j]”,b[i][j]=b[i][k-1]。更新N次之后,操作完成!
舉個例子
第一步,我們先初始化兩個矩陣,得到下圖兩個矩陣:
第二步,以v1為中介,更新兩個矩陣:
發現,a[1][0]+a[0][6] < a[1][6] 和a[6][0]+a[0][1] < a[6][1],所以我們只需要矩陣D和矩陣P,結果如下:
通過矩陣P,我發現v2–v7的最短路徑是:v2–v1–v7
第三步:以v2作為中介,來更新我們的兩個矩陣,使用同樣的原理,掃描整個矩陣,得到如下圖的結果:
OK,到這里我們也就應該明白Floyd算法是如何工作的了,他每次都會選擇一個中介點,然后,遍歷整個矩陣,查找需要更新的值,下面還剩下五步,就不繼續演示下去了,理解了方法,我們就可以寫代碼了。
總結
Floyd算法只能在不存在負權環的情況下使用,因為其并不能判斷負權環,上面也說過,如果有負權環,那么最短路將無意義,因為我們可以不斷走負權環,這樣最短路徑值便成為了負無窮。
但是其可以處理帶負權邊但是無負權環的情況。
以上就是求多源最短路的Floyd算法,基于動態規劃,十分優雅。
但是其復雜度確實是不敢恭維,因為要維護一個路徑矩陣,因此空間復雜度達到了O(n^ 2 ) ,時間復雜度達到了O(n^ 3),只有在數據規模很小的時候,才適合使用這個算法。
3.3 Bellman-Ford
Bellman-Ford是最常規下的單源最短路問題,對邊的情況沒有要求,不僅可以處理負權邊,還能處理負環。可謂是來者不拒。
算法描述:
對于一個圖G(v,e)(v代表點集,e代表邊集),執行|v|-1次邊集的松弛操作,所謂松弛操作,就是對于每個邊e1(v,w),將源點到w的距離更新為:原來源點到w的距離 和 源點到v的距離加上v到w的距離 中較小的那個。v-1輪松弛操作之后,判斷是否有源點能到達的負環,判斷的方法就是,再執行一次邊集的松弛操作,如果這一輪松弛操作,有松弛成功的邊,那么就說明圖中有負環。算法復雜度為O(ne),e為圖的邊數
優化(SPFA)
Bellman-Ford算法雖然可以處理負環,但是時間復雜度為O(ne),在圖為稠密圖的時候,是不可接受的。復雜度太高。我們分析一下能不能進行優化,Bellman-Ford算法的正確性保證依賴于路徑松弛性質,我們只要能夠保證最短路徑中的邊的松弛順序即可,Bellman-Ford算法屬于一種暴力的算法,即,每次將所有的邊都松弛一遍,這樣肯定能保證順序,但是仔細分析不難發現,源點s到達其他的點的最短路徑中的第一條邊,必定是源點s與s的鄰接點相連的邊,因此,第一次松弛,我們只需要將這些邊松弛一下即可。第二條邊必定是第一次松弛的時候的鄰接點與這些鄰接點的鄰接點相連的邊(這句話是SPFA算法的關鍵,請讀者自行體會),因此我們可以這樣進行優化:設置一個隊列,初始的時候將源點s放入,然后s出隊,松弛s與其鄰接點相連的邊,將松弛成功的點放入隊列中,然后再次取出隊列中的點,松弛該點與該點的鄰接點相連的邊,如果松弛成功,看這個鄰接點是否在隊列中,沒有則進入,有則不管,這里要說明一下,如果發現某點u的鄰接點v已經在隊列中,那么將點v再次放到隊列中是沒有意義的。因為即時你不放入隊列中,點v的鄰接點相連的邊也會被松弛,只有松弛成功的邊相連的鄰接點,且這個點沒有在隊列中,這時候稍后對其進行松弛才有意義。因為該點已經更新,需要重新松弛。
總結:
Bellman-Ford算法和SPFA算法,都是基于松弛操作,以及路徑松弛性質的,不同之處在于,前者是一種很暴力的算法,為了保證順序,每次把所有的邊都松弛一遍,復雜度很高,而后者SPFA,則是在Bellman-Ford算法的基礎上進行了剪枝優化,只松弛那些可能是下一條路徑的邊,但是關于SPFA算法的復雜度,現在還沒有個定論,不過達成的共識是:算法的時間復雜度為O(me),其中m為入隊的平均次數,其發明者,西安交大的段凡丁大神的論文中,m是一個常數(這個結論是錯的),我們“姑且”認為該算法的時間復雜度為O(e)【滑稽】,的確該算法的效率是很高的,博主在oi中經常使用這個算法,除非題目數據特意卡SPFA。或者的確是稠密圖,一般情況下不會超時。關于其判斷負環,我們可以認為,在某個節點入隊次數達到n的時候,就可以說明遇到了負環。SPFA有兩個優化策略:SLF:Small Label First 策略,設要加入隊列的節點是j,隊首元素為i,若d[j]<dist[i],則將j插入隊首,否則插入隊尾; LLL:Large Label Last 策略,設隊首元素為i,隊列中所有d值的平均值為x,若d[i]>x則將i插入到隊尾,查找下一元素,直到找到某一i使d[i]<=x,則將i出隊進行松弛操作。其實,實際應用中,SPFA的時間復雜度是很不穩定的,因此我們能用Dijkstra+優先隊列,就用Dijkstra+優先隊列為好。
https://zhuanlan.zhihu.com/p/34922624
https://zhuanlan.zhihu.com/p/150434472
https://zhuanlan.zhihu.com/p/33162490
https://zhuanlan.zhihu.com/p/40338107
總結
以上是生活随笔為你收集整理的最小生成树、最短路径树的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux人社区(linux人)
- 下一篇: 无线发射功率与增益