算法设计与分析——Floyd算法(任意两点最短路径问题)

目录

  • 前言
  • 一、算法思想分析
  • 二、算法代码
    • C语言
  • 三、算法效率分析
  • 后记

前言

关于最短路径的问题,我在上一篇文章《算法设计与分析——Dijkstra算法》中已经提到过了。但是,本篇我们需要讲解的Floyd算法,其解决的最短路径问题与Dijkstra算法有些许不同。
在《算法设计与分析——Dijkstra算法》中,我们就强调过,Dijkstra算法是一种单源点最短路径算法,什么是单源点最短路径算法?即在连通图中,我们从一个指定的点(这个就是源点)出发,寻找该点到连通图中其他所有点的最短路径及距离。而Floyd算法,其寻找的是连通图中任意一点到其他所有点的最短路径(我们讲解的过程中注重的是最短路径的距离,如果需要具体的路径,算法运行中做下记录即可),这是一个不同点。
再者,就是算法的核心思想问题,Dijkstra算法采用的是贪心算法(也称贪婪算法),即满足当前最优,以此来达到我最终结果的最优。Floyd算法则是利用的动态规划思想,利用子问题最优解,而来达到最终问题的最优解。二者有相似,但又不同。
那么,不废话了,接下来我们就来具体分析一下Floyd算法

一、算法思想分析

首先,我们来了解一下Floyd算法面对的问题:

  • 问题:给定一个加权连通图,要求找到从每个顶点到其他所有顶点之间的路径。

那么,Floyd算法是如何解决这个问题的呢?
接下来的内容可能有点绕,请大家慢慢阅读。
我们来想象一下,连通图中存在n个顶点,我们依次以这n个顶点作为中间点,什么是中间点?例如:a→b、b→c,如果我们要达成a→c的路径,这个时候b就是中间点。
在选好中间点后,就要开始构造最短路径了,我们的问题是什么?每个顶点到其他顶点之间的最短路径,那么我们肯定要遍历每个顶点到其他顶点吧?所以,选好中间点后,遍历每个点到其他顶点的路径(这里其实获取的是距离)。中间点这个时候的作用就体现到了,这个时候我们有一个选好的中间点,假设我们当前选到的中间点是c而我们当前遍历到的顶点路径是a→d,那么在这种情况下a→d所达成的最短路径距离就有两种构成,是之前已经记录过的a→d的最短路径(这个记录的最短路径,可能是我们利用前面的中间点获取到的zu),是a以c为中间点,然后抵达d的距离(即a→c,c→d的距离之和),这个就是当前情况下我们能够获取到的a→d的距离的两种方案,而要最短,即取二者最短存入即可。
上面是具体分析了一下,接下来我们简单总结一下:

  • 首先遍历所有点,将其作为中间点;
  • 在获取一个中间点后,开始分析每一个点到其他点的路径的情况,取最短路径距离为之前记录过的该点到另外一个点的最短距离和该点到中间点的最短路径距离加中间点到另外一个点的距离(有点像绕口令,希望大家能明白)中最短的那个。
  • 重复第二步操作,知道所有点都已经作为中间点,这个时候我们就获取到每一个顶点到另外一个点的最短路径(距离)。

再总结一下Floyd算法最短路径的计算公式:
算法设计与分析——Floyd算法(任意两点最短路径问题)_第1张图片
说句大白话就是,我到一个地方,我要么用以前的方法过去,要么我途径某个地方,再从这个地方到目的地。当然,这只是个比喻。
单独文字的话肯定过于枯燥,我们来举个《算法设计与分析基础》书中的栗子分析一下:
算法设计与分析——Floyd算法(任意两点最短路径问题)_第2张图片
上图中表示连通图的方式是邻接矩阵的方式,第一个矩阵中0表示自己到自己,肯定距离为0,∞表示暂时不可达(不能直接抵达),其他数字则表示两点间的距离(路径权值)。
但是,我给出的C语言代码将会不一样,也就是矩阵定义的规则变了一下,其他仍然相通。

二、算法代码

C语言

我前面几篇文章都是算法效率分析在前,我觉得这样的顺序似乎有些问题,在本篇文章我先贴出代码,在写出算法效率分析,其效果可能会好一点。
对于接下来我给出的代码,我有一点有说明一下,我给出的邻接矩阵规则不一样,-1表示不可达,0表示自己到自己,其他值则表示两点间距离。但这样其实给自己的代码加了一点工作量,这并不是一个聪明的做法,大家有自己的想法可以进行适当更改的。
注意,一开始用户输入的矩阵和最后获取到的矩阵,其意义已经改变。

/*
动态规划算法-加权连通图中任意两点间的最近距离-Floyd算法
输入:连通图的带权邻接矩阵 其中-1表示 不可达
输出:计算后的任意两点间的最短距离的邻接矩阵(类似于传递闭包的邻接矩阵 但是值表示的是距离 -1表示不可达)
例如:
输入:
0 -1 3 -1
2 0 -1 -1
-1 7 0 1
6 -1 -1 0
输出:
0 10 3 4
2 0  5 6
7 7  0 1
6 16 9 0
*/
#include
#define MAXN 1000
int input[MAXN][MAXN];
int min( int a, int b ) {
	return a < b ? a : b;
}
void Floyd(int n) {
	for(int k=1; k<=n; k++) {
		for(int i=1; i<=n; i++) {
			if(i==k) continue; // 节省计算量
			for(int j=1; j<=n; j++) {
				if(j==k) continue; // 同样 节省计算量 
				int sum;
				// 这里有一个问题 我们给出的公式其实蛮简单 为什么我们这里写的这么复杂呢?
				// 因为。。。我定的是-1为不可达 所以必须做一些特殊处理 这里的话我属于取巧的 
				// 大家可以自行更改这个部分, 不可达的定义也可以自己更改 
				if(input[i][k]==-1||input[k][j]==-1) sum=-1; // 对于求和的 如果一旦其中一个不可达 结果就是不可达 
				else sum=input[i][k]+input[k][j];
				if(input[i][j]==-1&&sum==-1) input[i][j]=-1; // 这里就是 如果两个都不可达 则最终结果就不可达 
				else if(input[i][j]!=-1&&sum==-1) input[i][j]=input[i][j]; //  
				else if(input[i][j]==-1&&sum!=-1) input[i][j]=sum;  // 其中一个可达 就取可达的距离 
				else  input[i][j]=min(input[i][j],sum); // 两个都可达 则取最短的距离 
			}
		}
	}
}
int main() {
	printf("输入有向图的顶点数n:");
	int n;
	scanf("%d",&n);
	printf("输入有向图的带权邻接矩阵(-1表示不可达):\n");
	for(int i=1; i<=n; i++) {
		for(int j=1; j<=n; j++) {
			scanf("%d",&input[i][j]);
		}
	}
	Floyd(n);
	printf("任意两点最短路径的邻接矩阵:\n");
	for(int i=1; i<=n; i++) {
		for(int j=1; j<=n; j++) {
			printf("%d ",input[i][j]);
		}
		printf("\n");
	}
}

代码如上,如果大家对代码有任何建议,欢迎提出,也欢迎大家贴出自己更高效的代码在留言区,谢谢。
这次的JavaScript代码又鸽了,我以后有时间一定补上!

三、算法效率分析

其实从代码中我们就可以看出,有三层嵌套循环,那么算法效率肯定是n的立方级别。
那么对于Floyd算法,算法复杂度其实同样介于O(n²)~O(n³),这是估计来说,但是如果严格来说的话,Floyd算法的算法复杂度还是应该是O(n³)

后记

对于后面这几篇文章分析的算法,其算法效率可能并没有前面的算法来的高,但是,还是那句话,一个算法是解决问题的,那么在解决这个问题的所有算法中进行比较,那才有意义。这些算法,我觉得更重要的是其中的思想,了解其中的一些关键处理,用解决这一个问题的算法思想,去高效的解决另一个问题,这样或许更不错,是吧?

感谢你看到这里。

你可能感兴趣的:(算法设计与分析)