最短路径之弗洛伊德算法(Floyd)——动态规划

弗洛伊德算法(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 n1,则经过 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} D1,D0,D1,...,Dk,...,Dn1
    其中, 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(n1)[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语言描述》 严蔚敏著

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值