Dijkstra 算法(C++)


一、Dijkstra 算法的基本思想

Dijkstra 算法是解決單源最短路徑問題的一般方法,它是一種貪心算法,要求圖中所有邊的權重非負。它的基本思想是:從一個起始頂點開始向外擴張,持續不斷地將生成的圖擴展到已知距離和最短路徑的區域。簡單地說,就是先加入最近的頂點,然后加入更遠一些的頂點。

Dijkstra 算法類似廣度優先搜索,擴展也是按照階段進行的。設圖的頂點集為 V,邊集為 E,已知最短路徑的頂點集為 R,(R 是 V 的一個子集) ,起始頂點為 s。在每個階段,Dijkstra 算法從集合 V-R 中選擇最短路徑估計最小的頂點 v(在 V-R 中距離 s 最近的頂點),將 v 加入已知區域 R,然后對 v 的鄰接點的最短距離進行調整更新(松弛)

下面給出拓展 R 的偽代碼:

二、算法要點

該算法有幾個要點。

  1. 圖的存儲方式。這里使用鄰接表較為簡單。

    vector< pair<int, int> > adj[1501];  // adj[i].first為鄰接點的編號,adj[i].second為到邊距離。
    
  2. 每個結點需要建立一個結構體

    struct Vertex
    {
        int index;
        int known;
        int dist;
        int path;
        Vertex() : index(-1), known(0), dist(INT_MAX), path(-1) {}
    };
    Vertex table[1501];
    
    • index 為圖中結點的編號(可有可無)
    • known 用來標記該節點的最短路徑是否已知(true 已知,false 未知)。true 表明該節點屬於已知區域 R,false 表明該節點屬於 V - R。
    • dist 表示起始頂點 s 到該節點的距離。如果 known 為 true,則 d 為最短距離
    • path 表示起始頂點 s 到該結點路徑中的上一個結點(前驅),它用來打印路徑。
  3. 核心部分(參考偽代碼寫出)

    void Dijkstra(int start, int n)
    {
        table[start].dist = 0;	// 起始頂點的距離為0。
    
        for (;;)
        {
            int k = findmin(n);		// 找到V-R中距離s最近的頂點。
            if (k == -1)			// 所有的頂點都已知最短路徑了,即V=R。
                break;
            table[k].known = 1;		// 將該節點加入已知區域R中。
    
            // 更新當前節點的所有鄰接點的最短路徑(松弛)
            for (int i = 0; i < adj[k].size(); ++i)
            {
                int v = adj[k][i].first;
                if (table[v].known == 0 && table[v].dist > table[k].dist + adj[k][i].second)
                {
                    table[v].dist = table[k].dist + adj[k][i].second;
                    table[v].path = k;
                }
                // 如果節點v最短路徑未知,且經過當前頂點k的路徑,能夠使得從源節點s到結點v的最短路徑的權重比當前的估計值更小,則我們對結點v的估計值dist和前驅path進行更新。
            }
        }
    }
    

    具體細節在注釋。在 main 函數中已經使用 Initiate 進行初始化(其實也不需要,因為 Vertex 類的默認構造函數已經初始化了)。

    對於 findmin,可以使用優先隊列,但是本人水平有限,就使用暴力搜索的方式。

    int findmin(int n)
    {
        int min = INT_MAX, key = -1;
        for (int i = 1; i <= n; ++i)
        {
            if (!table[i].known && table[i].dist < min)
            {
                min = table[i].dist;
                key = i;
            }
        }
    
        return key;
    }
    

三、時間復雜度

這里我使用了數組來存儲結點 dist 的信息,所以 findmin 操作(找到 V-R 中 dist 最小值的頂點)會花費 O(|V|) 時間,松弛操作又花費 O(|E|) 時間,所以運行時間為 O(|V|^2)。
補充:采用不同數據結構時間復雜度

四、測試代碼

最后給出測試代碼,如果有錯請指出。

#include <bits/stdc++.h>

using namespace std;

vector< pair<int, int> > adj[1501];

struct Vertex
{
    int index;
    int known;
    int dist;
    int path;
    Vertex() : index(-1), known(0), dist(INT_MAX), path(-1) {}
};
Vertex table[1501];

void Initiate(int n)
{
    for (int i = 1; i <= n; ++i)
    {
        table[i].index = i;
        table[i].known = 0;
        table[i].dist = INT_MAX;
        table[i].path = -1;
    }
    
    for(int i = 1; i <= n; ++i){
        adj[i].clear();
    }
}

int findmin(int n)
{
    int min = INT_MAX, key = -1;
    for (int i = 1; i <= n; ++i)
    {
        if (!table[i].known && table[i].dist < min)
        {
            min = table[i].dist;
            key = i;
        }
    }

    return key;
}

void Dijkstra(int start, int n)
{
    table[start].dist = 0;

    for (;;)
    {
        int k = findmin(n);
        if (k == -1)
            break;
        table[k].known = 1;

        // 更新當前節點的所有鄰接點
        for (int i = 0; i < adj[k].size(); ++i)
        {
            int v = adj[k][i].first;
            if (table[v].known == 0 && table[v].dist > table[k].dist + adj[k][i].second)
            {
                table[v].dist = table[k].dist + adj[k][i].second;
                table[v].path = k;
            }
        }
    }
}

void Print(int start, int n){
    for(int i = 1; i <= n; ++i){
        if(table[i].known == 1)
            cout << start << "-" << i << ":" << table[i].dist << endl;
    }
}

void print_path(Vertex v) {
    if(v.path != -1) {
        print_path(table[v.path]);
        cout << " to ";
    }
    cout << v.index;
}

int main()
{
    int t;
    cin >> t;   // 樣例數
    for (int i = 1; i <= t; ++i)
    {
        int n, m;
        cin >> n >> m;      // 頂點數,邊數
        Initiate(n);
        int u, v, w;
        for (int i = 1; i <= m; ++i)
        {
            cin >> u >> v >> w;
            adj[u].push_back({v, w});
        }

        int src;
        cin >> src;
        Dijkstra(src, n);
        Print(src, n);      // 輸出從源點到其他點的最短路徑
    }

    return 0;
}
輸入:
1
100 198
1 40 6
1 88 3
2 16 5
3 51 2
4 36 7
4 64 3
4 81 2
5 94 3
6 8 3
7 63 3
7 87 8
7 99 5
8 32 10
8 51 1
8 64 3
8 66 5
8 71 5
8 98 8
9 12 2
9 40 5
10 31 4
10 45 4
10 55 4
11 24 4
11 63 4
12 46 2
12 81 4
12 97 6
13 24 6
15 47 10
15 83 6
15 100 1
16 54 1
16 71 6
17 51 4
17 90 8
18 70 10
18 96 9
19 67 3
21 47 3
21 93 1
21 99 3
22 36 2
23 21 9
23 30 10
23 74 4
24 89 10
25 71 1
26 5 9
26 33 10
26 47 8
26 73 3
26 97 2
27 67 2
28 96 2
29 64 5
29 81 4
30 96 5
31 1 9
31 35 7
32 81 6
33 21 9
33 100 5
34 4 2
34 59 2
34 68 3
34 95 3
38 24 9
39 7 6
39 14 6
39 45 10
39 90 4
39 92 10
41 71 6
41 95 4
42 43 10
42 52 4
42 62 5
42 64 6
43 9 8
43 65 9
43 66 10
43 96 8
44 13 4
44 22 9
44 61 3
44 81 9
45 52 6
46 26 8
47 28 1
47 52 2
47 70 7
48 31 9
48 33 2
50 32 5
50 43 10
52 43 3
52 83 8
53 1 8
53 4 8
53 33 4
53 41 2
53 59 6
54 58 9
54 88 3
56 39 3
57 2 5
57 23 7
57 44 10
59 19 9
60 82 1
61 22 1
62 20 9
62 74 6
63 21 8
63 98 3
64 9 2
64 50 8
65 73 3
66 24 4
66 44 4
67 20 10
67 34 7
67 68 8
67 72 8
67 83 8
67 98 8
68 8 6
68 25 7
68 67 5
69 7 1
69 85 5
70 16 6
70 34 6
70 61 8
70 84 1
70 93 5
71 13 3
71 15 2
71 67 9
71 83 10
71 100 5
72 61 5
73 6 2
73 64 1
74 16 5
74 69 5
76 14 3
77 31 5
77 86 1
78 12 4
78 59 2
78 66 6
79 12 5
79 22 10
79 57 10
79 88 9
80 3 3
81 18 9
81 32 1
81 87 5
82 23 7
82 49 2
83 5 5
83 74 6
83 93 10
84 42 8
84 52 6
84 74 4
84 99 8
85 7 6
85 60 5
86 7 10
88 26 5
88 60 10
89 18 4
91 11 4
91 35 5
91 53 6
92 44 6
93 28 4
93 37 5
93 48 6
93 87 4
94 42 6
94 59 8
94 83 4
95 28 6
96 62 4
97 42 6
98 2 4
98 33 2
98 91 7
99 2 8
100 21 5
100 30 6
100 66 10
100 86 7 
10

輸出:
10-1:13
10-2:40
10-4:44
10-5:23
10-6:26
10-7:30
10-8:29
10-9:21
10-10:0
10-11:47
10-12:23
10-13:31
10-15:36
10-16:29
10-18:36
10-19:43
10-20:34
10-21:40
10-22:31
10-23:34
10-24:27
10-25:52
10-26:21
10-28:30
10-30:42
10-31:4
10-32:28
10-33:31
10-34:42
10-35:11
10-36:33
10-37:33
10-40:19
10-41:51
10-42:29
10-43:13
10-44:27
10-45:4
10-46:25
10-47:29
10-48:34
10-49:29
10-50:33
10-51:30
10-52:10
10-53:49
10-54:30
10-55:4
10-58:39
10-59:34
10-60:26
10-61:30
10-62:25
10-63:33
10-64:25
10-65:22
10-66:23
10-67:43
10-68:45
10-69:29
10-70:36
10-71:34
10-72:51
10-73:24
10-74:24
10-81:27
10-82:27
10-83:18
10-84:37
10-85:34
10-86:43
10-87:32
10-88:16
10-89:37
10-91:43
10-93:28
10-94:26
10-95:45
10-96:21
10-97:23
10-98:36
10-99:35
10-100:36

五、參考文獻

  1. 算法概論 作者: Sanjoy Dasgupta / Christos Papadimitriou / Umesh Vazirani 出版社: 清華大學出版社,P127-P129
  2. 算法導論 第三版,P383-P385


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM