<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

body, table{font-family: 微軟雅黑; font-size: 13.5pt}
table{border-collapse: collapse; border: solid gray; border-width: 2px 0 2px 0;}
th{border: 1px solid gray; padding: 4px; background-color: #DDD;}
td{border: 1px solid gray; padding: 4px;}
tr:nth-child(2n){background-color: #f8f8f8;}

迪傑克斯拉(Dijkstra)演算法:
  按路徑長度遞增的次序產生最短路徑的演算法。演算法解決了從某個源點到其餘各頂點的最短路徑問題。求兩個頂點之間的最短路徑,整個過程都是一步步求起始頂點到相鄰頂點的最短路徑,直到到達終點。過程中都是基於已經求出的最短路徑的基礎之上。

V0→V1    1
V0→V1→V2    4
V0→V1→V2→V4    5
V0→V1→V2→V4→V3    7
V0→V1→V2→V4→V3→V6    10
V0→V1→V2→V4→V3→V6→V7    12
V0→V1→V2→V4→V3→V6→V7→V8    16

在求最短路徑的過程中,V0到圖中所有路徑的最短距離都計算過了一遍,只要儲存中間結果就可以在求V0→V8的最短路徑的同時得到V0到圖中任意點的距離


isInShortestPath陣列標記對應下標頂點是不是已經計算在最短路徑內了

pathPre陣列標記對應下標頂點在最短路徑的前驅結點是誰,這個結點和weight陣列對應,如下表演算法開始
weight[3]=∞,這個距離是(0,3)得到的,所以pathPre[3]=0

weight陣列標記當前最短路徑到對應下標頂點的最短路徑,∞就是到不了了

//演算法開始,選中V0頂點開始遍歷找最短路徑

下標 0 1 2 3 4 5 6 7 8
isInShortestPath 1 0 0 0 0 0 0 0 0

下標 0 1 2 3 4 5 6 7 8
pathPre 0 0 0 0 0 0 0 0 0

下標 0 1 2 3 4 5 6 7 8
weight 0 1 5


//第一次遍歷找到(0,1),加入頂點1,1可以到達未加入最短路徑的頂點2,3,4; 更新陣列

下標 0 1 2 3 4 5 6 7 8
isInShortestPath 1 1 0 0 0 0 0 0 0

下標 0 1 2 3 4 5 6 7 8
pathPre 0 0 1 1 1 0 0 0 0

//V1->V2 = 4 < 5,更新 依次類推更新weight[3] weight[4]

下標 0 1 2 3 4 5 6 7 8
weight 0 1 5 4 8 6



//下一次遍歷就會選中V2,(1,2),加入頂點2

下標 0 1 2 3 4 5 6 7 8
isInShortestPath 1 1 1 0 0 0 0 0 0

下標 0 1 2 3 4 5 6 7 8
pathPre 0 0 1 1 2 2 0 0 0

weight[2]+(2,4)=5

 下標 0 1 2 3 4 5 6 7 8
weight 0 1 4 8 6 5 11
//加入weight最小的,且沒有加入到最短路徑中的頂點4(2,4)

下標 0 1 2 3 4 5 6 7 8
isInShortestPath 1 1 1 0 1 0 0 0 0

小標 0 1 2 3 4 5 6 7 8
pathPre 0 0 1 4 2 4 4 4 0

weight[4] = 5,能到達3,5,6,7

下標 0 1 2 3 4 5 6 7 8
weight 0 1 4 7 5 11 8 11 14


//沒加入的3,5,6,7,8中選一條最小權值的邊,選擇頂點3,(4,3)

下標 0 1 2 3 4 5 6 7 8
isInShortestPath 1 1 1 1 1 0 0 0 0

下標 0 1 2 3 4 5 6 7 8
pathPre 0 0 1 4 2 4 3 4 0

weight[3] = 7,3只能到沒到過的6,7+(3,6)=10,weight[6]=11,更新

下標 0 1 2 3 4 5 6 7 8
weight 0 1 4 7 5 8 11 10 14

//5,6,7,8中選一個weight最小的5

下標 0 1 2 3 4 5 6 7 8
isInShortestPath 1 1 1 1 1 1 0 0 0

下標 0 1 2 3 4 5 6 7 8
pathPre 0 0 1 4 2 4 3 5 0

weight[5]=8,5能到為加入最短路徑的7 ; weight[5]+(5,7)=13<weight[7],更新

下標 0 1 2 3 4 5 6 7 8
weight 0 1 4 7 5 8 10 14 13

//沒加入的6,7,8中選一個最小的6

下標 0 1 2 3 4 5 6 7 8
isInShortestPath 1 1 1 1 1 1 1 0 0

下標 0 1 2 3 4 5 6 7 8
pathPre 0 0 1 4 2 4 3 6 6

weight[6]=10,(6,7)=2,(6,8)=7;  

下標 0 1 2 3 4 5 6 7 8
weight 0 1 4 7 5 8 10 13 12 17
//沒加入的7,8中選一個最小的7, weight[7]=12

下標 0 1 2 3 4 5 6 7 8
isInShortestPath 1 1 1 1 1 1 1 1 0

下標 0 1 2 3 4 5 6 7 8
pathPre 0 0 1 4 2 4 3 6 7

weight[7]=12,(7,8)=4

下標 0 1 2 3 4 5 6 7 8
weight 0 1 4 7 5 8 10 12 17 16
//加入最後一個結點8

下標 0 1 2 3 4 5 6 7 8
isInShortestPath 1 1 1 1 1 1 1 1 1
在整個圖的遍歷搜尋中,從V0頂點到每一個其他頂點的最短距離都在這個過程中儲存在weight陣列中。
要找到兩個點之間的最短路徑,只要通過pathPre陣列找前驅就好了

/*  Dijksta.cpp /
#include"Dijkstra.h"
namespace meihao
{
        void ShortestPath_Dijkstra(const meihao::Graph& g,int pathPre,weight_vaule_type* weight,int vertexCnt)
        {
                if(nullptr==pathPre||nullptr==weight||0==vertexCnt)
                        return ;
                //定義一個數組標記每個結點是否已經在最短路徑中
                int* isInShortestPath = new intvertexCnt;  //初始為0,表示都還沒有在最短路徑之中
                //預設從圖中第0個結點開始,初始化weight,表示0到其他結點的最短路徑
                for(int idx=0;idx!=vertexCnt;++idx)
                {
                        weight[idx] = g.getGraphEdgeWeight(0,idx);   //weight[idx]存放的就是0到idx的權值
                        pathPre[idx] = 0;  //weight最開始用0到對應下標的距離來初始化,所以pathPre只能全部是0了
                }
                //計算V0到其他結點的最短路徑
                isInShortestPath[0] = 1;
                for(int idx=1;idx!=vertexCnt;++idx)  //V0已經在路徑中,只要從下一個頂點開始
                {
                        int min = max_weight_value;
                        int nextShortestPathVertex = 0;  //下一個可以加到最短路徑上的頂點
                        //開始遍歷weight陣列找到一條從V0頂點到對應陣列小標頂點的最有最小權值的邊,並記錄邊的另一端頂點
                        for(int iidx=0;iidx!=vertexCnt;++iidx)
                        {
                                if(0==isInShortestPath[iidx]&&
                                        weight[iidx]<min)
                                {
                                        nextShortestPathVertex = iidx;
                                        min = weight[iidx];
                                }
                        }
                        //找到了個可以加入到最短路徑的頂點
                        isInShortestPath[nextShortestPathVertex] = 1;  //修改nextShortestPathVertex為1
                        //得到了一個點,通過這個點可以到一些其他頂點,這是後可能路徑又會有變化
                        //加入nextShortestPathVertex,更新weight陣列
                        for(int iiidx=0;iiidx!=vertexCnt;++iiidx)
                        {
                                /if(0==isInShortestPath[iiidx]&&
                                        (weight[nextShortestPathVertex]+g.getGraphEdgeWeight(nextShortestPathVertex,iiidx))<weight[iiidx])  /  
                                //這裡錯誤的原因是有的weigth是weight_vaule_type能表示的最大值,再加就溢位了
                                if(0==isInShortestPath[iiidx] &&
                                         g.getGraphEdgeWeight(nextShortestPathVertex,iiidx) !=max_weight_value &&     //多一個條件,防止溢位,也就是(nextShortestPathVertex,iiidx)之間有邊的頂點
                                         (weight[nextShortestPathVertex]+g.getGraphEdgeWeight(nextShortestPathVertex,iiidx) ) < weight[iiidx] )
                                //如果新加入最短路徑的點到其他沒有標記到最短路徑中的點idx的權值小於之前某點到idx的權值,就可以更新這個權值
                                {
                                        weight[iiidx] = weight[nextShortestPathVertex]+g.getGraphEdgeWeight(nextShortestPathVertex,iiidx);
                                        pathPre[iiidx] = nextShortestPathVertex;
                                }
                        }
                }
        }
        void printShortestPath(int vi,int vj,int* pathPre,int vertexCnt)
        {
                if(nullptr==pathPre||0==vertexCnt||vi<0||vj<0||vi>vj||vi>=vertexCnt||vj>=vertexCnt)
                        return ;
                if(vi==vj)
                {
                        cout<<vi<<" ";
                        return ;
                }
                printShortestPath(vi,pathPre[vj],pathPre,vertexCnt);
                cout<<vj<<" ";
        }
};

/* Dijkstra.h /
#ifndef DIJKSTRA_H
#define DIJKSTRA_H
#include"Graph.h"
namespace meihao
{
        void ShortestPath_Dijkstra(const meihao::Graph& g,int pathPre,weight_vaule_type* weight,int vertexCnt);  //引數pathPre陣列是用來存放計算得到到圖中某一點的最短路徑前驅
        //pathPre[2] = 1,表示V2到V1的最短路徑中,V2前面一個點是V1, ...V1->V2...
        //weight是存放指定的一個開始遍歷的到對應陣列小標的點的權值。eg:圖中起始的點V0,weight[2] = N,表示V0到V2的最短路徑權值和為N
        //定義一個函式輸出指定兩點之間的最短路徑和權值和
        void printShortestPath(int vi,int vj,int* pathPre,int vertexCnt);
};
#endif


/* testmain.cpp /
#include"Graph.h"
#include<iostream>
#include"Dijkstra.h"
using namespace std;
int main()
{
        meihao::Graph g("data.txt");
        int vertexCnt = g.getGraphVertexNumber();
        int pathPre = new intvertexCnt;
        weight_vaule_type* weight = new weight_vaule_typevertexCnt;
        meihao::ShortestPath_Dijkstra(g,pathPre,weight,vertexCnt);
        for(int idx=0;idx!=vertexCnt;++idx)
        {
                meihao::printShortestPath(0,idx,pathPre,vertexCnt);
                cout<<"路徑權值:"<<weight[idx];
                cout<<endl;
        }
        cout<<endl;
        cout<<"圖的起始頂點0到終點8的最短路徑:";
        meihao::printShortestPath(0,8,pathPre,vertexCnt);
        cout<<"  路徑權值:"<<weight[8]<<endl;
        delete []pathPre;
        delete []weight;
        system("pause");
}



/* data.txt /

9
0 1 2 3 4 5 6 7 8
0 1 5 -1 -1 -1 -1 -1 -1
1 0 3 7 5 -1 -1 -1 -1
5 3 0 -1 1 7 -1 -1 -1
-1 7 -1 0 2 -1 3 -1 -1
-1 5 1 2 0 3 6 9 -1
-1 -1 7 -1 3 0 -1 5 -1
-1 -1 -1 3 6 -1 0 2 7
-1 -1 -1 -1 9 5 2 0 4
-1 -1 -1 -1 -1 -1 7 4 0 

弗洛伊德(Floyd)演算法:
  Floyd演算法又稱為插點法,是一種利用動態規劃的思想尋找給定的加權圖中多源點之間最短路徑的演算法。

圖中D-1矩陣是存放的圖的臨界矩陣,對應的P-1矩陣是存放對應頂點的前驅頂點矩陣。
eg:p[V1][V0]=0,(V1,V0),只能是0;  p[V1][V1]=1,(V1,V1); p[V1][V2]=2,(V1,V2)  這是


初始,現以V0為中間頂點更新兩個矩陣:
(V1,V0)不變,(V1,V1)不變,(V1,V2)->V1,V0,V2 = 3,之前為5,更新
所以更新兩個矩陣,p[V1][V2]=0,表示(V1,V2)最短路徑中V2前一個結點是V0,對應的D[V1][V2]=3。這時候的矩陣為P0,D0。

//以V0為中間頂點,所有點(Vi,Vj)經過V0求路徑,確定是否要更新
//和初始矩陣一樣,沒有變化
//以V1為中間頂點,所有點(Vi,Vj)經過V1求路徑,確定是否要更新
//之後的每次更新都是在前面得到的中間結果之上,最終到V8,這時候所有點之間的最短路徑就都得到了
p[V8][V8]=8
//最終結果,D矩陣中(Vi,Vj)就是Vi,Vj之間的最短路徑
//P矩陣中,P[V0][V8]=1表示V0->V8要先經過V0->V1->...->V8,P[V1][V8]=2 ... P[V8][V8]=8

/ Floyd.h */

#ifndef FLOYD_H
#define FLOYD_H
#include"Graph.h"
#include<iostream>
namespace meihao
{
        void ShortestPath_Floyd(const meihao::Graph& g,weight_vaule_type weight,int pathPre);
        //weight相當於D陣列,pathPre相當於P陣列
        void printPath(int vi,int vj,int vertexCnt,int** pathPre);
        //列印vi到vj的最短路徑
};
#endif


/* testmain.cpp */

#include"Graph.h"
#include<iostream>
#include"Floyd.h"
#include<iomanip>
using namespace std;
int main()
{
        cout<<"test Floyd:"<<endl;
        meihao::Graph g("data.txt");
        int vertexCnt = g.getGraphVertexNumber();
        int** pathPre = new int*vertexCnt;
        for(int idx=0;idx!=vertexCnt;++idx)
                pathPre[idx] = new intvertexCnt;
        weight_vaule_type** weight = new weight_vaule_typevertexCnt;
        for(int idx=0;idx!=vertexCnt;++idx)
                weight[idx] = new weight_vaule_type[vertexCnt];
        meihao::ShortestPath_Floyd(g,weight,pathPre);
        cout<<"print weight matrix:"<<endl;
        for(int idx=0;idx!=vertexCnt;++idx)
        {
                for(int iidx=0;iidx!=vertexCnt;++iidx)
                {
                        cout<<setw(3)<<weight[idx][iidx]<<" ";
                }
                cout<<endl;
        }
        cout<<endl;
        cout<<"print pathPre matrix:"<<endl;
        for(int idx=0;idx!=vertexCnt;++idx)
        {
                for(int iidx=0;iidx!=vertexCnt;++iidx)
                {
                        cout<<setw(3)<<pathPre[idx][iidx]<<" ";
                }
                cout<<endl;
        }
        cout<<endl;
        for(int idx=0;idx!=vertexCnt;++idx)
        {
                meihao::printPath(0,idx,vertexCnt,pathPre);
                cout<<"路徑權值: "<<weight[0][idx]<<endl;
        }
        //釋放動態記憶體
        for(int idx=0;idx!=vertexCnt;++idx)
        {
                delete []pathPre[idx];
                pathPre[idx] = nullptr;
                delete []weight[idx];
                weight[idx] = nullptr;
        }
        delete []pathPre;
        pathPre = nullptr;
        delete []weight;
        weight = nullptr;
        system("pause");
}

/ Floyd.cpp */

#include"Floyd.h"
#include<iostream>
namespace meihao
{
        void ShortestPath_Floyd(const meihao::Graph& g,weight_vaule_type weight,int pathPre)
        {
                if(nullptr==weight||nullptr==pathPre)
                        return ;
                //讀取圖中陣列初始化weight和pathPre
                int vertexCnt = g.getGraphVertexNumber();
                for(int idx=0;idx!=vertexCnt;++idx)
                {
                        for(int iidx=0;iidx!=vertexCnt;++iidx)
                        {
                                weight[idx][iidx] = g.getGraphEdgeWeight(idx,iidx);
                                pathPre[idx][iidx] = iidx;
                        }
                }
                //開始依次選擇中間結點,更新weight和pathPre陣列
                //中間結點為0時和初始化的矩陣一樣
                for(int idx=1;idx!=vertexCnt;++idx)
                {
                        for(int v=0;v!=vertexCnt;++v)
                        {
                                for(int w=0;w!=vertexCnt;++w)
                                {
                                        if( (max_weight_value!=weight[v][idx] && max_weight_value!=weight[idx][w]) &&  //經過中間結點idx,(v,idx)和(idx,w)都不能是最大值,不然下邊的相加判斷會溢位
                                                //max_weight_value是權值weight_vaule_type所能表示的最大值了,再加就溢位
                                                 weight[v][w]>(weight[v][idx] + weight[idx][w]) )
                                        {//更新
                                                weight[v][w] = weight[v][idx] + weight[idx][w];
                                                pathPre[v][w] = pathPre[v][idx];  //pathPre[v][w] = idx,也就是(v,w)要先到達idx,但是(v,idx)不是一步就到達的,也要先經過pathPre[v][idx]
                                                //pathPre[v][w] = idx;  //不能寫這個,要在前面得到的中間結果的基礎之上
                                        }
                                }
                        }
                }
        }
        void printPath(int vi,int vj,int vertexCnt,int** pathPre)
        {
                if(vi<0||vj<0||vertexCnt<=0||nullptr==pathPre)
                        return ;
                if(vi==vj)
                {
                        cout<<vi<<" ";
                        return ;
                }       
                int max = vi>=vj?vi:vj;
                int min = vi<vj?vi:vj;
                cout<<min<<" ";  //從最小的起始點開始
                while(min!=max)
                {
                        cout<<pathPre[min][max]<<" ";  //第一個要經過的點
                        min = pathPre[min][max];  //更新起始頂點
                }
        }
};