一、Dijkstra 算法的基本思想
Dijkstra 算法是解決單源最短路徑問題的一般方法,它是一種貪心算法,要求圖中所有邊的權重非負。它的基本思想是:從一個起始頂點開始向外擴張,持續不斷地將生成的圖擴展到已知距離和最短路徑的區域。簡單地說,就是先加入最近的頂點,然后加入更遠一些的頂點。
Dijkstra 算法類似廣度優先搜索,擴展也是按照階段進行的。設圖的頂點集為 V,邊集為 E,已知最短路徑的頂點集為 R,(R 是 V 的一個子集) ,起始頂點為 s。在每個階段,Dijkstra 算法從集合 V-R 中選擇最短路徑估計最小的頂點 v(在 V-R 中距離 s 最近的頂點),將 v 加入已知區域 R,然后對 v 的鄰接點的最短距離進行調整更新(松弛)。
下面給出拓展 R 的偽代碼:
二、算法要點
該算法有幾個要點。
-
圖的存儲方式。這里使用鄰接表較為簡單。
vector< pair<int, int> > adj[1501]; // adj[i].first為鄰接點的編號,adj[i].second為到邊距離。
-
每個結點需要建立一個結構體
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 到該結點路徑中的上一個結點(前驅),它用來打印路徑。
-
核心部分(參考偽代碼寫出)
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
五、參考文獻
- 算法概論 作者: Sanjoy Dasgupta / Christos Papadimitriou / Umesh Vazirani 出版社: 清華大學出版社,P127-P129
- 算法導論 第三版,P383-P385