算法思想
给定一个带权有向图(即有向网)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 到其它各顶点的最短路径全部求出为止。
例如在图 4.1(a)中,要求顶点 0 到其他各顶点的最短距离分别是多少。其求解过程如图 4.2所示。具体为:
1) 首先求出长度最短的一条最短路径,即顶点 0 到顶点 2 的最短路径,其长度为 5,其实就是顶点 0 到其他各顶点的直接路径中最短的路径(v0→v2)。
2) 顶点 2 的最短路径求出来以后,顶点 0 到其他各顶点的最短路径长度有可能要改变。例如从顶点 0 到顶点 1 的最短路径长度由∞缩短为 20,从顶点 0 到顶点 5 的最短路径长度也由∞缩短为 12。这样长度次短的最短路径长度就是在还未确定最终的最短路径长度的顶点中选择最小的,即顶点 0 到顶点 5 的最短路径长度,为 12,其路径为(v0→v2→v5)。
3) 顶点 5 的最短路径求出来以后,顶点 0 到其他各顶点的最短路径长度有可能要改变。例如从顶点 0 到顶点 3 的最短路径长度由 30 缩短为 22,从顶点 0 到顶点 4 的最短路径长度也由∞缩短为 30。这样长度第三短的最短路径长度就是在还未确定最终的最短路径长度的顶点中选择最小的,即顶点 0 到顶点 1 的最短路径长度,为 20,其路径为(v0→v2→v1)。
4) 此后再依次确定顶点 0 到顶点 3 的最短路径(v0→v2→v5→v3),其长度为 22;以及顶点 0 到顶点 4 的最短路径(v0→v2→v1→v4),其长度为 28。
算法实现
在Dijkstra 算法里,为了求源点 v0 到其他各顶点 vi 的最短路径及其长度,需要设置 3 个数组:
a) dist[n]:dist[i]表示当前找到的从源点 v0 到终点 vi 的最短路径的长度,初始时,dist[i]为 Edge[v0][i],即邻接矩阵的第 v0 行。
b) S[n]:S[i]为 0 表示顶点 vi 还未加入到集合 S 中,S[i]为 1 表示 vi 已经加入到集合 S 中。初始时,S[v0]为 1,其余为 0,表示最初集合 S 中只有顶点 v0。
c) path[n]:path[i]表示 v0 到 vi 的最短路径上顶点 vi 的前一个顶点序号。采用“倒向追踪”的方法,可以确定 v0 到顶点 vi 的最短路径上的每个顶点。在 Dijkstra 算法里,重复做以下 3 步工作:
1) 在数组 dist[n]里查找 S[i] != 1,并且 dist[i]最小的顶点 u;
2) 将 S[u]改为 1,表示顶点 u 已经加入进来了;
3) 修改 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。
代码如下:
1 #include <iostream> 2 #include <string.h> 3 #define INF 1000000 //无穷大 4 #define MAXN 20 //顶点个数的最大值 5 6 int n; //顶点个数 7 int edge[MAXN][MAXN]; //邻接矩阵 8 int visited[MAXN]; //用来划分S,T集合的数组(S:已访问集合,T:未访问集合) 9 int dist[MAXN]; //保存最短路径 10 int path[MAXN]; //保存路径 11 12 void Dijkstra(int v0) //求顶点v0到其他顶点的最短路径 13 { 14 for(int i = 0; i < n; ++i) { 15 dist[i] = edge[v0][i]; 16 visited[i] = 0; 17 if(i != v0 && dist[i] < INF) path[i] = v0; 18 else path[i] = -1; 19 } 20 visited[v0] = 1; 21 //dist[v0] = 0; 22 //从顶点v0确定n-1条最短路径 23 for(int i = 0; i < n-1; ++i) { 24 int min = INF, u = v0; 25 //在T集合中选择具有最短路径的顶点u 26 for(int j = 1; j < n; ++j) { 27 if(!visited[j] && dist[j] < min) { 28 u = j; 29 min = dist[j]; 30 } 31 } 32 visited[u] = 1; //将顶点u加入S集合,表示他的最短路径已找到 33 //更新T集合中顶点的dist和path值 34 for(int k = 1; k < n; ++k) { 35 if(!visited[k] && dist[u] + edge[u][k] < dist[k]) { 36 dist[k] = dist[u] + edge[u][k]; 37 path[k] = u; 38 } 39 } 40 } 41 } 42 43 int main() 44 { 45 int u, v, w; //边的起点、终点及权值 46 scanf("%d", &n); 47 while(1) { 48 scanf("%d%d%d", &u, &v, &w); 49 if(u == -1 && v == -1 && w == -1) break; 50 edge[u][v] = w; 51 } 52 for(int i = 0; i < n; ++i) { 53 for(int j = 0; j < n; ++j) { 54 if(i == j) edge[i][j] = 0; 55 else if(edge[i][j] == 0) edge[i][j] = INF; 56 } 57 } 58 Dijkstra(0); 59 int shortest[MAXN]; //输出最短路径上的各个顶点时存放各个顶点的序号 60 for(int i = 1; i < n; ++i) { 61 printf("%d ", dist[i]); //输出顶点0到顶点i的最短路径长度 62 memset(shortest, 0, sizeof(shortest)); 63 int k = 0; 64 shortest[k] = i; 65 while(path[shortest[k]] != 0) { 66 ++k; shortest[k] = path[shortest[k-1]]; 67 } 68 ++k; shortest[k] = 0; 69 for(int i = k; i > 0; --i) 70 printf("%d->", shortest[i]); 71 printf("%d ", shortest[0]); 72 } 73 return 0; 74 }
该程序的运行示例如下:
输入:
6
0 2 5
0 3 30
1 0 2
1 4 8
2 5 7
2 1 15
4 3 4
5 3 10
5 4 18
-1 -1 -1
输出:
20 0→2→1
5 0→2
22 0→2→5→3
28 0→2→1→4
12 0→2→5