图论:单源最短路

这是一篇讲解单源最短路算法Dijkstra的文章,阅读前先了解图的数据结构(带权图,无向图,有向图)

概念:

单源最短路:

给定一个带权有向图G=(V,E),其中每条边的权是一个实数。另外,还给定V中的一个顶点,称为源。
要计算从源到其他所有各顶点的最短路径长度。这里的长度就是指路上各边权之和。这个问题通常称
为单源最短路径问题。

Dijkstra

迪杰斯特拉算法(Dijkstra),是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。
迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶
点的邻接节点,直到扩展到终点为止。

需要注意的是,dijkstra算法只能解决正权图问题。

算法原理与实现:

我们使用链式前向星存图,存储每一条边,以edge数组作为容器
edge的下标是cnt,而不是节点名称,cnt作为该条边的编号,规定cnt单增,每一条新的边的编号都是当前cnt+1
edge[cnt].to的值表示这条边指向的目标点
edge[cnt].next的值是一个cnt,它指向的是下一个与起点连接的节点的cnt
如果该节点不指向任何一个节点,那么规定next 的值为-1
edge[cnt].w指的是边的权值
head数组存储每一个点存储的第一条边的编号,下标就是该点的编号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Edge {
public:
int to, next, w;
}edge[Num];
int head[Num], dis[Num], cnt = 0;
bool vis[Num];
void init() {
memset(head, -1, sizeof(head));
}
void addEdge(int u, int v, int w) {
edge[++cnt].to = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt;
}

将所有点归类为两个点集,点集A存储还未找到最短路径的点,点集B则存储已找到最短路的点。
我们创建一个Bool类型vis数组区分每一个点在哪个点集,若为false则说明在A点集,为true则在B点集,
初始所有点的vis默认为false(bool数组的特性,在不初始化时,默认值都为false)
然后设置数组dis,表示源点到每个点距离,在初始时,我们将源点距离设置为0,并将vis设置为true,
其他所有点的距离用memset设置为0x3f(memst函数的赋值方式是逐字节的,而int类型
占4个字节,故memset会将每一个点的距离设置为0x3f3f3f3f,这是一个极大数,用来表示目前无法到达该点)。

1
2
3
4
5
int dis[Num];
bool vis[Num];
memset(dis, 0x3f, sizeof(dis));
dis[Start] = 0;
vis[Start] = true;

由于Dijkstra算法是一种贪心算法,要将当前点集B的所有边进行排序,选出终点不在点集B而且距离最短的边,
使用传统容器每次存储后需要进行排序,这种遍历行为的时间复杂度是O(N)级别,而Dijkstra算法本身就需要
进行O(N)次选点,这样下来总的时间复杂度是O(N^2)级别的,我们能否将其优化,使得将边排序的复杂度降低?
这就需要一个名为堆的数据结构了,堆是一种完全二叉树,是以自上而下升序(降序)的方式排列,插入的复杂度
是O(logN)级别,我们需要的是当前最短的边,因此选择小根堆(升序),C++中的stl中含有一个名为优先队列(priority_queue)的容器内部实现就是堆,所以我们可以直接使用,在堆中存储对组(点的距离,点的编号)
,为了方便,我们用pii命名pair< int, int>。

1
2
using pii = pair<int, int>;
priority_queue<pii, vector<pii>, greater<pii>> heap;

最后便是Dijkstra算法的具体实现,基本原理就是每次存入一个距离源点最短的点,将距离和编号放入堆,然后
查询放入点后源点能到达所有点中,距离最短的点,重复操作,直至所有点都进入点集B,则算法完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void dijkstra(int Start) {
heap.emplace(dis[Start], Start);
while(heap.empty()==0) {
auto now = heap.top();
if (vis[now.second]){
heap.pop();
continue;
}
vis[now.second] = true;
heap.pop();
for (int i = head[now.second]; ~i; i = edge[i].next) {
int v = edge[i].to;
if (dis[v] > edge[i].w + dis[now.second]) {
dis[v] = edge[i].w + dis[now.second];
heap.emplace(dis[v], v);
}
}
}
}

代码总览:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include<bits/stdc++.h>
using namespace std;
const int lim = 2e+5;
class Edge {
public:
int to, next, w;
}edge[lim+5];
using pii = pair<int, int>;
priority_queue<pii, vector<pii>, greater<pii>> heap;
int head[lim+5], dis[lim+5], cnt = 0;
bool vis[lim+5];
void init() {
memset(head, -1, sizeof(head));
}
void addEdge(int u, int v, int w) {
edge[++cnt].to = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt;
}
void dijkstra(int Start) {
memset(dis, 0x3f, sizeof(dis));
dis[Start] = 0;
heap.emplace(dis[Start], Start);
while (heap.empty() == 0) {
auto now = heap.top();
if (vis[now.second]) {
heap.pop();
continue;
}
vis[now.second] = true;
heap.pop();
for (int i = head[now.second]; ~i; i = edge[i].next) {
int v = edge[i].to;
if (dis[v] > edge[i].w + dis[now.second]) {
dis[v] = edge[i].w + dis[now.second];
heap.emplace(dis[v], v);
}
}
}
}
int main() {
int n, m, s;
cin >> n >> m >> s;
init();
int u, v, w;
for (int i = 1; i <= m; i++) {
cin >> u >> v >> w;
addEdge(u, v, w);
}
dijkstra(s);
for (int i = 1; i <= n; i++) {
if (i == 1)cout << dis[i];
else cout << " " << dis[i];
}
return 0;
}

完结撒花