弗洛伊德算法(Floyd)主要针对多源最短路径,且可以解决路径中有负权的情况(不包含负权回路),但是迪杰斯特拉算法只能解决正权值的单源最短路径(可以迭代多次求多源)。 |
1.弗洛伊德算法的基本思想
弗洛伊德算法从图的带权邻接矩阵cost
出发,
假设求从顶点
v
i
v_{i}
vi到
v
j
v_{j}
vj的最短路径:
如果从
v
i
v_{i}
vi到
v
j
v_{j}
vj有弧,则从
v
i
v_{i}
vi到
v
j
v_{j}
vj存在一条长度为arcs[i][j]
的路径,但是不一定是最短的,还需进行n
次试探。
- 首先考虑 k = 0 k=0 k=0时,路径 ( v i , v 0 , v j ) (v_{i},v_{0},v_{j}) (vi,v0,vj)是否存在,若存在则判断 ( v i , v j ) (v_{i},v_{j}) (vi,vj)和 ( v i , v 0 , v j ) (v_{i},v_{0},v_{j}) (vi,v0,vj)的路径长度较短者,为从 v i v_{i} vi到 v i v_{i} vi的中间顶点的序号不大于0的最短路径;
- 假设在路径上再增加一个节点 v 1 v_{1} v1,也就是说如果 ( v i , . . . , v 1 ) (v_{i},...,v_{1}) (vi,...,v1)和 ( v 1 , . . . , v j ) (v_{1},...,v_{j}) (v1,...,vj)分别是当前找到的中间顶点的序号不大于0的最短路径,那么 ( v i , . . . , v 1 , . . . , v j ) (v_{i},...,v_{1},...,v_{j}) (vi,...,v1,...,vj)就有可能是从 v i v_{i} vi到 v j v_{j} vj中间的顶点的序号不大于1的最短路径。将它和从 v i v_{i} vi到 v j v_{j} vj中间的顶点的序号不大于0的最短路径,从中选最小者为最后从 v i v_{i} vi到 v j v_{j} vj中间的顶点的序号不大于1的最短路径;
- 依次类推,逐渐增加中间顶点的序号值到 n − 1 n-1 n−1,则经过 n n n次比较后可求得从 v i v_{i} vi到 v j v_{j} vj的最短路径。
2.弗洛伊德算法的基本表示
弗洛伊德算法属于动态规划算法的一种,而动态规划算法的关键是:
- 如果将原问题划分为子问题?
即如何确定子问题的状态; - 如何通过子问题的求解得到原问题的解?
即如何建立状态转移方程(我喜欢叫它动态规划的递归方程)。
通过1的分析,我们可以这样来定义一个子问题:
即定义一个n阶方阵序列:
D − 1 , D 0 , D 1 , . . . , D k , . . . , D n − 1 D^{-1},D^{0},D^{1},...,D^{k},...,D^{n-1} D−1,D0,D1,...,Dk,...,Dn−1
其中, D k D^{k} Dk表示当前求得的最短路径权值矩阵。
而动态递归方程:
其中, D ( k ) [ i ] [ j ] D^{(k)}[i][j] D(k)[i][j]表示从 v [ i ] v_[i] v[i]到 v j v_{j} vj的中间顶点的序号不大于k的最短路径的长度。 D ( n − 1 ) [ i ] [ j ] D^{(n-1)}[i][j] D(n−1)[i][j]就是我们最后要求的从 v i v_{i} vi到 v j v_{j} vj的最短路径。
3.弗洛伊德算法的实现
有了动态递归方程和初始条件,我们就可以编写代码像解数列公式一样求出各对顶点间的最短路径。
只需注意问题的初始化和主变量的确定,这个问题的主变量是中间顶点的标号,应该将其放在最外层循环,就像数列递推公式的下标一样。
为了存储最短路径序列,我们还需要一个数组path
,且path[v][w]
存储
v
v
v到
w
w
w的最短路径上
v
v
v的后继节点,初始化为
w
w
w。最后可以通过递归打印
v
v
v到
w
w
w的最短路径。
关键代码:
void ShortestPath_FLOYD(MGraph G, int** path, int**distance) {
// 初始化
int u, v, w;
for ( u = 0; u < G.vexnum; u++) {
for (v = 0; v < G.vexnum; v++) {
distance[u][v] = G.arcs[u][v];
path[u][v] = v;
}
}
// 3个FOR循环更新ditance共n-1次
for (u = 0; u < G.vexnum; u++) {
for (v = 0; v < G.vexnum; v++) {
for (w = 0; w < G.vexnum; w++) {
// 设置中间变量,防止整数溢出;
// 当然,若非邻接顶点的表示不是INT_MAX,而是一个非最大值的大数则不用
int temp = (distance[v][u] == INT_MAX || distance[u][w] == INT_MAX) ?
INT_MAX : (distance[v][u] + distance[u][w]);
if (temp < distance[v][w]) {
distance[v][w] = temp;
path[v][w] = path[v][u]; // 更新v的直接后继
}
}
}
}
}
完整代码:
#include <iostream>
#include <string>
using namespace std;
#define MAX_VEX_NUM 50
// 定义图的邻接矩阵存储
typedef struct MGraph {
int arcs[MAX_VEX_NUM][MAX_VEX_NUM];
string vexs[MAX_VEX_NUM];
int arcnum, vexnum;
}MGraph;
void createGraph(MGraph& G);
int locate(MGraph G, string u);
void showMatrix(MGraph G);
void ShortestPath_FLOYD(MGraph G, int** path, int**distance);
void showPath(MGraph G,int** path, int start, int end); //打印start-end的最短路径
int main() {
MGraph G;
createGraph(G);
showMatrix(G);
int** path; // 记录各个最短路径的信息
int** distance; // 记录权值矩阵
path = new int*[G.vexnum];
distance = new int*[G.vexnum];
for (int i = 0; i < G.vexnum; i++) {
path[i] = new int[G.vexnum];
distance[i] = new int[G.vexnum];
}
ShortestPath_FLOYD(G, path, distance);
cout << "path矩阵:\n";
for (int i = 0; i < G.vexnum; i++) {
for (int j = 0; j < G.vexnum; j++) cout << path[i][j];
cout << endl;
}
showPath(G, path,0, 2);
// 释放内存
for (int i = 0; i < G.vexnum; i++) {
delete[] path[i];
delete[] distance[i];
}
delete[] path;
delete[] distance;
system("pause");
return 0;
}
void showPath(MGraph G,int** path, int start, int end) {
int begin = path[start][end];
cout << G.vexs[start] << "到" << G.vexs[end] << "的最短路径:\n";
cout << G.vexs[start];
while (begin != end) {
cout << "-->" << G.vexs[begin];
begin = path[begin][end]; // 迭代后半段
}
cout << "-->" << G.vexs[end] << endl;
}
void showMatrix(MGraph G) {
cout << "图的邻接矩阵:\n";
for (int i = 0; i < G.vexnum; i++) {
for (int j = 0; j < G.vexnum; j++) {
if (G.arcs[i][j] == INT_MAX)
cout << "oo" << " ";
else
cout << G.arcs[i][j] << " ";
}
cout << endl;
}
}
void createGraph(MGraph& G) {
cout << "输入图的顶点数和边数:\n";
cin >> G.vexnum >> G.arcnum;
cout << "输入图的顶点信息:\n";
int i;
for (i = 0; i < G.vexnum; i++) cin >> G.vexs[i];
// 初始化邻接矩阵
int j;
for (i = 0; i < G.vexnum; i++) {
for (j = 0; j < G.vexnum; j++)
G.arcs[i][j] = INT_MAX;
}
cout << "输入图的边的权值信息vi vj weight:\n";
for (i = 0; i < G.arcnum; i++) {
string v1, v2;
int weight;
cin >> v1 >> v2 >> weight;
int l1 = locate(G, v1);
int l2 = locate(G, v2);
G.arcs[l1][l2] = weight;
}
}
测试用例:
3 5
V0 V1 V2
V0 V1 4
V0 V2 11
V1 V0 6
V1 V2 2
V2 V0 3
即带权有向图:
参考资料
《数据结构 C语言描述》 严蔚敏著