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
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 如此逆推不難得到 最短路徑記錄值 。
- #include "iostream"
- #include "vector"
- #include "stack"
- #include "fstream"
- usingnamespace std;
- std::vector<vector<int> > weight;
- std::vector<vector<int> > path;
- int vertexnum;
- int edgenum;
- constint intmax = 10000;
- void initialvector(){
- weight.resize(vertexnum);//路徑權重陣列
- path.resize(vertexnum);//儲存最短路徑陣列,記錄前繼
- for(int i = 0;i < vertexnum;i++){//建立陣列
- weight[i].resize(vertexnum,intmax);
- path[i].resize(vertexnum,-1);
- }
- }
- void getData(){//獲取資料
- ifstream in("data");
- in>>vertexnum>>edgenum;
- initialvector();
- int from,to;
- double w;
- while(in>>from>>to>>w){
- weight[from][to] = w;
- path[from][to] = from;//to的前繼是from
- weight[from][from] = 0;//自身到自身的權重為0
- path[from][from] = from;
- weight[to][to] = 0;
- path[to][to] = to;
- }
- }
- void floyd(){
- for(int k = 0;k < vertexnum;k++)
- for(int i= 0;i < vertexnum;i++)
- for(int j = 0;j < vertexnum;j++){
- 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])){//前面一部分是防止加法溢位
- weight[i][j] = weight[i][k] + weight[k][j];
- path[i][j] = path[k][j];
- }
- }
- }
- void displaypath(int source,int dest){
- stack<int> shortpath;
- int temp = dest;
- while(temp != source){
- shortpath.push(temp);
- temp = path[source][temp];
- }
- shortpath.push(source);
- cout<<"short distance:"<<weight[source][dest]<<endl<<"path:";
- while(!shortpath.empty()){
- cout<<shortpath.top()<<" ";
- shortpath.pop();
- }
- }
- int main(int argc, charconst *argv[])
- {
- getData();
- for(int i = 0;i < vertexnum;i++){
- for(int j = 0;j < vertexnum;j++){
- cout<<weight[i][j]<<"\t";
- }
- cout<<endl;
- }
- floyd();
- displaypath(2,1);
- return 0;
- }
資料: