1. 程式人生 > >floyd演算法求解最短路徑

floyd演算法求解最短路徑

轉自  http://blog.csdn.net/zhongkeli/article/details/8832946

這個演算法主要要弄懂三個迴圈的順序關係。

弗洛伊德(Floyd)演算法過程:
1、用D[v][w]記錄每一對頂點的最短距離。
2、依次掃描每一個點,並以其為基點再遍歷所有每一對頂點D[][]的值,看看是否可用過該基點讓這對頂點間的距離更小。

演算法理解:

最短距離有三種情況:
1、兩點的直達距離最短。(如下圖<v,x>)
2、兩點間只通過一箇中間點而距離最短。(圖<v,u>)
3、兩點間用通過兩各以上的頂點而距離最短。(圖<v,w>)

對於第一種情況:在初始化的時候就已經找出來了且以後也不會更改到。
對於第二種情況:弗洛伊德演算法的基本操作就是對於每一對頂點,遍歷所有其它頂點,看看可否通過這一個頂點讓這對頂點距離更短,也就是遍歷了圖中所有的三角形(演算法中對同一個三角形掃描了九次,原則上只用掃描三次即可,但要加入判斷,效率更低)。
對於第三種情況:如下圖的五邊形,可先找一點(比如x,使<v,u>=2),就變成了四邊形問題,再找一點(比如y,使<u,w>=2),可變成三角形問題了(v,u,w),也就變成第二種情況了,由此對於n邊形也可以一步步轉化成四邊形三角形問題。(這裡面不用擔心哪個點要先找哪個點要後找,因為找了任一個點都可以使其變成(n-1)邊形的問題)。

結合程式碼 並參照上圖所示 我們來模擬執行下 這樣才能加深理解:
第一關鍵步驟:當k執行到x,i=v,j=u時,計算出v到u的最短路徑要通過x,此時v、u聯通了。
第二關鍵步驟:當k執行到u,i=v,j=y,此時計算出v到y的最短路徑的最短路徑為v到u,再到y(此時v到u的最短路徑上一步我們已經計算過來,直接利用上步結果)。
第三關鍵步驟:當k執行到y時,i=v,j=w,此時計算出最短路徑為v到y(此時v到y的最短路徑長在第二步我們已經計算出來了),再從y到w。

依次掃描每一點(k),並以該點作為中介點,計算出通過k點的其他任意兩點(i,j)的最短距離,這就是floyd演算法的精髓!同時也解釋了為什麼k點這個中介點要放在最外層迴圈的原因.

對於這個演算法,網上有一個證明的版本:

 floyd演算法是一個經典的動態規劃演算法。用通俗的語言來描述的話,首先我們的目標是尋找從點i到點j的最短路徑。從動態規劃的角度看問題,我們需要為這個目標重新做一個詮釋(這個詮釋正是動態規劃最富創造力的精華所在),floyd演算法加入了這個概念  Ak(i,j):表示從i到j中途不經過索引比k大的點的最短路徑

    這個限制的重要之處在於,它將最短路徑的概念做了限制,使得該限制有機會滿足迭代關係,這個迭代關係就在於研究:假設Ak(i,j)已知,是否可以藉此推匯出Ak-1(i,j)。

    假設我現在要得到Ak(i,j),而此時Ak(i,j)已知,那麼我可以分兩種情況來看待問題:1. Ak

(i,j)沿途經過點k;2. Ak(i,j)不經過點k。如果經過點k,那麼很顯然,Ak(i,j) = Ak-1(i,k) + Ak-1(k,j),為什麼是Ak-1呢?因為對(i,k)和(k,j),由於k本身就是源點(或者說終點),加上我們求的是Ak(i,j),所以滿足不經過比k大的點的條件限制,且已經不會經過點k,故得出了Ak-1這個值。那麼遇到第二種情況,Ak(i,j)不經過點k時,由於沒有經過點k,所以根據概念,可以得出Ak(i,j)=Ak-1(i,j)。現在,我們確信有且只有這兩種情況---不是經過點k,就是不經過點k,沒有第三種情況了,條件很完整,那麼是選擇哪一個呢?很簡單,求的是最短路徑,當然是哪個最短,求取哪個,故得出式子:

    Ak(i,j) = min( Ak-1(i,j), Ak-1(i,k) + Ak-1(k,j) )

    現在已經得出了Ak(i,j) = Ak-1(i,k) + Ak-1(k,j)這個遞迴式,但顯然該遞迴還沒有一個出口,也就是說,必須定義一個初始狀態,事實上,這個初始狀態取決於索引k是從0開始還是從1開始,上面的程式碼是C寫的,是以0為開始索引,但一般描述演算法似乎習慣用1做開始索引,如果是以1為開始索引,那麼初始狀態值應設定為A0了,A0(i,j)的含義不難理解,即從i到j的邊的距離。也就是說,A0(i,j) = cost(i,j) 。由於存在i到j不存在邊的情況,也就是說,在這種情況下,cost(i,j)無限大,故A0(i,j) = oo(當i到j無邊時)

    到這裡,已經列出了求取Ak(i,j)的整個演算法了,但是,最終的目標是求dist(i,j),即i到j的最短路徑,如何把Ak(i,j)轉換為dist(i,j)?這個其實很簡單,當k=n(n表示索引的個數)的時候,即是說,An(i,j)=dist(i,j)。那是因為當k已經最大時,已經不存在索引比k大的點了,那這時候的An(i,j)其實就已經是i到j的最短路徑了。

    從floyd演算法中不難看出,要設計一個好的動態規劃演算法,首先需要研究是否能把目標進行重新詮釋(這一步是最關鍵最富創造力的一步),轉化為一個可以被分解的子目標,如果可以轉化,就要想辦法尋找數學等式使目標收斂為子目標,如果這一步可以實現了,還需要研究該遞迴收斂式的出口,即初始狀態是否明確(這一步往往已經簡單了)。


如果需要儲存最短路徑,需要藉助path陣列:

其中我們用 path 陣列記錄 經過路徑 其實 path 的定義如下  path[i][j]  = k 表示 是最短路徑 i-……j  和 j 的直接 前驅  為 k 即是: i-->...............-->k ->j

舉例子:

如  1-> 5->4   4->3->6  此時 path[1][6] = 0 ; 0表示 1->6 不通  當我們 引入 節點 k = 4 此時有 1->5->4->3->6 顯然有 paht[1][6] = 3 = paht[4][6] = paht[k][6]

於是有 path[i][j] = path[k][j] 

對於 1->5 相鄰邊 我們可以在初始化時候 有 paht[1][5] = 1;

如是對於 最短路徑 1->5->4->3->6 有 paht[1][6] = 3; paht[1][3]= 4; paht[1][4] = 5; paht[1][5] =1 如此逆推不難得到 最短路徑記錄值 。

  1. #include "iostream"
  2. #include "vector"
  3. #include "stack"
  4. #include "fstream"
  5. usingnamespace std;  
  6. std::vector<vector<int> > weight;  
  7. std::vector<vector<int> > path;  
  8. int vertexnum;  
  9. int edgenum;  
  10. constint intmax = 10000;  
  11. void initialvector(){  
  12.     weight.resize(vertexnum);//路徑權重陣列
  13.     path.resize(vertexnum);//儲存最短路徑陣列,記錄前繼
  14.     for(int i = 0;i < vertexnum;i++){//建立陣列
  15.         weight[i].resize(vertexnum,intmax);  
  16.         path[i].resize(vertexnum,-1);  
  17.     }  
  18. }  
  19. void getData(){//獲取資料
  20.     ifstream in("data");  
  21.     in>>vertexnum>>edgenum;  
  22.     initialvector();  
  23.     int from,to;  
  24.     double w;  
  25.     while(in>>from>>to>>w){  
  26.         weight[from][to] = w;  
  27.         path[from][to] = from;//to的前繼是from
  28.         weight[from][from] = 0;//自身到自身的權重為0
  29.         path[from][from] = from;  
  30.         weight[to][to] = 0;  
  31.         path[to][to] = to;  
  32.     }  
  33. }  
  34. void floyd(){  
  35.     for(int k = 0;k < vertexnum;k++)  
  36.         for(int i= 0;i < vertexnum;i++)  
  37.             for(int j = 0;j < vertexnum;j++){  
  38.                 if((weight[i][k] > 0 && weight[k][j] && weight[i][k] < intmax && weight[k][j] < intmax) && (weight[i][k] + weight[k][j] < weight[i][j])){//前面一部分是防止加法溢位
  39.                     weight[i][j] = weight[i][k] + weight[k][j];  
  40.                     path[i][j] = path[k][j];  
  41.                 }  
  42.             }  
  43. }  
  44. void displaypath(int source,int dest){  
  45.     stack<int> shortpath;  
  46.     int temp = dest;  
  47.     while(temp != source){  
  48.         shortpath.push(temp);  
  49.         temp = path[source][temp];  
  50.     }  
  51.     shortpath.push(source);  
  52.     cout<<"short distance:"<<weight[source][dest]<<endl<<"path:";  
  53.     while(!shortpath.empty()){  
  54.         cout<<shortpath.top()<<" ";  
  55.         shortpath.pop();  
  56.     }  
  57. }  
  58. int main(int argc, charconst *argv[])  
  59. {  
  60.     getData();    
  61.     for(int i = 0;i < vertexnum;i++){    
  62.         for(int j = 0;j < vertexnum;j++){    
  63.             cout<<weight[i][j]<<"\t";    
  64.         }    
  65.         cout<<endl;    
  66.     }  
  67.     floyd();  
  68.     displaypath(2,1);  
  69.     return 0;  
  70. }  

資料: