1、 问题描述:
设G=<V, E>为一有向图,V={1,2,...,n},表示顶点编号;E为边的集合,图G中的每一条边(i, j)∈E,对应的距离值为w[i,j]。 顶点i,j间的距离定义为从i出发到j的最短路径长度。
目的:找出图G中每一个顶点到其他所有顶点的距离(有向图,即A到B与B到A的距离可能不一样)。
约定:若(i,j)∉E,则w[i,j]=∞;若i∈V,w[i,i]=0;
问题输入:表示待权有向图G=<V,E>的n*n矩阵W。
问题输出:对任意的i, j∈V,i到j的距离以及最短路径。
2、分析过程:
(1)设G中两个不同的顶点i,j∈V,p是从i到j其间仅经过{1,2,...,k}的最短路径。
(2)若p不经过顶点k,则p也是从i到j其间敬经过{1,2,...,k-1}的最短路径。
(3)若p经过顶点k,即p-->k-->j。将这两段分别记为p1,p2,则p1是从i到k其间仅经过{1,2,...,k-1}的最短路径,p2是k到j其间仅经过{1,2,...,k-1}的最短路径。(可由反证法证明)
个人理解:k为i到j的最短路径上的一个顶点,则i到k的最短路径+k到j的最短路径,与i到j的最短路径相同,其顶点必定相同。 判断顶点k是否需要加入最短路径,比较(2)与(3)的结果即可,若(2)<(3),则k点不加入最短路径。自底向上,逐个判断即可。
定义di,jk为i到j其间仅经过{1,2,...,k}的最短路径长度。递归表达式如下:
\[d_{i,j}^{k}= \begin{cases} w[i,j]& \text{k=0}\\ min\lbrace d_{i,j}^{k-1},d_{i,k}^{k-1} + d_{k,j}^{k-1}\rbrace & \text{1≤k≤n}\\ \end{cases} \]顶点对的距离矩阵:
\[D_k={d_{i,j}^k}_{(n,n)} \]最短路径:
pair floydWarshall(double *w, int n) { // 使用一维数组存储图的矩阵(邻接矩阵) double *d = (double*)malloc(n * n * sizeof(double)); int i, j, k; // 存储最短路径的节点 int *pi = (int*)malloc(n * n * sizeof(int)); // 最短路径节点初始化 for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { // 顶点到自身的路径,顶点到某个无法直接到达的顶点之间路径,初始化为-1 if (j == i || w[i * n + j] >= DBL_MAX) { pi[i * n + j] = -1; } else { pi[i * n + j] = i; } } } // 内存复制,数组的拷贝(动态申请内存的数组),更简洁 memcpy(d, w, n * n * sizeof(double)); //计算最短路径,并保存顶点数据 for (k = 1; k <= n; k++) { // 以下计算,顶点k,对应于其他顶点的最短路径 for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { // 若D[i,j]>D[i,k]+D[k,j],即经过顶点K后,路径变短,则将顶点K加入到[i,j]的路径中 if (d[i * n + j] > d[i * n + k - 1] + d[(k - 1) * n + j]) { // 更新巨鹿数据D[i,j] d[i * n + j] = d[i * n + k - 1] + d[(k - 1) * n + j]; // 更新顶点数据:顶点k对应的前驱节点,即当前顶点对应的一个奠定 pi[i * n + j] = pi[(k - 1) * n + j]; } } } } // 需要返回两个数组,可以使用结构体 return make_pair(d, pi); }
路径打印:
void printAllPairsShortestPath(int *pi, int n, int i, int j) { // 顶点到自身的距离为0,由于初始化时将距离矩阵的对角线初始化为-1,此时将i+1即可 if (i == j) { printf("%d ", i + 1); return ; } // 若距离矩阵中的顶点不存在时,其路径也不存在 if (pi[i * n + j] == -1) { printf("no path from %d to %d exists.\n", i + 1, j + 1); } else { // 递归打印前驱节点 printAllPairsShortestPath(pi, n, i, pi[i * n + j]); printf("%d ", j + 1); } }
测试代码:
#include <stdio.h> #include <float.h> #include <stdlib.h> #include <string.h> typedef struct { void *first; void *second; } pair; pair make_pair(void *f, void *d) { pair p = {f, d}; return p; } pair floydWarshall(double *w, int n) { // 具体实现参考上一小节 } void printAllPairsShortestPath(int *pi, int n, int i, int j) { // 具体实现参考上一小节 } int main(void) { // 初始化 double w[] = {0, 3, 8, DBL_MAX, -4, DBL_MAX, 0, DBL_MAX, 1, 7, DBL_MAX, 4, 0, DBL_MAX, DBL_MAX, 2, DBL_MAX, -5, 0, DBL_MAX, DBL_MAX, DBL_MAX, DBL_MAX, 6, 0}; double *d; int i, j, k, *pi; int n = 5; pair r; // 计算最短路径 r = floydWarshall(w, n); // 各顶点间的最短距离 d = (double *)r.first; // 各顶点对应的前驱节点 pi = (int * )r.second; printf("\n---Shortest distance---\n"); for (i = 0; i < n; i++) { for (j =0; j < n; j++) { printf("%1.1f ", d[i * n + j]); } printf("\n\n"); } printf("\n---Precursor node---\n"); for (i = 0; i < n; i++) { for (j =0; j < n; j++) { printf("%2d ", pi[i * n + j]); } printf("\n\n"); } for (i = 0; i < n; i++) { printf("\n---Shortest path:[%d,x]:---\n", i + 1); for (j =0; j < n; j++) { printAllPairsShortestPath(pi, n, i, j); printf(":%2.1f\n", d[i * n + j]); } } free(d); free(pi); while (1); return 0; }
测试数据:
测试结果:
待补充