1. 最短路径问题
如果从图中某一顶点(称为源点)到达另一顶点(称为终点)的路径可能不止一条,如何找到一条路径,使得沿此路径各边上的权值总和(即从源点到终点的距离)达到最小,这条路径称为最短路径(shortest path)。
根据有向网或无向网中各边权值的取值情形及问题求解的需要,最短路径问题分为以下三种情形,分别用不同的算法求解:
- 求单源最短路径(边的权值非负) - Dijkstra 算法,所谓单源最短路径就是固定一个顶点为源点,求源点到其他每个顶点的最短路径;
- 求单源最短路径(边的权值允许为负值, 但不存在负权值回路) - Bellman-Ford 算法;
- Bellman-Ford 算法的改进 - SPFA 算法;
- 求所有顶点之间的最短路径(边的权值允许为负值,但不存在负权值回路) - Floyd算法。
2. Dijkstra 算法
2.1 算法思想
给定一个带权有向图(即有向网) G 和源点 v0,求 v0 到 G 中其他每个顶点的最短路径。限定各边上的权值大于或等于 0。
例如,在图 4.1(a)中,假设源点为顶点 0,则源点到其他顶点的最短距离分别为:
- 顶点 0 到顶点 1 的最短路径距离是: 20,其路径为 v0→v2→v1;
- 顶点 0 到顶点 2 的最短路径距离是: 5,其路径为 v0→v2;
- 顶点 0 到顶点 3 的最短路径距离是: 22,其路径为 v0→v2→v5→v3;
- 顶点 0 到顶点 4 的最短路径距离是: 28,其路径为 v0→v2→v1→v4;
- 顶点 0 到顶点 5 的最短路径距离是: 12,其路径为 v0→v2→v5。
这些最短路径距离及对应的最短路径是怎么求出来的?
为求得这些最短路径, Dijkstra 提出按路径长度的递增次序,逐步产生最短路径的算法。首先求出长度最短的一条最短路径,再参照它求出长度次短的一条最短路径,依次类推,直到从源点v0 到其它各顶点的最短路径全部求出为止。
在 Dijkstra 算法里, 为了求源点 v0 到其他各顶点 vi 的最短路径及其长度, 需要设置 3 个数组:
- dist[n]: dist[i]表示当前找到的从源点 v0 到终点 vi 的最短路径的长度,初始时, dist[i]为 Edge[v0][i],即邻接矩阵的第 v0 行。
- S[n]: S[i]为 0 表示顶点 vi 还未加入到集合 S 中, S[i]为 1 表示 vi 已经加入到集合 S 中。初始时, S[v0]为 1,其余为 0,表示最初集合 S 中只有顶点 v0。
- path[n]: path[i]表示 v0 到 vi 的最短路径上顶点 vi 的前一个顶点序号。采用“倒向追踪”的方法,可以确定 v0 到顶点 vi 的最短路径上的每个顶点。
在 Dijkstra 算法里,重复做以下 3 步工作:
- 在数组 dist[n]里查找 S[i] != 1,并且 dist[i]最小的顶点 u;
- 将 S[u]改为 1,表示顶点 u 已经加入进来了;
- 修改 T 集合中每个顶点 vk 的 dist 及 path 数组元素值: 当 S[k] != 1, 且顶点 u 到顶点 vk有边(Edge[u][k]<MAX),且 dist[u] + Edge[u][k] < dist[k],则修改 dist[k]为 dist[u] + Edge[u][k],修改 path[k]为 u。
利用 Dijkstra 算法求上图(a)中顶点 0 到其他各顶点的最短路径长度,并输出对应的最短路径 :
分析:
- Dijkstra 算法的 3 个数组: S、 dist、 path 初始状态如下图(a)所示(源点为顶点 0):
- S[0]为 1,其余为 0,表示初始时, S 集合中只有源点 v0,其他顶点都是在集合 T 中;
- dist 数组各元素的初始值就是邻接矩阵的第 v0 行对应的元素值;
- 在 path 数组中, path[2]和 path[3]为 0,表示当前求得的顶点 0 到顶点 2 的最短路径上,前一个顶点是顶点 0;顶点 0 到顶点 3 的最短路径上,前一个顶点也是 0;其余元素值均为-1,表示顶点 0 到其余顶点没有直接路径。
在 Dijkstra 算法执行过程中,以上 3 个数组各元素值的变化如图(b)~(f)所示。以图(b)为例加以解释。首先在 dist 数组的各元素 dist[t]中,选择一个满足 S[t]为 0、且 dist[t]取最小值,求得的最小值为 5,用粗体、斜体标明,对应顶点 v2;然后将 S[2]修改成 1,用粗体、下划线标明,表示将顶点 v2 加入到集合 S 中;最后修改 dist 数组和 path 数组中某些元素的值:
- 修改条件是: S[k]为 0,顶点 v2 到顶点 vk 有直接路径,且 dist[2] + Edge[2][k] < dist[k]。
- 修改方法是:将 dist[k]修改成 dist[2] + Edge[2][k],将 path[k]修改成 2。所有修改过的值用粗体、下划线标明。
当顶点 0 到其他各顶点的最短路径长度求解完毕后, 如何根据 path 数组求解顶点 0 到其他各顶点 vk 的最短路径?方法是:从 path[k]开始,采用“倒向追踪”方法,一直找到源点 v0。
- 设 k 为 4, 则因为 path[4] = 1, 说明最短路径上顶点 4 的前一个顶点是顶点 1; path[1] = 2,说明顶点 1 的前一个顶点是顶点 2; path[2] = 0,说明顶点 2 的前一个顶点是顶点 0,这就是源点; 所以从源点 v0 到终点 v4 的最短路径为: v0→v2→v1→v4, 最短路径长度为 dist[4] = 28。
2.2 算法代码
#include <iostream>
#define INF 10000
#define MAXL 21
using namespace std;
int n, m;
int Edge[MAXL][MAXL];
int S[MAXL];
int path[MAXL];
int dist[MAXL];
void Dijkstra(int v0) {
for (int i = 0; i < n; i++) { // 初始化三个数组
S[i] = 0;
dist[i] = Edge[v0][i];
if (i != v0 && dist[i] < INF) {
path[i] = v0;
}
else {
path[i] = -1;
}
}
S[0] = 1;
dist[v0] = 0;
for (int i = 0; i < n - 1; i++) { // 从顶点 v0 确定 n-1 条最短路径
int Min = INF;
int u = v0;
for (int j = 0; j < n; j++) { // 选择当前集合 T 中具有最短路径的顶点 u
if (!S[j] && dist[j] < Min) {
u = j;
Min = dist[j];
}
}
S[u] = 1; // 将顶点 u 加入到集合 S,表示它的最短路径已求得
for (int k = 0; k < n; k++) { // 修改 T 集合中顶点的 dist 和 path 数组元素值
if (!S[k] && Edge[u][k] < INF && Edge[u][k] + dist[u] < dist[k]) {
dist[k] = Edge[u][k] + dist[u];
path[k] = u;
}
}
}
}
int main() {
int u, v, w;
memset(Edge, 0, sizeof(Edge));
cin >> n >> m;
for (int i = 0; i < m; i++) {
cin >> u >> v >> w;
Edge[u][v] = w; // 有向图
}
for (int i = 0; i < n; i++) { // 初始化Edge矩阵
for (int j = 0; j < n; j++) {
if (i == j) {
Edge[i][j] = 0;
}
else if (Edge[i][j] == 0) {
Edge[i][j] = INF;
}
}
}
Dijkstra(0);
for (int i = 1; i < n; i++) {
cout << dist[i] << endl;
}
return 0;
}
标签:v0,dist,int,路径,最短,问题,path,顶点 From: https://www.cnblogs.com/love-9/p/18119248