图的最短路径问题
图的最短路径问题
在图中,我们经常要求从某一个点到另一个点的最短距离,这种问题就叫最短路径问题。下面介绍几种常用的最短路径算法。
Floyed
Floyed 算法是最简单的最短路径算法,可以计算图中任意两边的最短路径。时间复杂度为 \(O(n^3)\),空间复杂度为 \(O(n^2)\),适用于出现负边权的情况。
算法的思想如下:
设 \(dis_{i,j}\) 是从 \(i\) 到 \(j\) 的最短路径。若 \(dis_{i,k}+dis_{k, j}<dis_{i,j}\),则可以将 \(dis_{i,j}\) 更新为 \(dis_{i,k}+dis_{k, j}\)。
算法描述
- 初始化:若点 \(u,v\) 有边相连,边权为 \(w\),则 \(dis_{u,v}=w\);否则 \(dis_{u,v}=\infty\)。
- 计算过程:按照上述原理进行循环枚举。
- 算法结束:\(dis_{i,j}\) 即为从 \(i\) 到 \(j\) 的最短路径。
算法实现
#include <cstdio>
#include <cstring>
int dis[105][105];
int n, m, s;
void floyed() {
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (dis[i][k] + dis[k][j] < dis[i][j])
dis[i][j] = dis[i][k] + dis[k][j];
}
int main() {
memset(dis, 0x3f, sizeof(dis));
scanf("%d%d%d", &n, &m, &s);
for (int i = 1; i <= m; i++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
dis[u][v] = w;
}
floyed();
for (int i = 1; i <= n; i++)
printf("%d ", dis[s][i]);
return 0;
}
为什么这里要将 \(k\) 放在外层循环呢?因为每次做状态转移时,\(dis_{i,k}\) 和 \(dis_{k,j}\) 都需要已经算出,以保证算法的正确性。
算法变形
Floyed 算法还有一种广泛的应用:对于没有边权的图,我们可以使用该算法的变形来判断两点是否相连。
判断两点连通,需要满足以下两个条件中的至少一个:
- 已经判断出 \(i,j\) 两点连通。
- 存在点 \(k\) ,使得 \(i,k\) 连通且 \(k,j\) 连通。
具体代码如下:
#include <cstdio>
#include <cstring>
bool dis[105][105];
int n, m, s;
void floyed() {
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
dis[i][j] = dis[i][j] || (dis[i][k] && dis[k][j]);
}
int main() {
memset(dis, 0, sizeof(dis));
scanf("%d%d%d", &n, &m, &s);
for (int i = 1; i <= m; i++) {
int u, v;
scanf("%d%d", &u, &v);
dis[u][v] = 1;
}
floyed();
for (int i = 1; i <= n; i++)
printf("%d ", dis[s][i]);
return 0;
}
Dijkstra 算法
Dijkstra 算法是用来计算从一个点到其他所有点的最短路径算法,是一种单源最短路径算法。也就是说,只能计算起点只有一个的情况。
Dijkstra 算法的时间复杂度为 \(O(n^2)\),不能处理存在负边权的情况。
算法描述
设起点为 \(s\),\(dis_v\) 表示从 \(s\) 到 \(v\) 的最短路径。
- 初始化:\(dis_v=\infty(v\neq s)\),\(dis_s=0\)。
- 枚举所有点
- 在没有访问过的点中找一个点 \(u\) 使得 \(dis_u\) 最小。
- 将 \(u\) 标记为已确定最短路径。
- 枚举与 \(u\) 相连的所有未确定最短路径的顶点 \(v\),并更新它的最短路径。
- 算法结束:\(dis_v\) 为 \(s\) 到 \(v\) 的最短路径。
模拟算法过程

如图,以 \(1\) 为起点,粉色表示未访问,绿色表示已访问。使用 Dijkstra 算法模拟从 \(1\) 到所有点的最短路径。
第一次
找到 \(dis_1=0\) 最短,于是将 \(1\) 标记为已访问,并修改与其相连的其他点的值:\(dis_2=2,dis_3=4,dis_4=7\)。如图所示。

第二次
在未访问的点中找到 \(dis_2=2\) 最短,于是将 \(2\) 标记为已访问,并修改其他点的值:\(dis_3=3,dis_5=4\)。如图所示。

第三次
在未访问的点中找到 \(dis_3=3\) 最短,于是将 \(3\) 标记为已访问,并修改其他点的值:\(dis_4=4\)。注意:此时 \(dis_5=9>4\),所以不更新 \(dis_5\)。如图所示。

第四次
在未访问的点中找到 \(dis_4=4\) 最短,于是将 \(4\) 标记为已访问。此时,没有任何结点与其连通,所以不用更新。如图所示。

第五次
现在只剩下一个未访问结点,即点 \(5\)。将其标记为已访问,但无法更新结点。此时,所有结点都已被访问,所以算法结束。如图所示。

下面是该算法的具体执行过程,其中,加粗的数字表示该结点已被访问。
| \(dis\) | 初始化 | 第一次 | 第二次 | 第三次 | 第四次 | 第五次(最终结果) |
|---|---|---|---|---|---|---|
| \(1\) | \(0\) | \(\bm 0\) | \(\bm 0\) | \(\bm 0\) | \(\bm 0\) | \(\bm 0\) |
| \(2\) | \(\infty\) | \(2\) | \(\bm 2\) | \(\bm 2\) | \(\bm 2\) | \(\bm 2\) |
| \(3\) | \(\infty\) | \(4\) | \(3\) | \(\bm 3\) | \(\bm 3\) | \(\bm 3\) |
| \(4\) | \(\infty\) | \(7\) | \(7\) | \(4\) | \(\bm 4\) | \(\bm 4\) |
| \(5\) | \(\infty\) | \(\infty\) | \(4\) | \(4\) | \(4\) | \(\bm 4\) |
算法原理
如图,以 \(1\) 为起点,\(dis_3<dis_2\)。若距离全部为正数,不管 \(2,3\) 之间距离是多少,由 \(2\) 中转再到 \(3\) 的距离肯定大于从 \(1\) 直接到 \(3\) 的距离。

至此,Dijkstra 不能处理负边权的原因也真相大白了。在上图中,若 \(2,3\) 间距离为 \(-2\),则该算法会认为从 \(1\) 直接到 \(3\) 的距离最短,但实际上由 \(2\) 中转的距离是更短的。
代码实现
这里使用邻接表来存储图。C++ 代码如下。
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
struct edge {
int to, cost;
};
vector <edge> g[10005];
int dist[10005];
bool vis[10005];
int n, m, s;
void dijkstra() {
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
dist[s] = 0;
for (int i = 1; i <= n; i++) {
int u = -1;
int mind = 0x3f3f3f3f;
for (int v = 1; v <= n; v++)
if (!vis[v] && dist[v] < mind) {
mind = dist[v];
u = v;
}
if (u == -1) break;
vis[u] = 1;
for (edge e : g[u]) {
int v = e.to, w = e.cost;
if (!vis[v] && dist[u] + w < dist[v])
dist[v] = dist[u] + w;
}
}
}
int main() {
scanf("%d%d%d", &n, &m, &s);
for (int i = 1; i <= m; i++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
g[u].push_back((edge){v, w});
}
dijkstra();
for (int i = 1; i <= n; i++)
printf("%d ", dist[i]);
return 0;
}
(提示:可以使用 vector 来初始化数组,并使用 climits 头文件中的 INT_MAX 宏计算 int 类型最大值)

浙公网安备 33010602011771号