所有点对间最短路径 | 弗洛伊德算法 | All Pairs Shortest Path | C/C++实现 | 大年初一写CSDN

问题描述

请例举出加权有向图 G = ( V , E ) G=(V,E) G=(V,E)中每两点之间的最短路径的长度。

输入:
输入按照以下形式给出
∣ V ∣ |V| V ∣ E ∣ |E| E
s 0 s_0 s0 t 0 t_0 t0 d 0 d_0 d0
s 1 s_1 s1 t 1 t_1 t1 d 1 d_1 d1

s ∣ E ∣ − 1 s_{|E|-1} sE1 t ∣ E ∣ − 1 t_{|E|-1} tE1 d ∣ E ∣ − 1 d_{|E|-1} dE1
其中|V|、|E|分别代表图G的顶点数和边数。图G的各顶点编号分别为0, 1, …, |V|-1。
s i s_i si t i t_i ti d i d_i di分别表示图G第 i 条边(有向)连接的2个顶点的编号以及该边的权值。
输出:
如果图G包含负环(各边权值总和为负数的环),则在1行中输出以下内容
NEGATIVE CYCLE
否则按照以下格式输出路径长度。
D 0 , 0 D_{0,0} D0,0 D 0 , 1 D_{0,1} D0,1 D 0 , ∣ V ∣ − 1 D_{0,|V|-1} D0,V1
D 1 , 0 D_{1,0} D1,0 D 1 , 1 D_{1,1} D1,1 D 1 , ∣ V ∣ − 1 D_{1,|V|-1} D1,V1

D ∣ V ∣ − 1 , 0 D_{|V|-1,0} DV1,0 D ∣ V ∣ − 1 , 1 D_{|V|-1,1} DV1,1 D ∣ V ∣ − 1 , ∣ V ∣ − 1 D_{|V|-1,|V|-1} DV1,V1
总共占|V|行。在第 i 行中按顺序输出顶点 i 到各顶点 j 之间最短路径的长度。i 到 j 之间不存在路径时输出INF。相邻数值之间用1个空格隔开。
限制:
1 ≤ |V| ≤ 100
0 ≤ |E| ≤ 9900
− 2 ∗ 1 0 − 7 -2*10^{-7} 2107 d i d_i di 2 ∗ 1 0 7 2*10^7 2107
图G不存在多重边。
图G不存在自身循环。

输入示例

第一组
4 6
0 1 1
0 2 5
1 2 2
1 3 4
2 3 1
3 2 7
第二组
4 6
0 1 1
0 2 -5
1 2 2
1 3 4
2 3 1
3 2 7
第三组
4 6
0 1 1
0 2 5
1 2 2
1 3 4
2 3 1
3 2 -7

输出示例

第一组
0 1 3 4
INF 0 2 3
INF INF 0 1
INF INF 7 0
第二组
0 1 -5 -4
INF 0 2 3
INF INF 0 1
INF INF 7 0
第三组
NEGATIVE CYCLE

讲解

所有点对间最短路径问题APSP是指以图 G = ( V , E ) G=(V,E) G=(V,E)为对象,求G中每两点之间的最短路径(距离)的问题。如果G中不存在权值为负的边,我们可以将各个顶点作为起点执行|V|次狄克斯特拉算法来求解这类问题。这样做的算法复杂度为 O ( ∣ V ∣ 3 ) O(|V|^3) O(V3),优先级队列实现的话可以简化至 O ( ∣ V ∣ ( ∣ E ∣ + ∣ V ∣ ) l o g ∣ V ∣ ) O(|V|(|E|+|V|)log|V|) O(V(E+V)logV)

在解决APSP问题上,复杂度为 O ( ∣ V ∣ 3 ) O(|V|^3) O(V3)的弗洛伊德算法广为人知。它不需要G的所有边均非负,只要G不包含环即可正常执行。负环指所有边的权值之和为负的环。这种环可以让两点之间的成本无限缩小,因此无法定义最短路径。

弗洛伊德算法的另一个功能就是判断G中是否存在负环。算法执行结束时,如果G的某顶点v到顶点v(其自身)的最短距离为负,就证明G中存在负环。

弗洛伊德算法让图中各组顶点[i, j]之间的最短路径成本与二维数组元素A[i, j]相对应,然后用动态规划法进行求解。为方便说明,我们设 G = ( V , E ) G=(V,E) G=(V,E)的顶点为{1, 2, 3, …|V|}。

设从顶点 i 出发,仅经由顶点 V k V_k Vk = {1, 2 ,3, …, k}抵达顶点 j 的最短路径成本为 A k [ i , j ] A^k[i,j] Ak[i,j] P k [ i , j ] P^k[i,j] Pk[i,j]为此过程的路径之一。弗洛伊德算法就是依次对k = {1, 2, 3, …|V|}分别递归地计算 A k A^k Ak(即通过 A k − 1 A^{k-1} Ak1计算 A k A^k Ak),从而最终确定 A ∣ V ∣ A^{|V|} AV,也就是 A [ i , j ] A[i,j] A[i,j]

首先, A 0 [ i , j ] A^0[i,j] A0[i,j]表示从 i 到 j 不经由其他任何顶点,所有其值就等于连接 i 与 j 的边的权值。也就是说, A [ i , j ] A[i,j] A[i,j]与图的邻接矩阵相对应,从 i 到 j 存在权值为d的边时 A [ i , j ] = d A[i,j]=d A[i,j]=d,不存在边时 A [ i , j ] = ∞ A[i,j]=\infty A[i,j]=。此外,我们设 A [ i , i ] = 0 A[i,i]=0 A[i,i]=0显而易见,此时 A 0 [ i , j ] A^0[i,j] A0[i,j]就是 i 到 j 的最短路径成本。

接下来时k为1, 2, 3, …|V|的情况,我们要通过 A k − 1 A^{k-1} Ak1来计算 A k A_k Ak。这里我们要分别考虑 P k [ i , j ] P^k[i,j] Pk[i,j]经过点k与不经过点k两种情况。

如果 P k [ i , j ] P^k[i,j] Pk[i,j]不经过顶点k,那就意味着 P k [ i , j ] P^k[i,j] Pk[i,j]只经过端点 i、j、以及属于 V k − 1 V^{k-1} Vk1 = {1, 2, 3, …, k-1}的顶点,所以此时的 P k [ i , j ] P^k[i,j] Pk[i,j]就相当于 P k − 1 [ i , j ] P^{k-1}[i,j] Pk1[i,j]。于是这种情况下的 A k [ i , j ] A^k[i,j] Ak[i,j] = A k − 1 [ i , j ] A^{k-1}[i,j] Ak1[i,j]

如果 P k [ i , j ] P^k[i,j] Pk[i,j]经过顶点k,则 P k [ i , j ] P^k[i,j] Pk[i,j]会被k分为 i - k和k - j 两个子路径,且这两个子路径全都只经过 V k − 1 V^{k-1} Vk1 = {1, 2, 3, …, k-1}中的顶点。因此经过k的最短路径的子路径为 P k − 1 [ i , k ] P^{k-1}[i,k] Pk1[i,k] P k − 1 [ k , j ] P^{k-1}[k,j] Pk1[k,j]。也就是说, A k [ i , j ] A^k[i,j] Ak[i,j] = A k − 1 [ i , k ] A^{k-1}[i,k] Ak1[i,k] + A k − 1 [ i , j ] A^{k-1}[i,j] Ak1[i,j]

综上下式对所有 i、j 均成立
A k [ k , j ] A^k[k,j] Ak[k,j] = min( A k − 1 [ i , j ] A^{k-1}[i,j] Ak1[i,j], A k − 1 [ i , k ] A^{k-1}[i,k] Ak1[i,k] + A k − 1 [ k , j ] A^{k-1}[k,j] Ak1[k,j])

这个算法在求 A k [ i , j ] A^k[i,j] Ak[i,j]时看似需要占用 A [ i , j , k ] A[i,j,k] A[i,j,k]大小的内存空间 ( O ( ∣ V ∣ 3 ) ) (O(|V|^3)) (O(V3))。或许有人会认为在计算 A k [ i , j ] A^k[i,j] Ak[i,j]的过程中 A k − 1 [ i , j ] A^{k-1}[i,j] Ak1[i,j]的值会发生变化,但实际上由 A k [ k , k ] A^k[k,k] Ak[k,k] = 0可知
A k [ i , k ] A^k[i,k] Ak[i,k] = min( A k − 1 [ i , k ] A^{k-1}[i,k] Ak1[i,k], A k − 1 [ i , k ] A^{k-1}[i,k] Ak1[i,k] + A k − 1 [ k , k ] A^{k-1}[k,k] Ak1[k,k]) = A k − 1 [ i , k ] A_{k-1}[i,k] Ak1[i,k]
A k [ k , j ] A^k[k,j] Ak[k,j] = min( A k − 1 [ k , j ] A^{k-1}[k,j] Ak1[k,j], A k − 1 [ k , k ] A^{k-1}[k,k] Ak1[k,k] + A k − 1 [ k , j ] A^{k-1}[k,j] Ak1[k,j]) = A k − 1 [ k , j ] A_{k-1}[k,j] Ak1[k,j]

因此每一组顶点 [ i , j ] [i,j] [i,j] A k [ i , j ] A^k[i,j] Ak[i,j]均可以用 A k − 1 [ i , j ] A^{k-1}[i,j] Ak1[i,j]覆盖, A k [ i , j ] A^k[i,j] Ak[i,j]保存在二维数组中不会影响运算结果。

弗洛伊德算法可通过下述方法实现:

warshallFloyd() //1起点数组
	for k = 1 to |V|
		for i = 1 to |V|
			for j = 1 to |V|
				A[i][j] = min(A[i][j], A[i][k] + A[k][j])

AC代码如下

#include<iostream>
#include<algorithm>
#include<vector>
#include<climits>
using namespace std;

static const int MAX = 100;
static const long long INFTY = (1LL<<32);

int n;
long long d[MAX][MAX];

void floyd(){
	for(int k = 0; k < n; k++){
		for(int i = 0; i < n; i++){
			if(d[i][k] == INFTY) continue;
			for(int j = 0; j < n; j++){
				if(d[k][j] == INFTY) continue;
				d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
			}
		}
	}
} 

int main(){
	int e, u, v, c;
	cin>>n>>e;
	
	for(int i = 0; i < n; i++){
		for(int j = 0; j < n; j++){
			d[i][j] = ( (i == j) ? 0 : INFTY);
		}
	}
	
	for(int i = 0; i < e; i++){
		cin>>u>>v>>c;
		d[u][v] = c;
	}
	
	floyd();
	
	bool negative = false;
	for(int i = 0; i < n; i++) if(d[i][i] < 0) negative = true;
	
	if(negative){
		cout<<"NEGATIVE CYCLE"<<endl;
	} else {
		for(int i = 0; i < n; i++){
			for(int j = 0; j < n; j++){
				if(j) cout<<" ";
				if(d[i][j] == INFTY) cout<<"INF";
				else cout<<d[i][j];
			}
			cout<<endl;
		}
	}
	
	return 0;
}
  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
使用图的最短路径弗洛伊德算法实现地铁计费的Java代码如下: ```java import java.util.*; public class Subway { private int[][] graph; public Subway(int numStations) { graph = new int[numStations][numStations]; for (int i = 0; i < numStations; i++) { Arrays.fill(graph[i], Integer.MAX_VALUE); graph[i][i] = 0; } } public void addConnection(int station1, int station2, int cost) { graph[station1][station2] = cost; graph[station2][station1] = cost; } public int getShortestPath(int startStation, int endStation) { int numStations = graph.length; // Use Floyd's algorithm to compute all-pairs shortest paths for (int k = 0; k < numStations; k++) { for (int i = 0; i < numStations; i++) { for (int j = 0; j < numStations; j++) { if (graph[i][k] != Integer.MAX_VALUE && graph[k][j] != Integer.MAX_VALUE) { graph[i][j] = Math.min(graph[i][j], graph[i][k] + graph[k][j]); } } } } return graph[startStation][endStation]; } public static void main(String[] args) { Subway subway = new Subway(6); subway.addConnection(0, 1, 3); subway.addConnection(0, 2, 2); subway.addConnection(1, 3, 4); subway.addConnection(2, 3, 5); subway.addConnection(2, 4, 6); subway.addConnection(3, 4, 1); subway.addConnection(3, 5, 5); subway.addConnection(4, 5, 2); int shortestPath = subway.getShortestPath(0, 5); System.out.println("Shortest path from station 0 to station 5: " + shortestPath); } } ``` 在这个例子中,我们建立了一个地铁系统,并添加了6个地铁站和它们之间的连接。然后,我们使用弗洛伊德算法计算从第0个站到第5个站的最短路径。运行结果为: ``` Shortest path from station 0 to station 5: 8 ``` 这意味着从第0个站到第5个站的最短路径是8,即需要支付8元的地铁费用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值