最短路徑 - Dijkstra和Floyd

今天來看看圖的最短路徑。

最短路徑在許多方面都有廣泛的應用,最短路徑中的路徑一般不再是路徑上邊的數目,而是路徑邊上的權值之和。所以最短路徑都是用在帶權有向圖上面的。在最短路徑方面,有兩個著名的算法,Dijkstra算法和Floyd算法。


1.Dijkstra算法-從某個點到其餘各點的最短路徑

Dijkstra算法使用了貪心算法思想,主要特點是以起始點爲中心向外層層擴展,直到擴展到終點爲止。但是注意該算法要求圖中不存在負權邊。因爲單純講太過空虛,所以我直接用一個例子來講解。下面是一個帶權有向圖的示例。
這裏寫圖片描述

此時他的帶權鄰接矩陣爲(鄰接矩陣的定義在傳送門):

1055020301001060

接下來根據示例圖和鄰接矩陣來講解Dijkstra算法是如何求得最短路徑的。
這裏寫圖片描述
1. 一開始我們初始化已選擇點集S,我們假設源點爲v0 ,所以先把v0 加入S,此時S = {v0 }。
2. 我們首先遍歷除v0 之外的所有點,發現v0 無法直接到達v1v3 ,所以距離爲 。而其他三個點中,到v2 點的距離最近,此時我們選擇v2 加入S,此時S = {v0v2 }。
3. 接下來再一次遍歷除S內元素之外的所有點。我們可以知道,如果想知道v0vk 的最短距離,則最短距離有兩種情況:

  • v0vk 上的權值。
  • 或者是之間經歷了其他頂點,假設經歷了一個頂點vj ,則最短距離 = v0vj 的最短路徑 + vjvk 上的權值。而這個vj 就是我們加入到集合S裏面的頂點。

所以我們開始更新第一次的路徑長度結果,發現通過新加入的點(即v2 ),我們可以到達v3 ,此時路徑長度 = v0v2 的權值 + v2v3 權值 = 60。而其他點的路徑長度沒有被更新或者經歷了v2 後的路徑長度並不比更新前的小。最後我們找到路徑長度最短的點v4 ,加入S,此時S = {v0v2v4 }。

4.再次按照3中的形式進行遍歷S之外的點。 此時我們看到加入了v4 後,我們到達v3 的路徑長度被縮短到了50,這個50 = v0v4 的權值 + v4v3 權值,而v0v4 的權值已經被記錄過,所以可以直接使用,所以這個距離很容易就被求出。接下來v5 的長度更新和v3 一樣,就不在詳述了,最後找到路徑長度最短的點v3 ,加入S,此時S = {v0v2v4v3 }。
5. 再次按照3中的形式進行遍歷S之外的點。更新了v5 的路徑長,找到路徑長度最短的點v5 ,加入S,此時S ={v0v2v4v3v5 }。而此時我們發現,只剩下v1 ,但是從源點v0v1 的距離依然是 ,說明v0 沒有和v1 連通。此時我們將v1 加入S,這時集合S的長度 = 頂點數 = 6,算法結束。

接下來是主要的算法代碼,在這裏我們使用的圖存儲結構依然是鄰接矩陣。輸入的形式是以鄰接點對的形式,以0 0 0結束。在代碼裏,我們看到主程序就是一個嵌套循環,所以它的時間複雜度是O(n2 )。

#include<iostream>
#include<string.h>
#include<queue>
using namespace std;
#define MAX 100
#define INT_MAX 10000
#define max(x,y)(x>y?x:y)
typedef struct{
    int matrix[MAX][MAX]; //鄰接矩陣
    int num;// 最大的頂點數
}Graph;
int initNode=0;
int *shortPath= NULL ;//最短距離
int *S=NULL;
Graph example; //實例圖

// 初始化圖
void initGraph(){
    int i,j;
    for(i=0;i<MAX;i++){
        for(j=0;j<MAX;j++){
            example.matrix[i][j]=INT_MAX;
        }
    }
    example.num=0;
}
// 創建圖
void createPoint(){
    int i,j,val;
    initGraph();
    while(cin>>i>>j>>val){
        if(i==0 && j==0) break;
        example.matrix[i][j]=val;
        example.num = max(example.num,i); 
        //這裏偷了個懶,我這裏假設頂點之間序號是連續的,沒有點之間
        //序號跨很大的情況,如果需要的話,大家可以寫一個函數改一下
        //這裏。
        example.num = max(example.num,j);
    }
}

//輸出最後的最短路徑
void printResult(){
    cout<<"--------------------------"<<endl;
    for(int i=0;i<=example.num;i++){
        if(i!=initNode)cout<<initNode<<"->"<<i<<":"<<shortPath[i]<< " "<<endl;
    }
}

//dijkstra算法
void dijkstra(){
    int i,j;
    shortPath = new int[example.num+1];
    S = new int[example.num+1];
    int count = 0; //記錄S內元素個數
    int nowProP; //記錄現在處理的頂點
    int minPoint,minval;
    for(i=0;i<=example.num;i++){
        S[i]=-1;
        shortPath[i]=INT_MAX;
    }
    S[initNode] = 1;
    count++;
    nowProP = initNode;
    //兩層循環
    while(count<=example.num+1){
        minPoint  = -1,minval= INT_MAX+1;
        for(i=0;i<=example.num;i++){
          if(S[i]!=1){
            if(example.matrix[initNode][i]< shortPath[i]){
                shortPath[i] = example.matrix[initNode][i];
            }
            if(example.matrix[nowProP][i]+shortPath[nowProP] < shortPath[i]){
                shortPath[i] = example.matrix[nowProP][i]+shortPath[nowProP];
            }
            if(shortPath[i]<minval){minval=shortPath[i];minPoint=i;}
          }
        }
        S[minPoint] = 1;nowProP=minPoint;
        count++;
    }
}
int main(){
    initNode = 0; //這裏我們假設源點是v0,也可以改成其他的.
    createPoint();
    dijkstra();
    printResult();

    return 0;
}

最後結果是:

0 2 10
0 4 30
0 5 100
1 2 5
2 3 50
3 5 10
4 5 60
4 3 20
0 0 0
--------------------------
0->1:10000
0->2:10
0->3:50
0->4:30
0->5:60

2.Floyd算法-每對點之間的最短路徑

Floyd算法主要的是動態規劃的思想(下面的介紹來自於這篇博客)。
從任意節點i到任意節點j的最短路徑不外乎2種可能,1是直接從i到j,2是從i經過若干個節點k到j。所以,我們假設Dis(i,j)爲節點u到節點v的最短路徑的距離,對於每一個節點k,我們檢查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,證明從i到k再到j的路徑比i直接到j的路徑短,我們便設置Dis(i,j) = Dis(i,k) + Dis(k,j),這樣一來,當我們遍歷完所有節點k,Dis(i,j)中記錄的便是i到j的最短路徑的距離。

算法描述:
1. 從任意一條單邊路徑開始。所有兩點之間的距離是邊的權,如果兩點之間沒有邊相連,則權爲無窮大。   
2. 對於每一對頂點 u 和 v,看看是否存在一個頂點 w 使得從 u 到 w 再到 v 比己知的路徑更短。如果是更新它。

我覺得上面的介紹已經基本上差不多了,下面是一個帶權有向圖和它的鄰接矩陣。

這裏寫圖片描述

下面是該例子的講解圖。

這裏寫圖片描述

  1. D(1) : 鄰接矩陣的初始狀態。
  2. D(0) : 此時我們對於節點0,檢查Dis(i,0) + Dis(0,j) < Dis(i,j)是否成立。 循環後我們發現Dis(2,0) + Dis(0,1) = 7 < Dis(2,1) = ,所以替換掉 Dis(2,1)=7。
  3. D(1) : 按照2裏面的方法,我們發現Dis(0,1) + Dis(1,2) = 6 < Dis(0,2) = 11。替換掉Dis(0,2) 。重複該步驟,直到循環結束。

而根據圖我們可以知道這個主程序是三層循環,所以時間複雜度爲O(n3 )。但是Dijkstra算法只是從一個源點到其他各點的最短路徑,而如果找尋所有點的話,還是需要一個外循環的,所以其實從這個角度看,Dijkstra和Floyd的時間複雜度可以說是一樣的,只不過Floyd的思想稍微簡單一些。

接下來是主要的算法代碼,在這裏我們使用的圖存儲結構依然是鄰接矩陣。輸入的形式是以鄰接點對的形式,以0 0 0結束。輸入例子就是這個圖。此時我們注意這個鄰接矩陣的對角線需要處理成0,而不是

這裏寫圖片描述

#include<iostream>
#include<string.h>
#include<queue>
using namespace std;
#define MAX 100
#define INT_MAX 10000
#define max(x,y)(x>y?x:y)
typedef struct{
    int matrix[MAX][MAX]; //鄰接矩陣
    int num;// 最大的頂點數
}Graph;

Graph example; //實例圖

// 初始化圖
void initGraph(){
    int i,j;
    for(i=0;i<MAX;i++){
        for(j=0;j<MAX;j++){
            example.matrix[i][j]=INT_MAX;
        }
        example.matrix[i][i]=0;
    }
    example.num=0;
}
// 創建圖
void createPoint(){
    int i,j,val;
    initGraph();
    while(cin>>i>>j>>val){
        if(i==0 && j==0) break;
        example.matrix[i][j]=val;
        example.num = max(example.num,i); 
        //這裏偷了個懶,我這裏假設頂點之間序號是連續的,沒有點之間
        //序號跨很大的情況,如果需要的話,大家可以寫一個函數改一下
        //這裏。
        example.num = max(example.num,j);
    }
}

//輸出最短路徑矩陣
void printResult(){
    cout<<"--------------------------"<<endl;
    for(int i=0;i<=example.num;i++){
        for(int j=0;j<=example.num;j++){
            cout<<example.matrix[i][j]<<" ";
        }
        cout<<endl;
    }
}
//Floyd算法
void Floyd(){
    int i,j,k;
    for(k=0;k<=example.num;k++){
        for(i=0;i<=example.num;i++){
            for(j=0;j<=example.num;j++){
                if(example.matrix[i][k]+example.matrix[k][j]<example.matrix[i][j]){
                    example.matrix[i][j] = example.matrix[i][k]+example.matrix[k][j];
                }
            }
        }
    }
}
int main(){
    createPoint();
    Floyd();
    printResult();
    return 0;
}

最後輸出結果:

0 1 4
0 2 11
1 0 6
1 2 2
2 0 3
0 0 0
--------------------------
0 4 6
5 0 2
3 7 0
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章