1. 程式人生 > 實用技巧 >dijkstra演算法 基礎版+鄰接表、優先佇列優化 雙版本

dijkstra演算法 基礎版+鄰接表、優先佇列優化 雙版本

0 例題

Problem
在每年的校賽裡,所有進入決賽的同學都會獲得一件很漂亮的t-shirt。但是每當我們的工作人員把上百件的衣服從商店運回到賽場的時候,
卻是非常累的!所以現在他們想要尋找最短的從商店到賽場的路線,你可以幫助他們嗎?

Input
輸入包括多組資料。每組資料第一行是兩個整數N、M(N<=100,M<=10000),N表示成都的大街上有幾個路口,標號為1的路口是商店所在地,標號為N的路口是賽場所在地,M則表示在成都有幾條路。N=M=0表示輸入結束。接下來M行,每行包括3個整數A,B,C(1<=A,B<=N,1<=C<=1000),表示在路口A與路口B之間有一條路,我們的工作人員需要C分鐘的時間走過這條路。輸入保證至少存在1條商店到賽場的路線。

Output
對於每組輸入,輸出兩行行,第一行表示工作人員從商店走到賽場的最短時間,第二行表示路徑各點。

Sample Input
2 1
1 2 3
3 3
1 2 5
2 3 5
3 1 2
0 0

Sample Output
3
1 2
2
1 3

1 例題分析

本題求得是源點1到點N的最短路徑最少花費以及具體路徑點。所以很容易地想到求單源最短路徑求解演算法——dijkstra演算法。
在每條邊權值為非負實數的有向帶權圖中,給定一個點稱為源點,求該源點到其他點的最短路徑長度就是單源最短路徑問題。在帶權圖中,兩個頂點的路徑長度指它們之間的路徑上各邊的權值之和。
dijkstra演算法是解決單源最短路徑問題的貪心演算法,它先求出長度最短的一條路徑,然後參照該路徑求出長度次短的一條路徑,直到求出源點到其它各個頂點的最短路徑。
dijkstra演算法的基本思想是假定源點為u,頂點集合V被劃分為兩部分,集合S和集合V-S。初始時集合S中只有源點u,S中的頂點到遠點的最短路徑已確定,V-S中的頂點到源點的最短路徑待定。從源點出發只經過S中的點到達V-S中的點的路徑為特殊路徑,用陣列dist[]記錄每個頂點所對應的最短特殊路徑長度。
dijkstra演算法採用的貪心策略是選擇特殊路徑長度最短的路徑,將其連線的V-S中的頂點加入集合S,同時更新陣列dist[]。一旦S包含了所有頂點,dist[]就是從源點到其他所有頂點的最短路徑長度。

2 程式碼

2.1 鄰接矩陣原始程式碼

#include <iostream>
#include <queue>

#define MAX 1<<30
#define MAXN 1105
using namespace std;
int map[MAXN][MAXN], dist[MAXN], flag[MAXN], p[MAXN], n, m;


/*map[][]為鄰接矩陣,dist[]記錄各點到源點的距離;
 * flag[]記錄頂點的最短路徑是否已經找出;所有的點在V集合中,找出就放在S集合裡,
 * 否則就在V-S集合中,flag=1代表在S集合,否則在V-S集合中;
 * p[]是記錄最短路徑上某一頂點的前驅頂點*/
void dij(int);

void findpath(int);

int main() {
    while (cin >> n >> m, n && m) {
        for (int i = 0; i < 1105; i++) {
            for (int j = 0; j < 1105; j++) {
                map[i][j] = MAX;
            }
        }
        for (int i = 1; i <= m; i++) {
            int a, b, c;
            cin >> a >> b >> c;
            map[a][b] = c;
            map[b][a] = c;
        }/*儲存圖*/
        dij(1);
        cout << dist[n] << endl;
        findpath(1);

        cout << endl;
    }
    return 0;
}

void dij(int v) {
    for (int i = 1; i <= n; i++) {
        dist[i] = map[v][i];
        flag[i] = 0;
        if (dist[i] == MAX) {
            p[i] = -1;/*源點到該點的距離為無限大,說明源點與該點不相鄰*/
        } else {
            p[i] = v;/*說明與源點v相鄰*/
        }
    }
    flag[v] = 1;/*初始化dist[]和flag[]*/
    for (int i = 1; i <= n; i++) { /*找到每個點距離源點最短路徑*/
        int temp = MAX, t = v;
        for (int j = 1; j <= n; j++) {
            if (!flag[j] && dist[j] < temp) {/*找到V-S集合(flag==0代表屬於該集合)中距離源點最小的點,即dist[]最小的點*/
                t = j;
                temp = dist[j];
            }
        }
        if (t == v) {
            return;
        }
        flag[t] = 1;/*將找到的最小dist[]的點t加入S集合*/
        for (int j = 1; j <= n; j++) {/*將找到的點t加入S集合後,更新與t相鄰的點的dist[]值*/
            if (!flag[j] && map[t][j] < MAX) {
                if (dist[j] > temp + map[t][j]) {
                    dist[j] = temp + map[t][j];
                    p[j] = t;
                }
            }
        }
    }
}

void findpath(int v) {
    deque<int> path;
    path.clear();
    /*for (int i = 1; i <= n; i++) {
        int x = n;
        if (x == -1 && v != i) {
            cout << "源點到第" << i << "點" << ",無路可達" << endl;
            continue;
        }
        while (x != -1) {
            path.push_front(x);
            x = p[x];
        }
        cout << "源點到第" << i << "點的最短路徑為:";
        while (!path.empty()) {
            cout << path.front();
        if (path.size() != 1) {
            cout << "--";
        }
        path.pop_front();
        }
        cout << endl;
    }*/
    /*由於此題只需要找出第N點的最短路徑,所以不需要寫出上面的程式碼,上面的程式碼是所有點的最短路徑
     * 本題的程式碼如下*/

    int x = n;
    if (x == -1 && v != n) {
        cout << "no path" << endl;
    }
    while (x != -1) {
        path.push_front(x);
        x = p[x];
    }
    cout << "path:";
    while (!path.empty()) {
        cout << path.front();
        if (path.size() != 1) {
            cout << "--";
        }
        path.pop_front();
    }
    cout << endl;
}

2.2 鄰接表+優先佇列優化後的程式碼

dijkstra演算法實際上就是BFS演算法的“升級版”。BFS演算法每次選擇佇列中的隊首元素進行擴充套件,擴展出隊首元素的相鄰元素(並且未曾訪問過),將這些元素加入佇列;dijkstra演算法是BFS的升級版。當一個圖中的每條邊都加上權值後,BFS就沒辦法求一個點到另一個點的最短路徑了。這時候,需要用到dijkstra演算法。從最基本原理上講,把BFS改成dijkstra演算法,只需要把“佇列”改成“優先佇列”就可以了。