1. 程式人生 > >圖的最短路徑之迪傑斯特拉演算法和弗洛伊德演算法

圖的最短路徑之迪傑斯特拉演算法和弗洛伊德演算法

一、迪傑斯特拉(Dijkstra)演算法

1、定義描述

  Dijkstra(迪傑斯特拉)演算法是典型的單源最短路徑演算法,用於計算一個節點到其他所有節點的最短路徑。主要特點是以起始點為中心向外層層擴充套件,直到擴充套件到終點為止。Dijkstra演算法的時間複雜度為O(N^2)。例如求下圖中的1號頂點到2、3、4、5、6號頂點的最短路徑:
這裡寫圖片描述

2、演算法思想

  設G=(V,E)是一個帶權有向圖,把圖中頂點集合V分成兩組,第一組為已求出最短路徑的頂點集合(用S表示,初始時S中只有一個源點,以後每求得一條最短路徑 , 就將加入到集合S中,直到全部頂點都加入到S中,演算法就結束了),第二組為其餘未確定最短路徑的頂點集合

(用U表示),按最短路徑長度的遞增次序依次把第二組的頂點加入S中。在加入的過程中,總保持從源點v到S中各頂點的最短路徑長度不大於從源點v到U中任何頂點的最短路徑長度。此外,每個頂點對應一個距離,S中的頂點的距離就是從v到此頂點的最短路徑長度,U中的頂點的距離,是從v到此頂點只包括S中的頂點為中間頂點的當前最短路徑長度。

3、演算法步驟

  • 將所有的頂點分為兩部分:已知最短路程的頂點集合P和未知最短路徑的頂點集合Q。最開始,已知最短路徑的頂點集合P中只有源點一個頂點。我們這裡用一個book[ i ]陣列來記錄哪些點在集合P中。例如對於某個頂點i,如果book[ i ]為1則表示這個頂點在集合P中,如果book[ i ]為0則表示這個頂點在集合Q中
  • 設定源點s到自己的最短路徑為0即dis=0。若存在源點有能直接到達的頂點i,則把dis[ i ]設為e[ s ][ i ]。同時把所有其它(源點不能直接到達的)頂點的最短路徑為設為∞。
  • 在集合Q的所有頂點中選擇一個離源點s最近的頂點u(即dis[u]最小)加入到集合P。並考察所有以點u為起點的邊,對每一條邊進行鬆弛操作。例如存在一條從u到v的邊,那麼可以通過將邊u->v新增到尾部來拓展一條從s到v的路徑,這條路徑的長度是dis[u]+e[u][v]。如果這個值比目前已知的dis[v]的值要小,我們可以用新值來替代當前dis[v]中的值
  • 重複第3步,如果集合Q為空,演算法結束。最終dis陣列中的值就是源點到所有頂點的最短路徑。

4、演算法圖解

這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

二、弗洛伊德(Floyd)演算法

1、定義描述

  Floyd演算法是解決任意兩點間的最短路徑的一種演算法,可以正確處理有向圖或負權的最短路徑問題,同時也被用於計算有向圖的傳遞閉包。Floyd演算法的時間複雜度為O(N^3),空間複雜度為O(N^2)。
這裡寫圖片描述
  上圖中有4個城市8條公路,公路上的數字表示這條公路的長短。請注意這些公路是單向的。我們現在需要求任意兩個城市之間的最短路程,也就是求任意兩個點之間的最短路徑。這個問題這也被稱為“多源最短路徑”問題。

2、演算法思想

  Floyd演算法是一個經典的動態規劃演算法。用通俗的語言來描述的話,首先我們的目標是尋找從點i到點j的最短路徑。從動態規劃的角度看問題,我們需要為這個目標重新做一個詮釋。
  從任意節點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的最短路徑的距離。

3、演算法步驟

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

三、Dijkstra演算法和Floyd演算法的demo:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int inf = 999999;//不連通的點之間的距離設為無窮大
long long int e[10000][10000];
int dis[10000];//最短距離陣列
int book[10000];//記錄下哪些點被選中

//計算單點到全部頂點的距離
int Dijkstra(int &n, int &m, int &s, vector<vector<int>> &data, int &t)
{
    //初始化任意兩點之間的距離陣列
    for (int i = 1; i <= n; ++i)
    {
        for (int j = 1; j <= n; ++j)
        {
            if (i == j)
                e[i][j] = 0;
            else
                e[i][j] = inf;
        }
    }
    //把權值加入到任意兩點之間的距離陣列中
    for (int i = 1; i <= m; ++i)
    {
        e[data[i - 1][0]][data[i - 1][1]] = data[i - 1][2];
    }
    for (int i = 1; i <= n; ++i)
    {
        if (i != s)
        {
            dis[i] = e[s][i];//記錄源點到其餘所有點的最短路徑
            book[i] = 0;//記錄哪些點被選取了
        }
    }
    int u, min;
    for (int i = 1; i <= n - 1; ++i)
    {
        min = inf;
        for (int j = 1; j <= n; ++j)
        {
            if (book[j] == 0 && dis[j] < min)//找到源點離還沒有被選取的點中的最近頂點
            {
                min = dis[j];
                u = j;//記錄下最近頂點的位置
            }
        }
        book[u] = 1;
        /*
        *例如存在一條從u到v的邊,那麼可以通過將邊u->v新增到尾部來拓展一條從源點到v的路徑,
        *這條路徑的長度是dis[u]+e[u][v]。如果這個值比目前已知的dis[v]的值要小,
        *我們可以用新值來替代當前dis[v]中的值。
        */
        for (int v = 1; v <= n; ++v)
        {
            if (e[u][v] < inf)
            {
                if (dis[v] > dis[u] + e[u][v])
                    dis[v] = dis[u] + e[u][v];//鬆弛
            }
        }
    }
    return dis[t];
}

//計算兩兩頂點之間的最短路徑
void Floyd(int &n, int &m, vector<vector<int>> &data)
{
    //初始化任意兩點之間的距離陣列
    for (int i = 1; i <= n; ++i)
    {
        for (int j = 1; j <= n; ++j)
        {
            if (i == j)
                e[i][j] = 0;
            else
                e[i][j] = inf;
        }
    }
    //把權值加入到任意兩點之間的距離陣列中
    for (int i = 1; i <= m; ++i)
    {
        e[data[i - 1][0]][data[i - 1][1]] = data[i - 1][2];
    }
    /*
    *最開始只允許經過1號頂點進行中轉,接下來只允許經過1和2號頂點進行中轉……允許經過1~n號所有頂點
    *進行中轉,求任意兩點之間的最短路程。用一句話概括就是:從i號頂點到j號頂點只經過前k號點的最短路程。
    */
    for (int k = 1; k <= n; ++k)
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= n; ++j)
                if (e[i][j] > e[i][k] + e[k][j])
                    e[i][j] = e[i][k] + e[k][j];
    for (int i = 1; i <= n; ++i)
    {
        for (int j = 1; j <= n; ++j)
            cout << e[i][j] << " ";
        cout << endl;
    }
}

int main(int argc, char const *argv[])
{
    int n, m, s, t;
    cin >> n >> m >> s >> t;//輸入頂點數和邊數,以及起止位置
    vector<vector<int>> Path_Cost;
    for (int i = 0; i < m; ++i)
    {
        vector<int> vec;
        int x;
        for (int j = 0; j < n; ++j)
        {
            cin >> x;
            vec.push_back(x);
        }
        Path_Cost.push_back(vec);
    }
    cout << Dijkstra(n, m, s, Path_Cost, t) << endl;
    Floyd(n, m, Path_Cost);
    system("pause");
    return 0;
}