算法6:只有五行的Floyd最短路算法

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



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


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

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


       我们来想一想,根据我们以往的经验,如果要让任意两点(例如从顶点a点到顶点b)之间的路程变短,只能引入第三个点(顶点k),并通过这个顶点k中转即a->k->b,才可能缩短原来从顶点a点到顶点b的路程。那么这个中转的顶点k是1~n中的哪个点呢?甚至有时候不只通过一个点,而是经过两个点或者更多点中转会更短,即a->k1->k2b->或者a->k1->k2…->k->i…->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。通过这个的例子,我们发现每个顶点都有可能使得另外两个顶点之间的路程变短。好,下面我们将这个问题一般化。

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

       如现在只允许经过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循环,代码实现如下。

1
2
3
4
5
6
7
8
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号顶点的情况下,任意两点之间的最短路程更新为:
081029itl7z7m4l9qqg56d.png

       通过上图我们发现:在只通过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
2
3
4
5
6
7
8
//经过1号顶点
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];
//经过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号顶点的情况下,任意两点之间的最短路程更新为:
081029e7gjlaaul4zk7z4n.png

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

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

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

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

1
2
3
4
5
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——伟大思维闪耀时》在做详细的讨论。下面给出这个算法的完整代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>
int main()
{
    int e[10][10],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即可。如果你认为正无穷和其它值相加得到一个大于正无穷的数是不被允许的话,我们只需在比较的时候加两个判断条件就可以了,请注意下面代码中带有下划线的语句。

1
2
3
4
5
6
//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];



       上面代码的输入数据样式为:

1
2
3
4
5
6
7
8
9
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。
      得到最终结果如下:
081030is22w3mmnz3r33m3.png


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

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

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






转载:http://ahalei.blog.51cto.com/4767671/1383613

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/509553.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vim的强大,vim设置和插件的使用,脱离windows才是王道

map <C-F12> :!ctags -R --c-kindsp --fieldsiaS --extraq .<CR> let Tlist_Show_One_File1 let Tlist_Exit_OnlyWindow1 let Tlist_Auto_Open1 set nocompatible """"""""""界面""&quo…

Apache Kylin从入门到精通

Kylin 文章目录Kylin一、概述1.1 Kylin定义1.2 Kylin架构1.3 Kylin特点1.4 Apache Kylin4 概述为什么选择 Parquet 替换 HBase?预计算结果在 Kylin4.0 中如何存储?Kylin 4.0 的构建引擎Kylin 4.0 的查询引擎Kylin 4.0 与 Kylin 3.1 功能对比Kylin 4.0 性能表现如何升级Kylin …

排序分析

九大基础排序总结与对比 标签&#xff1a; 数据结构排序算法九大排序2016-06-07 15:43 12646人阅读 评论(6) 收藏 举报分类&#xff1a;data structure版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 目录(?)[] 请尊重个人劳动成果&#xff0c;转…

Flume 实战开发指南

Flume 文章目录FlumeFlume介绍Flume核心概念Flume NG的体系结构SourceChannelSinkFlume的部署类型单一流程多代理流程&#xff08;多个agent顺序连接&#xff09;流的合并&#xff08;多个Agent的数据汇聚到同一个Agent &#xff09;多路复用流&#xff08;多级流&#xff09;l…

Flink Chain任务链分隔

Chain分隔 文章目录Chain分隔如何切断任务链&#xff1f;startNewChain 与 disableChaining区别全局切断任务链(chain)web端效果查看隔离后依赖链忙碌程度什么是Backpressured(被压/反压)&#xff1f;代码样例参考文献如何切断任务链&#xff1f; 由于共享slot的存在&#xff…

KMP 深度讲解next数组的求解

【经典算法】——KMP&#xff0c;深入讲解next数组的求解 前言   之前对kmp算法虽然了解它的原理&#xff0c;即求出P0Pi的最大相同前后缀长度k&#xff1b;但是问题在于如何求出这个最大前后缀长度呢&#xff1f;我觉得网上很多帖子都说的不是很清楚&#xff0c;总感觉没有把…

Yarn 命令详细介绍

文章目录yarn命令根据状态查看Yarn全部运行应用程序根据提交程序代码提交类型查看运行程序yarn top 查看正在运行的状态yarn top整体资源使用查看Yarn web页面工具脚本根据yarn应用名称kill进程根据yarn应用名称查看日志yarn命令 根据状态查看Yarn全部运行应用程序 # 查看全部…

BackPressure详细介绍

BackPressure详细介绍 文章目录BackPressure详细介绍前言什么是反压&#xff1f;为什么需要关注反压&#xff1f;为什么不需要关注反压&#xff1f;如何发现和追踪反压的根源&#xff1f;反压的坏处经常碰到哪些问题会任务反压怎么处理反压&#xff1f;前言 Flink反压已经是老…

给IT新人的15个建议:程序员的辛酸反省与总结!

很多人表面上看着老实巴交的&#xff0c;实际上内心比谁都好强、自负、虚荣、甚至阴险。工作中见的多了&#xff0c;也就习惯了。   有一些人&#xff0c;什么事都写在脸上&#xff0c;表面上经常得罪人&#xff0c;甚至让人讨厌。但是他们所表现的又未必不是真性情。 我相信…

AOE网

博客来源&#xff1a;http://blog.csdn.net/wang379275614/article/details/13990163 认识AOE网 有向图中&#xff0c;用顶点表示活动&#xff0c;用有向边表示活动之间开始的先后顺序&#xff0c;则称这种有向图为AOV网络&#xff1b;AOV网络可以反应任务完成的先后顺序&#…

数据结构前缀,后缀,中缀表达式

[cpp] view plaincopy [cpp] view plaincopy <span style"color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px; background-color: rgb(255, 255, 255);">举例&#xff1a;</span> (3 4) 5 - 6 就是中缀表达式 - 3…

priority_queueint,vectorint,greaterint优先队列,按照从小到大

原网址&#xff1a; 优先队列 C优先队列的基本使用方法 在优先队列中&#xff0c;优先级高的元素先出队列。 标准库默认使用元素类型的<操作符来确定它们之间的优先级关系。 优先队列的第一种用法&#xff0c;也是最常用的用法&#xff1a; priority_queue<int>qi;通…

Spark stage如何划分

窄依赖和宽依赖 窄依赖&#xff1a; 指父RDD的每一个分区最多被一个子RDD的分区所用&#xff0c;表现为一个父RDD的分区对应于一个子RDD的分区&#xff0c;和两个父RDD的分区对应于一个子RDD 的分区。图中&#xff0c;map/filter和union属于第一类&#xff0c;对输入进行协同…

引出发射和什么是反射和Class类和Class实例、基本类型的字节码对象

引出发射和什么是反射 问题1: 1.对象有编译类型和运行类型Object obj new java.util.Date();编译类型: Object运行类型: java.util.Date需求:通过obj对象,调用java.util.Date类中的toLocaleString方法.obj.toLocaleString(); 此时编译报错, 编译时,会检查该编译类型中是…

获取类中的构造器

需求:通过反射来获取某一个类的构造器: 1):获取该类的字节码对象. 2):从该字节码对象中去找需要获取的构造器. ------------------------------------------------------------------------ Class类获取构造器方法: Constructor类:表示类中构造器的类型,Constructor的实例…

Maxwell读取MySQL数据

文章目录Maxwell 概述1.1 Maxwell 定义1.2 Maxwell工作原理1.2.1 MySQL主从复制过程1.2.2 Maxwell的工作原理1.2.3 **MySQL** **的** binlog1.3 Maxwell和Canal的对比Maxwell使用2.1 Maxwell安装2.1.1 安装地址2.1.2 安装部署2.1.3 MySQL环境准备2.1.4 初始化Maxwell元数据库2.…

Linux-(C/C++)动态链接库生成以及使用(libxxx.so)

Linux中so文件为共享库&#xff0c;与windows下dll类似&#xff0c;不过实现要简单。 so可以供多个进程使用&#xff0c;不同进程调用同一个so文件&#xff0c;所使用so文件不同。 so文件源程序不需要main函数&#xff0c;有也不会被执行。 下面通过一个简单例子&#xff0c;来…

IO之打印流

打印流,打印数据的,打印流只能是输出流: PrintStream: 字节打印流 PrintWriter: 字符打印流 -对于PrintWriter来说,当启用字段刷新之后, 调用println或者printf或者format方法,便会立马刷新操作. 如果没有开启自动刷新,则需要手动刷新或者当缓冲区满的时候,再自动刷新. 使…

IO之 Properties类加载文件

配置文件:资源文件(以.properties作为拓展名的文件)/属性文件: 做项目开发,为何使用配置文件? 把所有的数据存储在代码中,写死了,”硬编码”. 比如:在Java中需要连接数据库,必须拥有数据的账号和密码. 此时我们就得在Java代码中编写,类似的代码: String username”root”…

IO之数据流

数据流,提供了可以读/写任意数据类型的方法: DataOutputStream: 提供了 writeXxx(xxx value)方法. DataInputStream: 提供了 readXxx()方法. 注意: writeXxx和readXxx必须要对应起来, writeByte写出的数据,此时只能使用readByte读取回来.