Floyd最短路算法

  ----转自啊哈磊【坐在马桶上看算法】算法6:只有五行的Floyd最短路算法

  

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

  上图中有4个城市8条公路,公路上的数字表示这条公路的长短。请注意这些公路是单向的。我们现在需要求任意两个城市之间的最短路程,也就是求任意两个点之间的最短路径。这个问题这也被称为“多源最短路径”问题。
  现在需要一个数据结构来存储图的信息,我们仍然可以用一个4*4的矩阵(二维数组dist)来存储。比如1号城市到2号城市的路程为2,则设dist[1][2]的值为2。2号城市无法到达4号城市,则设置dist[2][4]的值为∞。另外此处约定一个城市自己是到自己的也是0,例如dist[1][1]为0,具体如下。
 

  现在回到问题:如何求任意两点之间最短路径呢?通过之前的学习我们知道通过深度或广度优先搜索可以求出两点之间的最短路径。所以进行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)的路程dist[4][3]原本是12。如果只通过1号城市中转(4->1->3),路程将缩短为11(dist[4][1]+dist[1][3]=5+6=11)。其实1号城市到3号城市也可以通过2号城市中转,使得1号到3号城市的路程缩短为5(dist[1][2]+dist[2][3]=2+3=5)。所以如果同时经过1号和2号两个城市中转的话,从4号城市到3号城市的路程会进一步缩短为10。通过这个的例子,我们发现每个顶点都有可能使得另外两个顶点之间的路程变短。好,下面我们将这个问题一般化。

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

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

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

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

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

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

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

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

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

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

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

  

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

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

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

  代码:

/*
Author:Mengmeng
Time:2016-6-29 23:53:47
Description:
Floyd-Warshall算法求最短路径。
*/ #include <iostream>
using namespace std; int main(void)
{
int dist[][];//两点之间的距离数组;dist[1][2]表示顶点1到顶点2之间的距离
int inf = ;//用inf(infinity的缩写)存储一个我们认为的正无穷值
int peak_num;//顶点个数
int initPath_num;//初始路径的数量
int p1, p2;//顶点p1,p2
int D;//p1->p2的距离
cout << "读入顶点数peak_num=";
cin >> peak_num;
cout << endl;
cout << "初始路径数initPath_num=";
cin >> initPath_num;
cout << endl; //初始化:本顶点到本顶点路程为0;本顶点到其它顶点为无限大
for (int i = ; i <= peak_num; i++)
{
for (int j = ; j <= peak_num; j++)
{
if (i == j)
dist[i][j] = ;
else
dist[i][j] = inf;
}
} //读入初始路径的路程;格式:顶点p1->p2的距离为D
cout << "读入初始路径的路程,格式:p1 p2 D" << endl;
for (int i = ; i <= initPath_num; i++)
{
cin >> p1 >> p2 >> D;
dist[p1][p2] = D;
} //输出原始的路程
cout << "原始的路程如下:" << endl;
for (int i = ; i <= peak_num; i++)
{
cout << endl;
for (int j = ; j <= peak_num; j++)
{
if (dist[i][j]==inf)
cout << "∞" << " ";
else
cout << dist[i][j] << " ";
}
cout << endl; }
//Floyd-Warshall算法核心语句
for (int k = ; k <= peak_num;k++)
for (int i = ; i <= peak_num;i++)
for (int j = ; j <= peak_num;j++)
if (dist[i][j] > dist[i][k] + dist[k][j])
dist[i][j] = dist[i][k] + dist[k][j]; //输出最终的结果
cout << "Floyd-Warshall算法得到的最短路程如下:" << endl;
for (int i = ; i <= peak_num; i++)
{
for (int j = ; j <= peak_num; j++)
{
cout << dist[i][j] << " ";
}
cout << endl; }
}

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

         //顶点数 初始路径数
//顶点a 顶点b 距离 下同
  第一行两个数为peak_num和initPath_num,peak_num表示顶点个数,initPath_num表示初始路径的条数。
  接下来initPath_num行,每一行有三个数p1、p2 和D,表示顶点p1到顶点p2的路程是D。
 
  程序运行结果:
 
   

  

  用下图来表示最终结果更直观一些,

  

  通过这种方法我们可以求出任意两个点之间最短路径。它的时间复杂度是O(N3)。令人很震撼的是它竟然只有五行代码,实现起来非常容易。正是因为它实现起来非常容易,如果时间复杂度要求不高,使用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年获得了图灵奖。

Floyd最短路算法的更多相关文章

  1. 【啊哈!算法】算法6:只有五行的Floyd最短路算法

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

  2. 【坐在马桶上看算法】算法6:只有五行的Floyd最短路算法

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

  3. 只有五行的Floyd最短路算法

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

  4. 仅仅有五行的Floyd最短路算法

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

  5. [转]坐在马桶上看算法:只有五行的Floyd最短路算法

    此算法由Robert W. Floyd(罗伯特·弗洛伊德)于1962年发表在“Communications of the ACM”上.同年Stephen Warshall(史蒂芬·沃舍尔)也独立发表了 ...

  6. Dijkstra 最短路算法(只能计算出一条最短路径,所有路径用dfs)

    上周我们介绍了神奇的只有五行的 Floyd 最短路算法,它可以方便的求得任意两点的最短路径,这称为"多源最短路".本周来来介绍指定一个点(源点)到其余各个顶点的最短路径,也叫做&q ...

  7. Dijkstra最短路算法

    Dijkstra最短路算法 --转自啊哈磊[坐在马桶上看算法]算法7:Dijkstra最短路算法 上节我们介绍了神奇的只有五行的Floyd最短路算法,它可以方便的求得任意两点的最短路径,这称为“多源最 ...

  8. 【啊哈!算法】算法7:Dijkstra最短路算法

    上周我们介绍了神奇的只有五行的Floyd最短路算法,它可以方便的求得任意两点的最短路径,这称为“多源最短路”.本周来来介绍指定一个点(源点)到其余各个顶点的最短路径,也叫做“单源最短路径”.例如求下图 ...

  9. 【坐在马桶上看算法】算法7:Dijkstra最短路算法

           上周我们介绍了神奇的只有五行的Floyd最短路算法,它可以方便的求得任意两点的最短路径,这称为“多源最短路”.本周来来介绍指定一个点(源点)到其余各个顶点的最短路径,也叫做“单源最短路径 ...

随机推荐

  1. 可以开心的用Markdown了

    1 计划 月计划 周计划 日计划 2 实现

  2. Hbase Shell常用命令

    hbase shell常用的操作命令有create,describe,disable,drop,list,scan,put,get,delete,deleteall,count,status等,通过h ...

  3. Apache配置默认首页

    操作系统:CentOS 6.5 Apache默认主页为index.html,如果要修改为index.php或其它,需要修改httpd.conf文件 用vim或其它编辑器打开httpd.conf 在上图 ...

  4. varnish4.0简介

    Varnish 4.0 简介 Varnish 是一款开源的HTTP加速器和反向代理服务器,它的主要特点有: (1)是基于内存缓存,重启后数据将消失.(2)利用虚拟内存方式,io性能好.(3)支持设置0 ...

  5. DetachedCriteria详细使用

    一.基本使用 1. 说明 Restrictions 是产生查询条件的工具类. 2. 定义 可以直接用class 创建 DetachedCriteria searDc = DetachedCriteri ...

  6. glibc 简介:

    glibc 编辑 glibc是GNU发布的libc库,即c运行库.glibc是linux系统中最底层的api,几乎其它任何运行库都会依赖于glibc.glibc除了封装linux操作系统所提供的系统服 ...

  7. 利用SecureCRT上传、下载文件(使用sz与rz命令)

    sz用法: 下载一个文件 sz filename 下载多个文件 sz filename1 filename2 下载dir目录下的所有文件,不包含dir下的文件夹 sz dir/* 下载文件存放位置在s ...

  8. JavaScript简易缩放程序

    一.前言: 上一篇随笔中已经把拖动程序完成了,这篇主要把缩放程序完成,后面合并后可以做成一个图片裁剪的功能 简易缩放程序DEMO:http://jsfiddle.net/UN99R/ 限制缩放程序DE ...

  9. C语言中的struct和typedef struct<转载>

    原文:http://www.nowamagic.net/librarys/veda/detail/1785 typedef为C语言的关键字,作用是为一种数据类型定义一个新名字.这里的数据类型包括内部数 ...

  10. thinkphp中F方法

    我们已经了解了ThinkPHP中的S方法的用法,F方法其实是S方法的一个子集功能,仅用于简单数据缓存,并且只能支持文件形式,不支持缓存有效期,因为采用的是PHP返回方式,所以其效率较S方法较高,因此我 ...