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

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

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

1、最短路徑問題介紹

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

解決問題的演算法:

之前已經對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();
};

  • 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;
    }
}
  • 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;
}

輸入:

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 

輸出:

這裡寫圖片描述