Dijkstra 算法(C++)
一、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