ACM
January 30, 2021

最短路

最短路

定义

最短路问题是图论理论的一个经典问题。寻找最短路径就是在指定网络中两结点间找一条距离最小的路。最短路不仅仅指一般地理意义上的距离最短,还可以引申到其它的度量,如时间、费用、线路容量等。

——百度百科

名词解释

分类

链式前向星

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#define _CRTSECURE_NOWARNINGS
#pragma warning(disable:4996)
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<cctype>
#include<algorithm>
#include<iostream>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<set>
#include<list>
using namespace std;
#define INF 0x3f3f3f3f

int n, m; //n 个点,m 条单向边
int cnt;
int front[10001];

struct EDGE
{
int to, next, w;
};

EDGE edge[10001];

void Init()
{
memset(front, 0, sizeof(front));
cnt = 0;
}

void add_edge(int u, int v, int w)
{
++cnt;
edge[cnt].to = v;
edge[cnt].w = w;
edge[cnt].next = front[u];
front[u] = cnt;
}

int main()
{
freopen("in.txt", "r", stdin);
Init();
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
add_edge(u, v, w);
add_edge(v, u, w); //双向边
}

//遍历与每个点相连的所有边
for (int i = 1; i <= n; i++)
{
printf("%d\n", i);
for (int j = front[i]; j; j = edge[j].next)
{
printf("%d %d %d\n", i, edge[j].to, edge[j].w);
}
printf("\n");
}

return 0;

}

Dijkstra · 单源最短路

思路

  1. 将所有节点分成两个集合,已求出最短路的集合(集合1)和未求出最短路的集合(集合2)。
  2. 更新并记录集合2 中所有节点和源点的距离
  3. 从集合2 中找到距离源点距离最近的点
  4. 将该点移到集合1 中
  5. 重复步骤 2-3,直到集合2 为空

这个方法用了贪心的思想,每次把距离最小的点视作确定的,因为它已经是未确定的点中距离源点最近的了,不可能存在一条路经过其他未确定的点到这个点,距离还比直接到这个点近的了。

模板

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#define _CRTSECURE_NOWARNINGS
#pragma warning(disable:4996)
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<cctype>
#include<algorithm>
#include<iostream>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<set>
#include<list>
using namespace std;
#define INF 0x3f3f3f3f

int n, m; //n 个点,m 条边
int mp[10001][10001];
int dis[10001], vis[10001];

void Init()
{
memset(mp, INF, sizeof(mp));
for (int i = 1; i <= n; i++) mp[i][i] = 0;
}

void Getmap()
{
int u, v, w;
for (int t = 1; t <= m; t++)
{
scanf("%d%d%d", &u, &v, &w);
if (w < mp[u][v])
{
mp[u][v] = w;
mp[v][u] = w;
}
}
}

void Dijkstra(int x)
{
memset(vis, 0, sizeof(vis));
for (int t = 1; t <= n; t++) dis[t] = mp[x][t];

vis[x] = 1;
for (int t = 1; t < n; t++)
{
int minn = INF, temp;
for (int i = 1; i <= n; i++)
{
if (!vis[i] && dis[i] < minn)
{
minn = dis[i];
temp = i;
}
}
vis[temp] = 1;
for (int i = 1; i <= n; i++)
if (mp[temp][i] + dis[temp] < dis[i])
dis[i] = mp[temp][i] + dis[temp];
}
}

int main()
{
scanf("%d%d", &n, &m); //n 个点,m 条边
Init(); //地图初始化
Getmap(); //读图
Dijkstra(x); //以点 x 为出发点的单源最短路
for (int i = 1; i <= n; i++) printf("%d\t%d\n", i, dis[i]); //dis[i]:x 点到 n 点的最短距离


return 0;

}

复杂度 · $O(n2)$

拓展

优化

Floyd · 多源最短路

思路

我们定义一个数组 dis[k][x][y] ,表示只允许经过结点 $V_1$ 到 $V_k$,结点 $x$ 到结点 $y$ 的最短路长度。很显然, dis[n][x][y] 就是最终结点 $x$ 到结点 $y$ 的最短路长度。

dis[0][x][y] 是 $x$ 与 $y$ 的边权,或者 $0$,或者 $\infty$(当 $x$ 与 $y$ 间有直接相连的边的时候,为它们的边权;当 $x = y$ 的时候为零,因为到本身的距离为零;当 $x$ 与 $y$ 没有直接相连的边的时候,为 $\infty$)

dis[k][x][y] = min(dis[k-1][x][y], dis[k-1][x][k]+dis[k-1][k][y])dis[k-1][x][y] 为不经过 $k$ 点的最短路径,而 dis[k-1][x][k]+dis[k-1][k][y] 为经过了 $k$ 点的最短路)。

模板

1
2
3
4
5
6
7
8
9
10
11
12
13
void Floyd()
{
for (int k = 1; k <= n; k++)
{
for (int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
dis[k][i][j] = min(dis[k - 1][i][j], dis[k - 1][i][k] + dis[k - 1][k][j]);
}
}
}
}

不难看出,$k$ 每次循环后只是调用了 $k - 1$ 的数据,而 $k$ 之前的数据对结果没有作用,因此第一维是可以省略的(数据可以不保存,直接在下一次循环被覆盖,但是还是需要有这 $k$ 次循环)

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#define _CRTSECURE_NOWARNINGS
#pragma warning(disable:4996)
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<cctype>
#include<algorithm>
#include<iostream>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<set>
#include<list>
using namespace std;
#define INF 0x3f3f3f3f

int n, m; //n 个点,m 条边
int dis[10001][10001];

void Init()
{
memset(dis, INF, sizeof(dis));
for (int i = 1; i <= n; i++) dis[i][i] = 0;
}

void Getmap()
{
for (int i = 1; i <= m; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
if (w < dis[u][v])
{
dis[u][v] = w;
dis[v][u] = w;
}
}
}

void Floyd()
{
for (int k = 1; k <= n; k++)
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
}
}

int main()
{
scanf("%d%d", &n, &m);
Init();
Getmap();
Floyd();
for (int i = 1; i <= n; i++)
{
for (int j = i; j <= n; j++)
{
printf("%d %d %d\n", i, j, dis[i][j]); //dis[i][j] = i 到 j 的最短距离
}
}


return 0;

}

复杂度

Bellman-Ford · 单源最短路

思路

优化

模板

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#define _CRTSECURE_NOWARNINGS
#pragma warning(disable:4996)
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<cctype>
#include<algorithm>
#include<iostream>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<set>
#include<list>
using namespace std;
#define INF 0x3f3f3f3f

int n, m; //n 个点,m 条单向边
int cnt;
int dis[10001];
int front[10001];

struct EDGE
{
int to, next, w;
};

EDGE edge[10001];

void Init()
{
memset(front, 0, sizeof(front));
cnt = 0;
}

void add_edge(int u, int v, int w)
{
++cnt;
edge[cnt].to = v;
edge[cnt].w = w;
edge[cnt].next = front[u];
front[u] = cnt;
}

bool SPFA(int s)
{
queue<int> q;
bool vis[10001];
int cnt[10001];
memset(dis, INF, sizeof(dis));
memset(vis, 0, sizeof(vis));
memset(cnt, 0, sizeof(cnt));
while (!q.empty()) q.pop();
dis[s] = 0;
cnt[s] = 1;
q.push(s);
vis[s] = 1;
while (!q.empty())
{
int x = q.front();
q.pop();
vis[x] = 0;
for (int i = front[x]; i; i = edge[i].next)
{
int k = edge[i].to;
if (dis[k] > dis[x] + edge[i].w)
{
dis[k] = dis[x] + edge[i].w;
cnt[k] = cnt[x] + 1;
if (cnt[k] > n) return 0;
if (!vis[k])
{
vis[k] = 1;
q.push(k);
}
}
}
}
return 1;
}

int main()
{
freopen("in.txt", "r", stdin);
Init();
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
add_edge(u, v, w);
add_edge(v, u, w); //双向边
}

//SPFA(x):以 x 点为源点的单源最短路
//dis[y]:x 到 y 的最短距离
//SPFA 返回值:是否存在负环
for (int i = 1; i <= n; i++)
{
if (!SPFA(i)) printf("%d Yes\n", i);
else printf("%d No\n", i);
for (int j = 1; j <= n; j++)
{
printf("%d %d %d\n", i, j, dis[j]);
}
printf("\n");
}

return 0;

}

复杂度 · $O(|V| · |E|)$

习题

A 最短路 · HDU 2544

B Til the Cows Come Home · POJ 2387

C Silver Cow Party · POJ 3268

D Heavy Transportation · POJ 1797

E Cow Contest · POJ 3660

F Edge Deletion · CodeForces 1076D

About this Post

This post is written by OwlllOvO, licensed under CC BY-NC 4.0.

#C++#Floyd#Dijkstra#SPFA