暑假,小哼准备去一些城市旅游。有些城市之间有公路,有些城市之间则没有,如下图。为了节省经费以及方便计划旅程,小哼希望在出发之前知道任意两个城市之前的最短路程。

上图中有4个城市8条公路,公路上的数字表示这条公路的长短。请注意这些公路是单向的。我们现在需要求任意两个城市之间的最短路程,也就是求任意两个点之间的最短路径。这个问题这也被称为“多源最短路径”问题。

现在需要一个数据结构来存储图的信息,我们仍然可以用一个4*4的矩阵(二维数组e)来存储。比如1号城市到2号城市的路程为2,则设e[ 1 ][ 2 ]的值为2。2号城市无法到达4号城市,则设置e[ 2 ][ 4 ]的值为∞。另外此处约定一个城市自己是到自己的也是0,例如e[ 1 ][ 5 ]为0,具体如下。

现在回到问题:如何求任意两点之间最短路径呢?通过之前的学习我们知道通过深度或广度优先搜索可以求出两点之间的最短路径。所以进行n的平方遍深度或广度优先搜索,即对每两个点都进行一次深度或广度优先搜索,便可以求得任意两点之间的最短路径。可是还有没有别的方法呢?

我们来想一想,根据我们以往的经验,如果要让任意两点(例如从顶点a点到顶点b)之间的路程变短,只能引入第三个点(顶点k),并通过这个顶点k中转即a->k->b,才可能缩短原来从顶点a点到顶点b的路程。那么这个中转的顶点k是1~n中的哪个点呢?甚至有时候不只通过一个点,而是经过两个点或者更多点中转会更短,即a->k1->k2->b或者a->k1->k2…->ki->…->b。比如上图中从4号城市到3号城市(4->3)的路程e[ 4 ][ 3 ]原本是12。如果只通过1号城市中转(4->1->3),路程将缩短为11(e[ 4 ][ 1 ]+e[ 1 ][ 3 ]=5+6=11)。其实1号城市到3号城市也可以通过2号城市中转,使得1号到3号城市的路程缩短为5(e[ 1 ][ 2 ]+e[ 2 ][ 3 ]=2+3=5)。所以如果同时经过1号和2号两个城市中转的话,从4号城市到3号城市的路程会进一步缩短为10。通过这个的例子,我们发现每个顶点都有可能使得另外两个顶点之间的路程变短。好,下面我们将这个问题一般化。

当任意两点之间不允许经过第三个点时,这些城市之间最短路程就是初始路程,如下。

假如现在只允许经过1号顶点,求任意两点之间的最短路程,应该如何求呢?只需判断e[ i ][ 1 ]+e[ 1 ][ j ]是否比e[ i ][ j ]要小即可。e[ i ][ j ]表示的是从i号顶点到j号顶点之间的路程。e[ i ][ 1 ]+e[ 1 ][ j ]表示的是从i号顶点先到1号顶点,再从1号顶点到j号顶点的路程之和。其中i是1~n循环,j也是1~n循环,代码实现如下。

for(i=1;i<=n;i++)
{for(j=1;j<=n;j++){if ( e[i][j] > e[i][1]+e[1][j] )e[i][j] = e[i][1]+e[1][j];}
}

在只允许经过1号顶点的情况下,任意两点之间的最短路程更新为:

通过上图我们发现:在只通过1号顶点中转的情况下,3号顶点到2号顶点(e[ 3 ][ 2 ])、4号顶点到2号顶点(e[ 4 ][ 2 ])以及4号顶点到3号顶点(e[ 4 ][ 3 ])的路程都变短了。

接下来继续求在只允许经过1和2号两个顶点的情况下任意两点之间的最短路程。如何做呢?我们需要在只允许经过1号顶点时任意两点的最短路程的结果下,再判断如果经过2号顶点是否可以使得i号顶点到j号顶点之间的路程变得更短。即判断e[ i ][ 2 ]+e[ 2 ][ j ]是否比e[ i ][ j ]要小,代码实现为如下。

//经过1号顶点
for(i=1;i<=n;i++)for(j=1;j<=n;j++)if (e[i][j] > e[i][2]+e[1][j] )  e[i][j]=e[i][2]+e[1][j];//经过2号顶点
for(i=1;i<=n;i++)for(j=1;j<=n;j++)if (e[i][j] > e[i][2]+e[2][j])  e[i][j]=e[i][2]+e[2][j];

在只允许经过1和2号顶点的情况下,任意两点之间的最短路程更新为:

通过上图得知,在相比只允许通过1号顶点进行中转的情况下,这里允许通过1和2号顶点进行中转,使得e[ 1 ][ 2 ]和e[ 4 ][ 2 ]的路程变得更短了。

同理,继续在只允许经过1、2和3号顶点进行中转的情况下,求任意两点之间的最短路程。任意两点之间的最短路程更新为:

最后允许通过所有顶点作为中转,任意两点之间最终的最短路程为:

整个算法过程虽然说起来很麻烦,但是代码实现却非常简单,核心代码只有五行:

for(k=1;k<=n;k++)for(i=1;i<=n;i++)for(j=1;j<=n;j++)if(e[i][j]>e[i][k]+e[k][j])e[i][j]=e[i][k]+e[k][j];

这段代码的基本思想就是:最开始只允许经过1号顶点进行中转,接下来只允许经过1和2号顶点进行中转……允许经过1~n号所有顶点进行中转,求任意两点之间的最短路程。用一句话概括就是:从i号顶点到j号顶点只经过前k号点的最短路程。其实这是一种“动态规划”的思想,关于这个思想我们将在《啊哈!算法2——伟大思维闪耀时》在做详细的讨论。下面给出这个算法的完整代码:

#include <stdio.h>
int main()
{int e[10][31],k,i,j,n,m,t1,t2,t3;int inf=99999999; //用inf(infinity的缩写)存储一个我们认为的正无穷值//读入n和m,n表示顶点个数,m表示边的条数scanf("%d %d",&n,&m);//初始化for(i=1;i<=n;i++)for(j=1;j<=n;j++)if(i==j) e[i][j]=0;  else e[i][j]=inf;//读入边for(i=1;i<=m;i++){scanf("%d %d %d",&t1,&t2,&t3);e[t1][t2]=t3;}//Floyd-Warshall算法核心语句for(k=1;k<=n;k++)for(i=1;i<=n;i++)for(j=1;j<=n;j++)if(e[i][j]>e[i][k]+e[k][j] ) e[i][j]=e[i][k]+e[k][j];//输出最终的结果for(i=1;i<=n;i++){for(j=1;j<=n;j++){printf("%10d",e[i][j]);}printf("\n");}return 0;
}

有一点需要注意的是:如何表示正无穷。我们通常将正无穷定义为99999999,因为这样即使两个正无穷相加,其和仍然不超过int类型的范围(C语言int类型可以存储的最大正整数是2147483647)。在实际应用中最好估计一下最短路径的上限,只需要设置比它大一点既可以。例如有100条边,每条边不超过100的话,只需将正无穷设置为10001即可。如果你认为正无穷和其它值相加得到一个大于正无穷的数是不被允许的话,我们只需在比较的时候加两个判断条件就可以了,请注意下面代码中带有下划线的语句。

//Floyd-Warshall算法核心语句
for(k=1;k<=n;k++)for(i=1;i<=n;i++)for(j=1;j<=n;j++)if(e[i][k]<inf && e[k][j]<inf && e[i][j]>e[i][k]+e[k][j])e[i][j]=e[i][k]+e[k][j];上面代码的输入数据样式为:
4 8
1 2 2
1 3 6
1 4 4
2 3 3
3 1 7
3 4 1
4 1 5
4 3 12

第一行两个数为n和m,n表示顶点个数,m表示边的条数。 接下来m行,每一行有三个数t1、t2 和t3,表示顶点t1到顶点t2的路程是t3。 得到最终结果如下:

通过这种方法我们可以求出任意两个点之间最短路径。它的时间复杂度是O(N的三次方)。令人很震撼的是它竟然只有五行代码,实现起来非常容易。正是因为它实现起来非常容易,如果时间复杂度要求不高,使用Floyd-Warshall来求指定两点之间的最短路或者指定一个点到其余各个顶点的最短路径也是可行的。当然也有更快的算法,请看下一节:Dijkstra算法。

另外需要注意的是:Floyd-Warshall算法不能解决带有“负权回路”(或者叫“负权环”)的图,因为带有“负权回路”的图没有最短路。例如下面这个图就不存在1号顶点到3号顶点的最短路径。因为1->2->3->1->2->3->…->1->2->3这样路径中,每绕一次1->-2>3这样的环,最短路就会减少1,永远找不到最短路。其实如果一个图中带有“负权回路”那么这个图则没有最短路。

此算法由Robert W. Floyd(罗伯特·弗洛伊德)于1962年发表在“Communications of the ACM”上。同年Stephen Warshall(史蒂芬·沃舍尔)也独立发表了这个算法。Robert W.Floyd这个牛人是朵奇葩,他原本在芝加哥大学读的文学,但是因为当时美国经济不太景气,找工作比较困难,无奈之下到西屋电气公司当了一名计算机操作员,在IBM650机房值夜班,并由此开始了他的计算机生涯。此外他还和J.W.J. Williams(威廉姆斯)于1964年共同发明了著名的堆排序算法HEAPSORT。堆排序算法我们将在第七章学习。Robert W.Floyd在1978年获得了图灵奖。

推荐阅读


《啊哈!算法》

聊一聊多源最短路径问题(只有5行代码哦)相关推荐

  1. onclick 源码_仿照React源码流程打造90行代码的Hooks

    作者:苏畅 转发链接:https://mp.weixin.qq.com/s/YLSD4IojDWTPlov_RQtVAA 前言 你可能已经看过其它简易的Hooks实现.那么本文和其它实现有什么区别呢? ...

  2. 猜猜乐游戏php源码,C/C++百行代码实现热门游戏消消乐功能的示例代码

    游戏设计 首先我们需要使用第三方框架,这里我使用的是sfml,不会使用sfml在我的上几篇文章当中-扫雷(上)有详细的开发环境搭建介绍 首先准备图片资源 一张背景图片,一张宝石图片 窗口初始化加载图片 ...

  3. Adobe源码泄漏?3行代码搞定,Flash动画无缝导入Android/iOS/cocos2dx(二)

    [注] iOS代码已重构,效率提升90%,200层动画不卡.[2016.10.27] 上一篇 点此阅读 简要介绍了FlashToAnimation的功能,也就是将flash动画无缝导入到Android ...

  4. Adobe源码泄漏?3行代码搞定,Flash动画无缝导入Android iOS cocos2dx(二)

    [注] iOS代码已重构,效率提升90%,200层动画不卡.[2016.10.27] 上一篇 点此阅读 简要介绍了FlashToAnimation的功能,也就是将flash动画无缝导入到Android ...

  5. Adobe源码泄漏?3行代码搞定,Flash动画无缝导入Android/iOS/cocos2dx(一)

    [注] iOS代码已重构,效率提升90%,200层动画不卡.[2016.10.27] 项目介绍 项目名称:FlashAnimationToMobile 源码. 使用方法点这里. 这是一个把flash中 ...

  6. 微信/易信公共平台开发(一):开发了一个简单易用的PHP类(提供源码),十几行代码搞定一个公众号

    这两天学习了一下微信公共平台和易信公共平台的开发 (易信与微信协议是基本一样的)(PS:没听说过易信?你out了?) 公共平台开发原理这里就不讲了,如果是初次接触的同学,建议先看 David_Tang ...

  7. 【算法】【ACM】深入理解Dijkstra算法(单源最短路径算法)

    Dijkstra算法是用来求解从某个源点到其他各顶点的最短路径(单源最短路径). 下面的Dijkstra算法的讲解都是基于这个有向图,在遇到其他问题可以类比. 算法的基本思想: 把图中的定点分成两组, ...

  8. 你必须会的--Dijkstra算法--单源最短路径问题

    文章目录 一.算法原理 1.基本原理 2.如何保存最短路径? 二.算法实战一 1.测试 2.结果 二.算法实战2 Input Output Sample Input Sample Output 一.算 ...

  9. (单源最短路径)一文搞懂dijkstra算法

    前言 大家好,我是bigsai,今天给大家讲讲Dijkstra算法,下次拿着这个算法找女神少绕路,有女朋友的可以试试行不行的通. 对于Dijkstra算法,很多人可能感觉熟悉而又陌生,可能大部分人比较 ...

最新文章

  1. Pandas映射(转化)dataframe中的布尔值True和False值到1和0数值、使用astype函数
  2. 公积金联名卡——提取公积金用,用身份证即可办理
  3. Day5:python之函数(3)
  4. final、finally、finalize差异
  5. 获取来源网站php,js实现获取网站搜索来源代码
  6. NPM酷库:uuid,生成随机ID
  7. 使用GDAL下载并转换SRTM的DEM数据(一)
  8. hibernate操作数据库
  9. K3/Cloud 用插件打开一张已存在的单据
  10. 本博客自排名300到264的各项数据变化
  11. JAVA代码爬虫获取网站信息
  12. 实现虚拟机VMware上Centos操作系统与主机windows之间互相复制与粘贴
  13. 惠普笔记本电脑驱动BIOS下载中心,战66驱动下载
  14. arcgis 批量计算几何_GIS中的计算几何
  15. Axure--使用阿里云搭建自己的原型发布站点
  16. 五十部电影(4)—和青春有关
  17. Qt组件-QLabel
  18. Sed 删除匹配行,匹配的字符用Shell变量替换,且变量里含有斜杠“/”时,无法删除 解决办法
  19. 爱读掌阅java版_爱读掌阅app官方下载-爱读掌阅旧版本 - 超好玩
  20. 滤波算法一(C程序)

热门文章

  1. 货物与产品的区别_详解海外仓与保税仓的区别特点!
  2. java坐标点对称点的输出,编程java来确定一个对称的单词
  3. python3 time模块_python3 time模块
  4. 工程师文化:Chrome快捷键
  5. Laravel使用之七牛对象存储
  6. RedHat 7.0 Chrome浏览器 安装
  7. JAVA多线程Thread VS Runnable详解
  8. Java 中 Comparable 和 Comparator 比较
  9. 24 UsageEnvironment使用环境抽象基类——Live555源码阅读(三)UsageEnvironment
  10. ant design vue table 高度自适应_html之table表格