1. 程式人生 > >最短路徑問題---Floyd算法詳解

最短路徑問題---Floyd算法詳解

bool value 第一步 mea ESS http cout watermark AR

前言
Genius only means hard-working all one’s life.
Name:Willam
Time:2017/3/8

1、最短路徑問題介紹

問題解釋:
從圖中的某個頂點出發到達另外一個頂點的所經過的邊的權重和最小的一條路徑,稱為最短路徑

解決問題的算法:

  • 迪傑斯特拉算法(Dijkstra算法)
  • 弗洛伊德算法(Floyd算法)
  • SPFA算法

之前已經對Dijkstra算法做了介紹(不懂的可以看這篇博客:Dijkstra算法詳解),所以這篇博客打算對Floyd算法做詳細的的介紹。

2、Floyd算法的介紹

  • 算法的特點:
    弗洛伊德算法是解決任意兩點間的最短路徑的一種算法,可以正確處理有向圖或有向圖或負權(但不可存在負權回路)的最短路徑問題,同時也被用於計算有向圖的傳遞閉包。

  • 算法的思路

通過Floyd計算圖G=(V,E)中各個頂點的最短路徑時,需要引入兩個矩陣,矩陣S中的元素a[i][j]表示頂點i(第i個頂點)到頂點j(第j個頂點)的距離。矩陣P中的元素b[i][j],表示頂點i到頂點j經過了b[i][j]記錄的值所表示的頂點。

假設圖G中頂點個數為N,則需要對矩陣D和矩陣P進行N次更新。初始時,矩陣D中頂點a[i][j]的距離為頂點i到頂點j的權值;如果i和j不相鄰,則a[i][j]=∞,矩陣P的值為頂點b[i][j]的j的值。 接下來開始,對矩陣D進行N次更新。第1次更新時,如果”a[i][j]的距離” > “a[i][0]+a[0][j]”(a[i][0]+a[0][j]表示”i與j之間經過第1個頂點的距離”),則更新a[i][j]為”a[i][0]+a[0][j]”,更新b[i][j]=b[i][0]。 同理,第k次更新時,如果”a[i][j]的距離” > “a[i][k-1]+a[k-1][j]”,則更新a[i][j]為”a[i][k-1]+a[k-1][j]”,b[i][j]=b[i][k-1]。更新N次之後,操作完成!

3、Floyd算法的實例過程

上面,我們已經介紹了算法的思路,如果,你覺得還是不理解,那麽通過一個實際的例子,把算法的過程過一遍,你就明白了,如下圖,我們求下圖的每個點對之間的最短路徑的過程如下:

技術分享圖片

第一步,我們先初始化兩個矩陣,得到下圖兩個矩陣:
技術分享圖片

技術分享圖片

第二步,以v1為中階,更新兩個矩陣:
發現,a[1][0]+a[0][6] < a[1][6] 和a[6][0]+a[0][1] < a[6][1],所以我們只需要矩陣D和矩陣P,結果如下:

技術分享圖片

技術分享圖片

通過矩陣P,我發現v2–v7的最短路徑是:v2–v1–v7

第三步:以v2作為中介,來更新我們的兩個矩陣,使用同樣的原理,掃描整個矩陣,得到如下圖的結果:

技術分享圖片
技術分享圖片

OK,到這裏我們也就應該明白Floyd算法是如何工作的了,他每次都會選擇一個中介點,然後,遍歷整個矩陣,查找需要更新的值,下面還剩下五步,就不繼續演示下去了,理解了方法,我們就可以寫代碼了。

4、Floyd算法的代碼實現

  • Floyd.h文件代碼
/************************************************************/
/*                程序作者:Willam                          */
/*                程序完成時間:2017/3/11                   */
/*                有任何問題請聯系:[email protected]       */
/************************************************************/
//@盡量寫出完美的程序

#pragma once
//#pragma once是一個比較常用的C/C++雜註,
//只要在頭文件的最開始加入這條雜註,
//就能夠保證頭文件只被編譯一次。

/*
本博客開始對Floyd算法的使用鄰接矩陣實現的
*/

#include<iostream>
#include<string>
using namespace std;

class Graph_DG {
private:
    int vexnum;   //圖的頂點個數
    int edge;     //圖的邊數
    int **arc;   //鄰接矩陣
    int ** dis;   //記錄各個頂點最短路徑的信息
    int ** path;  //記錄各個最短路徑的信息
public:
    //構造函數
    Graph_DG(int vexnum, int edge);
    //析構函數
    ~Graph_DG();
    // 判斷我們每次輸入的的邊的信息是否合法
    //頂點從1開始編號
    bool check_edge_value(int start, int end, int weight);
    //創建圖
    void createGraph(int);
    //打印鄰接矩陣
    void print();
    //求最短路徑
    void Floyd();
    //打印最短路徑
    void print_path();
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • Floyd.cpp文件代碼
#include"Floyd.h"


//構造函數
Graph_DG::Graph_DG(int vexnum, int edge) {
    //初始化頂點數和邊數
    this->vexnum = vexnum;
    this->edge = edge;
    //為鄰接矩陣開辟空間和賦初值
    arc = new int*[this->vexnum];
    dis = new int*[this->vexnum];
    path = new int*[this->vexnum];
    for (int i = 0; i < this->vexnum; i++) {
        arc[i] = new int[this->vexnum];
        dis[i] = new int[this->vexnum];
        path[i] = new int[this->vexnum];
        for (int k = 0; k < this->vexnum; k++) {
            //鄰接矩陣初始化為無窮大
            arc[i][k] = INT_MAX;
        }
    }
}
//析構函數
Graph_DG::~Graph_DG() {

    for (int i = 0; i < this->vexnum; i++) {
        delete this->arc[i];
        delete this->dis[i];
        delete this->path[i];

    }
    delete dis;
    delete arc;
    delete path;
}

// 判斷我們每次輸入的的邊的信息是否合法
//頂點從1開始編號
bool Graph_DG::check_edge_value(int start, int end, int weight) {
    if (start<1 || end<1 || start>vexnum || end>vexnum || weight < 0) {
        return false;
    }
    return true;
}

void Graph_DG::createGraph(int kind) {
    cout << "請輸入每條邊的起點和終點(頂點編號從1開始)以及其權重" << endl;
    int start;
    int end;
    int weight;
    int count = 0;
    while (count != this->edge) {
        cin >> start >> end >> weight;
        //首先判斷邊的信息是否合法
        while (!this->check_edge_value(start, end, weight)) {
            cout << "輸入的邊的信息不合法,請重新輸入" << endl;
            cin >> start >> end >> weight;
        }
        //對鄰接矩陣對應上的點賦值
        arc[start - 1][end - 1] = weight;
        //無向圖添加上這行代碼
        if(kind==2)
        arc[end - 1][start - 1] = weight;
        ++count;
    }
}

void Graph_DG::print() {
    cout << "圖的鄰接矩陣為:" << endl;
    int count_row = 0; //打印行的標簽
    int count_col = 0; //打印列的標簽
                       //開始打印
    while (count_row != this->vexnum) {
        count_col = 0;
        while (count_col != this->vexnum) {
            if (arc[count_row][count_col] == INT_MAX)
                cout << "∞" << " ";
            else
                cout << arc[count_row][count_col] << " ";
            ++count_col;
        }
        cout << endl;
        ++count_row;
    }
}

void Graph_DG::Floyd() {
    int row = 0;
    int col = 0;
    for (row = 0; row < this->vexnum; row++) {
        for (col = 0; col < this->vexnum; col++) {
            //把矩陣D初始化為鄰接矩陣的值
            this->dis[row][col] = this->arc[row][col];
            //矩陣P的初值則為各個邊的終點頂點的下標
            this->path[row][col] = col;
        }
    }

    //三重循環,用於計算每個點對的最短路徑
    int temp = 0;
    int select = 0;
    for (temp = 0; temp < this->vexnum; temp++) {
        for (row = 0; row < this->vexnum; row++) {
            for (col = 0; col < this->vexnum; col++) {
                //為了防止溢出,所以需要引入一個select值
                select = (dis[row][temp] == INT_MAX || dis[temp][col] == INT_MAX) ? INT_MAX : (dis[row][temp] + dis[temp][col]);
                if (this->dis[row][col] > select) {
                    //更新我們的D矩陣
                    this->dis[row][col] = select;
                    //更新我們的P矩陣
                    this->path[row][col] = this->path[row][temp];
                }
            }
        }
    }
}

void Graph_DG::print_path() {
    cout << "各個頂點對的最短路徑:" << endl;
    int row = 0;
    int col = 0;
    int temp = 0;
    for (row = 0; row < this->vexnum; row++) {
        for (col = row + 1; col < this->vexnum; col++) {
            cout << "v" << to_string(row + 1) << "---" << "v" << to_string(col+1) << " weight: "
                << this->dis[row][col] << " path: " << " v" << to_string(row + 1);
            temp = path[row][col];
            //循環輸出途徑的每條路徑。
            while (temp != col) {
                cout << "-->" << "v" << to_string(temp + 1);
                temp = path[temp][col];
            }
            cout << "-->" << "v" << to_string(col + 1) << endl;
        }

        cout << endl;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • main.cpp文件的代碼
#include"Floyd.h"


//檢驗輸入邊數和頂點數的值是否有效,可以自己推算為啥:
//頂點數和邊數的關系是:((Vexnum*(Vexnum - 1)) / 2) < edge
bool check(int Vexnum, int edge) {
    if (Vexnum <= 0 || edge <= 0 || ((Vexnum*(Vexnum - 1)) / 2) < edge)
        return false;
    return true;
}
int main() {
    int vexnum; int edge;
    cout << "輸入圖的種類:1代表有向圖,2代表無向圖" << endl;
    int kind;
    cin >> kind;
    //判讀輸入的kind是否合法
    while (1) {
        if (kind == 1 || kind == 2) {
            break;
        }
        else {
            cout << "輸入的圖的種類編號不合法,請重新輸入:1代表有向圖,2代表無向圖" << endl;
            cin >> kind;
        }
    }

    cout << "輸入圖的頂點個數和邊的條數:" << endl;
    cin >> vexnum >> edge;
    while (!check(vexnum, edge)) {
        cout << "輸入的數值不合法,請重新輸入" << endl;
        cin >> vexnum >> edge;
    }
    Graph_DG graph(vexnum, edge);
    graph.createGraph(kind);
    graph.print();
    graph.Floyd();
    graph.print_path();
    system("pause");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

輸入:

2
7 12
1 2 12
1 6 16
1 7 14
2 3 10
2 6 7
3 4 3
3 5 5
3 6 6
4 5 4
5 6 2
5 7 8
6 7 9 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

輸出:

技術分享圖片

最短路徑問題---Floyd算法詳解