图论:最短路径算法_Floyd算法

Floyd算法

floyd算法是一个经典的动态规划算法,他又被称为插电法。
Floyd算法是一种利用动态规划的思想寻找给定的加权(可以有负权)图中多源点之间最短路径的算法,算法目标是寻找从点i到点j的最短路径。
在这里插入图片描述

dp数组与递推式

既然是动态规划那么一定有递推式,我们发现 i − > j i->j i>j的路径不外乎两种:
1.直接 i − > j i->j i>j中间不经过任何点。
2. i − > k − > j i->k->j i>k>j经过了一些节点k。
k可能是许多点,怎么办,这就用到了之前的一个概念
dist[S,i,j]相对最短路径
从i到j的相对于集合S的最短路径,即从源点i到顶点j的路径中间只能经过已经包含在集合S中的顶点,而不能经过其余还未在集合S中的顶点。
递推边界
开始边界我们可以确定

d i s t [ S , i , j ] , i , j 有 连 线 dist[S,i,j],i,j有连线 dist[S,i,j]ij线

其中S为空集,如果i,j之间有连线, d i s t [ S , i , j ] = c [ i , j ] dist[S,i,j]=c[i,j] dist[S,i,j]=c[i,j]其中c是连线长度;如果没有连线 d i s t [ S , i , j ] = 正 无 穷 dist[S,i,j]=正无穷 dist[S,i,j]=

结束边界当 d i s t [ S , i , j ] dist[S,i,j] dist[S,i,j]中的集合 S = V S=V S=V时我们就得到了全局最短路径,即i到j中间可以经过集合V中的所有点的最短路径。

递推式
集合S做参我们需要进行状态压缩,很麻烦,所以我们可以按编号顺序遍历k,然后用k代替集合S,即

S = { 1 , 2 , 3 , ⋯   , k } ( 0 < k ≤ n ) S=\{1,2,3,\cdots,k\}(0<k\le n) S={1,2,3,,k}(0<kn)

i到j的最短距离就是

d i s t [ k ] [ i ] [ j ] = min ⁡ ( d i s t [ k − 1 ] [ i ] [ j ] , d i s t [ k − 1 ] [ i ] [ k ] + d i s t [ k − 1 ] [ k ] [ j ] ) dist[k][i][j]=\min(dist[k-1][i][j],dist[k-1][i][k]+dist[k-1][k][j]) dist[k][i][j]=min(dist[k1][i][j],dist[k1][i][k]+dist[k1][k][j])

其中遍历k(1—n),这样就将集合S由空集扩充到了集合V。

但实际我们发现我们不用建立一个三维数组,只需一个二维滚动数组即可,这样可以节约很多空间。
我们发现递推式中在求集合k的状态时,仅用到了集合k-1的状态,我们可以仅用两个二维数组存储,一个存集合k的状态,一个存集合k-1的状态,即二者交替。
我们进一步优化,我们能不能将其放进一个数组滚动,自我滚动的前提是在计算一个新值时仅用到了还没有被覆盖的值。

d i s t [ i ] [ j ] = min ⁡ ( d i s t [ i ] [ j ] , d i s t [ i ] [ k ] + d i s t [ k ] [ j ] ) dist[i][j]=\min(dist[i][j],dist[i][k]+dist[k][j]) dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j])

显然 d i s t [ i ] [ k ] , d i s t [ k ] [ j ] dist[i][k],dist[k][j] dist[i][k],dist[k][j]在计算 d i s t [ i ] [ j ] dist[i][j] dist[i][j]之前有被覆盖过,我们可以转变一下思路,其实已经被覆盖但新值与旧值相同的情况下也可以实现正确的滚动。

d i s t [ i ] [ k ] = min ⁡ ( d i s t [ i ] [ k ] , d i s t [ i ] [ k ] + d i s t [ k ] [ k ] ) dist[i][k]=\min(dist[i][k],dist[i][k]+dist[k][k]) dist[i][k]=min(dist[i][k],dist[i][k]+dist[k][k])
d i s t [ k ] [ j ] = min ⁡ ( d i s t [ k ] [ j ] , d i s t [ k ] [ k ] + d i s t [ k ] [ j ] ) dist[k][j]=\min(dist[k][j],dist[k][k]+dist[k][j]) dist[k][j]=min(dist[k][j],dist[k][k]+dist[k][j])

其中 d i s t [ k ] [ k ] dist[k][k] dist[k][k]一定是0(没有负权环的情况下),所以 d i s t [ i ] [ k ] , d i s t [ k ] [ j ] dist[i][k],dist[k][j] dist[i][k],dist[k][j]计算后都不发生改变,进而递推式可以自我滚动

d i s t [ i ] [ j ] = min ⁡ ( d i s t [ i ] [ j ] , d i s t [ i ] [ k ] + d i s t [ k ] [ j ] ) dist[i][j]=\min(dist[i][j],dist[i][k]+dist[k][j]) dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j])

伪码

在这里插入图片描述

代码

#include<iostream>
using namespace std;
const int MAX = 100, MAXN = 0x3f3f3f3f;
int dist[MAX][MAX], path[MAX][MAX];
int map[MAX][MAX];
int n, m;
void floyd() {
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			dist[i][j] = map[i][j];
			path[i][j] = j;
		}
	}

	for (int k = 1; k <= n; k++) {
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= n; j++) {
				if (dist[i][k] != MAXN && dist[k][j] != MAXN) {
					int newdist = dist[i][k] + dist[k][j];
					if (dist[i][j] > newdist) {
						dist[i][j] = newdist;
						path[i][j] = k;
					}
				}
			}
		}
		/*if(map1[k][k] < 0){
	    	printf("错误,有负权环") ;
	    	return ;
		}*/
	}
}

int main()
{
	memset(map, 0x3f, sizeof(map));
	cin >> n >> m;
	for (int i = 1; i <= m; i++) {
		int u, v, w;
		cin >> u >> v >> w;
		map[u][v] = w;
	}

	for (int i = 1; i <= n; i++) {
		map[i][i] = 0;
	}

	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			if (map[i][j] == MAXN) {
				cout << 'x' << ' ';
				continue;
			}
			cout << map[i][j] << ' ';
		}
		cout << endl;
	}

	floyd();

	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			if (dist[i][j] == MAXN) {
				cout << 'x' << endl;
				continue;
			}
			cout << dist[i][j] << ' ';
		}
		cout << endl;
	}
	system("pause");
	return 0;
}

算法复杂度分析

floyd算法中核心代码三层循环,时间复杂度为 O ( n 3 ) O(n^3) O(n3),空间复杂度为 O ( n 2 ) O(n^2) O(n2),算法优势在于求稠密图时算法时间耗时不会明显提升,Dijkstra算法求n次也可以求出图中任意两点之间的最短路径,时间复杂度也为 O ( n 3 ) O(n^3) O(n3),空间复杂度也为 O ( n 2 ) O(n^2) O(n2),但在进行稠密图的运算时算法耗时明显提升。

floyd算法可以解决一些Dijkstra算法解决不了的问题,可以正确的处理负权边,可以快速的发现负权环错误。发现负权环错误需要在代码中加上一个判断,如注释。如果有负权环出现那么一定会在过程中发现 d i s t [ i ] [ i ] < 0 dist[i][i]<0 dist[i][i]<0

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值