最短路徑
最短路徑問題是圖的一個經典問題,常用的求最短路徑的方法有
(迪傑斯特拉)Dijkstra算法,(弗洛伊德)Floyd算法。
Dijkstra算法用於求單源點最短路徑問題,復雜度為O(n2),而Floyd算法用於求對每一對頂點之間的最短路問題(采用枚舉法,枚舉所有可能),復雜度為O(n3)。
一、Dijkstra算法:
迪傑斯特拉提出了一個按路徑長度遞增的次序產生最短路的算法,其基本思想是:設置一個集合S存放已經找到最短路徑的頂點,S的初始狀態只包含源點v,對vi屬於V-S,假設從源點v到vi的有向邊為最短路徑,以后每求得每一條最短路徑v,…vk,就將vk加入到集合S中,並將路徑v,…vk,vi與原來的假設相比較,取路徑長度較小者為當前最短路徑。
核心代碼如下:
for(int i=1; i<=n-1; i++) //更新dis數組
{
min1=inf;
for(int j=1; j<=n; j++)
{
if(book[j]==0&&dis[j]<min1)//每次找最短的邊
{
min1=dis[j];
u=j;
}
}
book[u]=1;
for(int v=1; v<=n; v++)
{
if(e[u][v]<inf)
if(dis[v]>dis[u]+e[u][v])//如果從起始點到j的距離大於起始點到u的距離加上u到j的距離就更新,專業術語松弛操作
dis[v]=dis[u]+e[u][v];
}
}
Dijkstra算法是應用貪心的一個典例。但是也正是因為其貪心算法的思想,其不適用於帶負權邊的圖,例:

我們利用Dijkstra算法試驗一下上面的圖(從A->B):
1.先把A點標記,不需要訪問本身
2.首先找到距A最近的且直接相連的點(也就是兩點間沒有中轉點)C,把C標記
3.找出C點的出點A,,B,A被標記了不管,此時A到B的距離為3,大於A到C的距離加上C到B的距離0,所以更新A到B的距離為0
4.更新后A到C的距離仍然為2,A到B的距離為0,A,C都被標記,只有B未被標記,進行下一步
5.找到距A最近的且未被標記的點B,標記B
6.找出B的出點A,C,然而A,C兩點都被標記,不能松弛
7.程序結束,結果為A到C的距離為2而不是1,說明普通dijkstra算法並不能處理帶負權邊的無向圖
Dijkstra算法求最短路的算法是由貪心得來的,也就是說長路徑的松弛正確的前提是用來松弛它的短路徑是最短的,也就是說在之后是不會變的,這在非負權值的情況下是對的,然而遇到負權值便錯了,因為當加入了負權值邊后便可能使之前的短邊變得更短。
對Dijkstra算法的改進:
Bellman-Ford(BF算法)(適用於帶負權值的邊):
Bellman-Ford算法同樣是求起始點到各個頂點的最短路,但與dijkstra不同的是,dijkstra每次找到最近的點進行松弛操作,而這個bellman則是只要路程更短就松弛。也是因為這樣才能用來解決負權值問題。
核心代碼:
for(int k=1;k<=n-1;k++)//進行n-1次松弛(最糟糕的情況,因為可能不到n-1次就已經找出來了)
for(int i=1;i<=m;i++)//枚舉每一條邊
if(dis[v[i]]>dis[u[i]]+w[i])//嘗試松弛每一條邊
dis[v[i]]=dis[u[i]]+w[i];
SPFA算法(SPFA是一種用隊列優化的B-F算法):
1.初始時,只有把起點放入隊列中。
2.遍歷與起點相連的邊,如果可以松弛就更新距離dis[],然后判斷如果這個點沒有在隊列中就入隊標記。
3.出隊隊首,取消標記,循環2-3步,直至隊為空。
4.所有能更新的點都更新完畢,dis[]數組中的距離就是,起點到其他點的最短距離。
SPFA如何判斷成環:
在儲存邊時,記錄下每個點的入度,每個點入隊的時候記錄一次,如果入隊的次數大於這個點的入度,說明從某一條路進入了兩次,即該點處成環。
二、Floyd算法:
Floyd算法用於求每一對頂點之間的最短路徑問題,給定帶權有向圖G=(V,E),對任意頂點vi和vj(i!=j),求頂點vi到vj之間的最短路徑。
解決這個問題的方法(借助Dijkstra算法)是:每次以一個頂點為源點,調用Dijkstra算法n次,便可得到每一對頂點之間的最短路徑,時間復雜度為O(n3)。
Floyd算法:
一般來說我們從i到j有兩種走法:
1.直接從i到j
2.通過中間點k中轉,i->k->j
所以我們就遍歷所有情況,如果通過某個中轉點距離小於直接到達的距離,就更新這兩點間的距離。(Floyd算法)
核心代碼如下:
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
{
if(map1[i][j]>map1[i][k]+map1[k][j])
map1[i][j]=map1[i][k]+map1[k][j];
}
因此Floyd算法能解決負邊(負權)但不能解決負環。
上述幾種求最短路徑的方法僅限於求不帶環的圖中:
1.Dijkstra算法不能解決帶負權邊的圖
2.B-F算法做了優化允許圖中出現負權邊並解決該問題
3.SPFA算法利用隊列對B-F算法進一步優化,允許圖中可以出現環但是不能解決帶環的圖(僅能判別出現但是不能解決)
4.Floyd算法能解決負權邊但不能解決負環
我們可以假設某一條環上路徑長度為負(單循環時),那么我們就可以進行多循環直至無限循環無限縮短路徑長度,那么問題便無解了。因此任何算法都不能求帶環的問題。
