寫最短路我猶豫了好久,由於最短路它涵蓋的內容不少(四個基礎算法),並且在基礎算法上還有許多不一樣的優化,甚至存邊都有幾種方式,就顯得特別複雜html
最短路就是求在一個圖中一個點到指定點的最短路程(其實光看名字就猜的出來)。算法
幾個概念:數組
1.出發的點叫源點 2.一個點到另外一個點的路程(也能夠理解爲鏈接這兩個點的線段的長度)叫權 3.圖分兩種:有向圖和無向圖。有向圖就像單行道,只能去不能回。而無向圖就像雙行道,能夠去也能夠回。
還有一些例如鬆弛操做這些的概念會在下面進行解釋優化
對於一個圖,咱們須要把邊存儲下來。而存儲有兩種方式。spa
用一個二維數組來存儲。假如用數組k來存儲邊,則.net
k[i][j]
表示有一條從i到j的邊,權值爲code
k[i][j]
前向星比鄰接表省空間。
前向星分兩種,正常前向星(這名字是我取的,反正就是那個意思)和鏈式前向星。htm
思路是用3個數組,(這裏假設爲a,b,c)(可能有點難理解)
當blog
a[m]=i b[m]=j c[m]=n(其中m爲常數)
就是表示從i到j有一條邊,權值爲n。
可是假如要排序的話,開三個數組很麻煩,因此我建議用結構體。而結構體的排序方法我在講最小生成樹之kruskarl的時候講過,這裏就再也不贅述。排序
這個是一種鏈式的存邊方法。假如要細講的話,可能又要講一篇文章了,下文也不會使用這個(主要是沒有正常前向星好理解,並且也比較複雜)。
感興趣的話能夠去看這篇博客
https://blog.csdn.net/acdreamers/article/details/16902023/
以上講的都是存有向邊的方式。存無向邊其實區別不是很大。舉個例子相信一下就懂了。假如你要存一條從i到j的無向邊,你能夠這麼寫
k[i][j]=n; k[j][i]=n;
就是把終點和起點換一下位置,把終點當起點,把起點當終點。
這個算法有一大特色,那就是寫起來極其簡單(核心代碼只有5行),可是運行效率極低(o(n^3)),就讓它顯得很毒瘤,並且數據基本都會卡這種算法。
假如用二維數組
k[i][j]
來存儲從i到j的最段路。
初始化:先把k數組所有賦一個很大很大的值(方便後面比較)
floyd算法以下
for(int k=1;k<=n;k++)//k枚舉途經的節點 for(int i=1;i<=n;i++)//i枚舉起點 for(int j=1;j<=n;j++)//j枚舉終點 if(d[i][j]!=inf&&d[i][k]!=inf)//假如i到k沒有邊,那麼假設不成立,同理,假如i到j沒有邊,這個假設也不成立 d[i][j]=min(d[i][k]+d[k][j],d[i][j]);//拿現有的從i到j的最段路和i途經k再到j的總路程作比較。(這就是爲何要把d[i][j]的初始值賦爲一個很大的數)
能夠看一下上面的註釋。。
floyd就是靠三重循環來枚舉起點、途經的點和終點。而後作鬆弛操做。(鬆弛操做就是比大小那一步,有點像三角形的三邊關係)拿現有的從i到j的最段路和i途經k再到j的總路程作比較。也算是一個dp。
這個算法沒啥優化(也沒啥可優化的)。能夠求單源最段路(有向圖中的最段路),也能夠求無向圖的最段路。不能對付負權
這裏順便引入兩個新概念:負權&環
負權:一條權值爲負的路(於現實生活中不存在)。
環:有些時候在有向圖中會同時有從i到j和從j到i的兩條路。他們就組成了一個環。
負權環就是一個很恐怖的東西了,你能夠靠負權環在環裏面不停的刷,這樣你的最短路。。。就成了無限的更短路。。。
這是一個很經典的單源最短路的算法。
這個算法的描述我以爲在一本叫算法圖解的書中描述的比較詳細,在這裏就附上書的照片,以爲講得很通俗易懂,應該理解沒有什麼問題。(多圖預警)
總之就是重複那四步,就能夠找出最優解。
算法效率比floyd不知道高到哪裏去了。。
不能對付負權
這是一個能夠對付負權的算法
算法:
給你一個由v個點,e條邊的圖,和原點s 數組Distant[i]記錄從源點s到頂點i的路徑長度,初始化 數組Distant[n]爲, Distant[s]爲0; 如下操做循環執行至多n-1次,n爲頂點數: 對於每一條邊e(u, v),若是Distant[u] + w(u, v) < Distant[v],則另Distant[v] = Distant[u]+w(u, v)。w(u, v)爲邊e(u,v)的權值; 若上述操做沒有對Distant進行更新,說明最短路徑已經查找完畢,或者部分點不可達,跳出循環。不然執行下次循環; 爲了檢測圖中是否存在負環路,即權值之和小於0的環路。對於每一條邊e(u, v),若是存在Distant[u] + w(u, v) < Distant[v]的邊,則圖中存在負環路,便是說改圖沒法求出單源最短路徑。不然數組Distant[n]中記錄的就是源點s到各頂點的最短路徑長度。 可知,Bellman-Ford算法尋找單源最短路徑的時間複雜度爲O(V*E). Bellman-Ford算法能夠大體分爲三個部分 第一,初始化全部點。每個點保存一個值,表示從原點到達這個點的距離,將原點的值設爲0,其它的點的值設爲無窮大(表示不可達)。 第二,進行循環,循環下標爲從1到n-1(n等於圖中點的個數)。在循環內部,遍歷全部的邊,進行鬆弛計算。 第三,遍歷途中全部的邊(edge(u,v)),判斷是否存在這樣狀況: d(v) > d (u) + w(u,v) 則返回false,表示途中存在從源點可達的權爲負的迴路。
鬆弛操做以前講過,再看一遍複習一下
鬆弛計算以前,點B的值是8,可是點A的值加上邊上的權重2,獲得5,比點B的值(8)小,因此,點B的值減少爲5。這個過程的意義是,找到了一條通向B點更短的路線,且該路線是先通過點A,而後經過權重爲2的邊,到達點B。
固然,若是出現如下狀況
則不會修改點B的值,由於3+4>6。
spfa留着下次再講(和優化一塊兒)。
會了Bellman-Ford算法和狄傑斯特拉算法就已經能夠作一些最段路的題了,能夠練習一下。
重點理解:鬆弛操做(這玩意真的很重要,基本上最段路的精髓就是它了)
tks!
(參考文獻:https://www.cnblogs.com/tanky... , 算法圖解)