1. 程式人生 > >最短路徑---Floyd演算法(C++)

最短路徑---Floyd演算法(C++)

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次之後,操作完成!

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演算法是如何工作的了,他每次都會選擇一箇中介點,然後,遍歷整個矩陣,查詢需要更新的值,下面還剩下五步,就不繼續演示下去了,理解了方法,我們就可以寫程式碼了。
 

程式碼:

floyd.h

#ifndef FLOYD_H
#define FLOYD_H

#pragma once

#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 v, int e);
    ~Graph_DG();

    // 判斷每次輸入的邊是否合法,頂點從1開始編號
    bool check_edge_value(int start, int end);
    void creatGraph(int kind);
    void print();  // 列印鄰接矩陣
    void Floyd();
    void print_path();  // 列印最短路徑
};

#endif // FLOYD_H

 

floyd.cpp

#include "floyd.h"

Graph_DG::Graph_DG(int v, int e)
{
    vexnum = v;
    edge = e;

    arc = new int*[vexnum];
    dis = new int*[vexnum];
    path = new int*[vexnum];

    for (int i = 0; i < vexnum; ++i)
    {
        arc[i] = new int[vexnum];
        dis[i] = new int[vexnum];
        path[i] = new int[vexnum];

        for (int j = 0; j < vexnum; ++j)
        {
            // 鄰接矩陣初始化為無窮大
            arc[i][j] = INT_MAX;
        }
    }
}

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

// 判斷我們每次輸入的的邊的資訊是否合法
//頂點從1開始編號
bool Graph_DG::check_edge_value(int start, int end)
{
    if (start < 1 || end < 1 || start > vexnum || end > vexnum)  // Floyd演算法,權值可以為負
    {
        return false;
    }
    return true;
}

void Graph_DG::creatGraph(int kind)
{
    cout << "請輸入每條邊的起點和終點(頂點編號從1開始)以及其權重" << endl;
    int start, end, weight;
    int count = 0;
    while (count != edge)
    {
        cin >> start >> end >> weight;
        while (!check_edge_value(start, end))
        {
            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 row = 0;
    int col = 0;

    while (row != vexnum)
    {
        col = 0;
        while (col != vexnum)
        {
            if (arc[row][col] == INT_MAX)
            {
                cout << "∞ ";
            }
            else
            {
                cout << arc[row][col] << " ";
            }
            ++col;
        }
        cout << endl;
        ++row;
    }
}

void Graph_DG::Floyd()
{
    int row, col;
    for (row = 0; row < vexnum; ++row)
    {
        for (col = 0; col < vexnum; ++col)
        {
            // 把矩陣D初始化為鄰接矩陣
            dis[row][col] = arc[row][col];

            // 矩陣P的初值為各個邊的終點頂點下標
            path[row][col] = col;
        }
    }

    // 三重迴圈,用於計算每兩個點之間的最短路徑.【動態規劃的思想】
    int temp, select;
    for (temp = 0; temp < vexnum; ++temp)
    {
        for (row = 0; row < vexnum; ++row)
        {
            for (col = 0; col < vexnum; ++ col)
            {
                // 為防止溢位,引入一個select值
                select = (dis[row][temp] == INT_MAX || dis[temp][col] == INT_MAX) ?
                            INT_MAX : dis[row][temp] + dis[temp][col];
                if (dis[row][col] > select)
                {
                    // 更新D矩陣
                    dis[row][col] = select;

                    // 更新P矩陣
                    path[row][col] = path[row][temp];
                }
            }
        }
    }
}

void Graph_DG::print_path()
{
    cout << "各個頂點對的最短路徑:" << endl;
    int row, col, temp;
    for (row = 0; row < vexnum; ++row)
    {
        for (col = row + 1; col < vexnum; ++col)
        {
            cout << "v" << to_string(row + 1) << "---v" << to_string(col + 1) << " weight: "
                 << dis[row][col] << " path: v" << to_string(row + 1);
            temp = path[row][col];

            // 迴圈輸出途徑的每條路徑
            while (temp != col)
            {
                // path[i][j] = k, 表示從i走到j,第一步需要從i到k
                // 同理,再從k到j,第一步需要走到path[k][j]
                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, edge, kind;
    cout << "輸入圖的種類:1代表有向圖,2代表無向圖" << endl;
    cin >> 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.creatGraph(kind);
    graph.print();
    graph.Floyd();
    graph.print_path();

    return 0;
}

執行結果:

 

注:

  1. 原文程式碼中,解構函式部分delete後面沒有加 [] ,會出現記憶體洩漏的情況。
  2. Floyd演算法可以對負權值圖進行求解,若出現負環,則原問題本身無法求解,因此Floyd演算法也可用於檢驗是否出現負環(初始化所有dis[i][i] = 0,在第三層迴圈relax結束後,加一個判斷dis[i][i]是否為負,若為負則說明存在負環,詳細程式碼可參考下一篇部落格)。
  3. 在列印路徑部分,對path[i][j]的理解如下:path[i][j] = k, 表示從i走到j,第一步需要從i到k;同理,再從k到j,第一步需要走到path[k][j]。
  4. Floyd演算法本身採用的是動態規劃的思想。

 

參考資料:https://blog.csdn.net/qq_35644234/article/details/60875818