Floyd算法

1.定义概览

Floyd-Warshall算法(Floyd-Warshall algorithm)是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题,同时也被用于计算有向图的传递闭包。Floyd-Warshall算法的时间复杂度为O(N3),空间复杂度为O(N2)。

2.算法描述

1)算法思想原理:

Floyd算法是一个经典的动态规划算法。用通俗的语言来描述的话,首先我们的目标是寻找从点i到点j的最短路径。从动态规划的角度看问题,我们需要为这个目标重新做一个诠释(这个诠释正是动态规划最富创造力的精华所在)

从任意节点i到任意节点j的最短路径不外乎2种可能,1是直接从i到j,2是从i经过若干个节点k到j。所以,我们假设Dis(i,j)为节点u到节点v的最短路径的距离,对于每一个节点k,我们检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,我们便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。

2).算法描述:

a.从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。   

b.对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比己知的路径更短。如果是更新它。

3).Floyd算法过程矩阵的计算----十字交叉法

方法:两条线,从左上角开始计算一直到右下角 如下所示

给出矩阵,其中矩阵A是邻接矩阵,而矩阵Path记录u,v两点之间最短路径所必须经过的点

相应计算方法如下:

最后A3即为所求结果

3.算法代码实现

 1 typedef struct
 2 {
 3     char vertex[VertexNum];                                //顶点表
 4     int edges[VertexNum][VertexNum];                       //邻接矩阵,可看做边表
 5     int n,e;                                               //图中当前的顶点数和边数
 6 }MGraph;
 7
 8 void Floyd(MGraph g)
 9 {
10    int A[MAXV][MAXV];
11    int path[MAXV][MAXV];
12    int i,j,k,n=g.n;
13    for(i=0;i<n;i++)
14       for(j=0;j<n;j++)
15       {   
16              A[i][j]=g.edges[i][j];
17             path[i][j]=-1;
18        }
19    for(k=0;k<n;k++)
20    {
21         for(i=0;i<n;i++)
22            for(j=0;j<n;j++)
23                if(A[i][j]>(A[i][k]+A[k][j]))
24                {
25                      A[i][j]=A[i][k]+A[k][j];
26                      path[i][j]=k;
27                 }
28      }
29 }

算法时间复杂度:O(n3)

对上面代码还可以进一步优化?答案是肯定的,不信你看下面这个小故事告诉你诀窍:

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

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

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

1 for(i=1;i<=n;i++)
2 {
3     for(j=1;j<=n;j++)
4     {
5         if ( e[i][j] > e[i][1]+e[1][j] )
6               e[i][j] = e[i][1]+e[1][j];
7     }
8 }

在只允许经过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 //经过1号顶点
2 for(i=1;i<=n;i++)
3     for(j=1;j<=n;j++)
4         if (e[i][j] > e[i][1]+e[1][j])  e[i][j]=e[i][1]+e[1][j];
5 //经过2号顶点
6 for(i=1;i<=n;i++)
7     for(j=1;j<=n;j++)
8         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][3]和e[4][3]的路程变得更短了。
       同理,继续在只允许经过1、2和3号顶点进行中转的情况下,求任意两点之间的最短路程。任意两点之间的最短路程更新为:
       最后允许通过所有顶点作为中转,任意两点之间最终的最短路程为:

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

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

这段代码的基本思想就是:最开始只允许经过1号顶点进行中转,接下来只允许经过1和2号顶点进行中转……允许经过1~n号所有顶点进行中转,求任意两点之间的最短路程。用一句话概括就是:从i号顶点到j号顶点只经过前k号点的最短路程。其实这是一种“动态规划”的思想!

动态转移方程:dp[k][i][j]=min(dp[k-1][i][j],dp[k-1][i][k]+dp[k-1][k][j]);

下面给出这个算法的完整代码:

 1 #include <stdio.h>
 2 int main()
 3 {
 4     int e[10][10],k,i,j,n,m,t1,t2,t3;
 5     int inf=99999999; //用inf(infinity的缩写)存储一个我们认为的正无穷值
 6     //读入n和m,n表示顶点个数,m表示边的条数
 7     scanf("%d %d",&n,&m);
 8
 9     //初始化
10     for(i=1;i<=n;i++)
11         for(j=1;j<=n;j++)
12             if(i==j) e[i][j]=0;
13               else e[i][j]=inf;
14     //读入边
15     for(i=1;i<=m;i++)
16     {
17         scanf("%d %d %d",&t1,&t2,&t3);
18         e[t1][t2]=t3;
19     }
20
21     //Floyd-Warshall算法核心语句
22     for(k=1;k<=n;k++)
23         for(i=1;i<=n;i++)
24             for(j=1;j<=n;j++)
25                 if(e[i][j]>e[i][k]+e[k][j] )
26                     e[i][j]=e[i][k]+e[k][j];
27
28     //输出最终的结果
29     for(i=1;i<=n;i++)
30     {
31      for(j=1;j<=n;j++)
32         {
33             printf("%10d",e[i][j]);
34         }
35         printf("\n");
36     }
37
38     return 0;
39 }

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

1 //Floyd-Warshall算法核心语句
2 for(k=1;k<=n;k++)
3   for(i=1;i<=n;i++)
4       for(j=1;j<=n;j++)
5         if(e[i][k]<inf && e[k][j]<inf && e[i][j]>e[i][k]+e[k][j])
6             e[i][j]=e[i][k]+e[k][j];

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

1 4 8
2 1 2 2
3 1 3 6
4 1 4 4
5 2 3 3
6 3 1 7
7 3 4 1
8 4 1 5
9 4 3 12

第一行两个数为n和m,n表示顶点个数,m表示边的条数。

接下来m行,每一行有三个数t1、t2 和t3,表示顶点t1到顶点t2的路程是t3。
得到最终结果如下:
      通过这种方法我们可以求出任意两个点之间最短路径。它的时间复杂度是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年获得了图灵奖。

转载于:https://www.cnblogs.com/ECJTUACM-873284962/p/6995648.html

最短路径Floyd算法【图文详解】相关推荐

  1. Dijkstra算法图文详解和C++代码

    文章目录 1 Dijkstra算法基本原理 2 算法过程图解1(有向图) 3 算法过程图解2(无向图) 4 C++代码 4.1 案例1代码 4.2 案例2邻接矩阵定义 4.3 案例2代码Dijkstr ...

  2. KMP算法图文详解(为什么是next[0]=-1、next[j]=k和k=next[k])

    文章目录 一:KMP算法解决的问题 二:详解KMP (1)暴力匹配的缺点 (2)最长相同前缀和后缀 (3)究竟怎么回溯 (3)next数组 (4)求解next数组 A:next[0]=-1 B:nex ...

  3. Dijkstra算法图文详解

    Dijkstra算法算是贪心思想实现的,首先把起点到所有点的距离存下来找个最短的,然后松弛一次再找出最短的,所谓的松弛操作就是,遍历一遍看通过刚刚找到的距离最短的点作为中转站会不会更近,如果更近了就更 ...

  4. 归并求逆序对算法图文详解

    给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量. 逆序对的定义如下:对于数列的第 i 个和第 j 个元素,如果满足 i<j 且 a[i]>a[j],则其为一个逆序对:否则不是 ...

  5. 七大排序算法—图文详解(插入排序,希尔排序,选择排序,堆排序,冒泡排序,快速排序,归并排序)

    作者:渴望力量的土狗 博客主页:渴望力量的土狗的博客主页 专栏:数据结构与算法 工欲善其事必先利其器,给大家介绍一款超牛的斩获大厂offer利器--牛客网 点击免费注册和我一起刷题吧 目录 插入排序: ...

  6. 图文详解两种算法:深度优先遍历(DFS)和广度优先遍历(BFS)

    图文详解两种算法:深度优先遍历(DFS)和广度优先遍历(BFS) 阅读本文前,请确保你已经掌握了递归.栈和队列的基本知识,如想掌握搜索的代码实现,请确保你能够用代码实现栈和队列的基本操作. 深度优先遍 ...

  7. c++数据结构中 顺序队列的队首队尾_数据结构与算法—队列图文详解

    前言 栈和队列是一对好兄弟,前面我们介绍过数据结构与算法-栈详解,那么栈的机制相对简单,后入先出,就像进入一个狭小的山洞,山洞只有一个出口,只能后进先出(在外面的先出去).而队列就好比是一个隧道,后面 ...

  8. CRF(条件随机场)与Viterbi(维特比)算法原理详解

    摘自:https://mp.weixin.qq.com/s/GXbFxlExDtjtQe-OPwfokA https://www.cnblogs.com/zhibei/p/9391014.html C ...

  9. SSL之CA证书颁发机构安装图文详解

    上一节我们说到,在验证公钥安全性时,是在CA机构颁发的包含用户的公钥及其身份信息的数字证书,数字证书由权威机构--CA签发.这个CA权威机构可以是自己的服务器也可以是国际公认的CA权威机构.下面我就来 ...

  10. qt on android qml,Qt on Android: Qt Quick 之 Hello World 图文详解

    在上一篇文章,<Qt on Android:QML 语言基础>中,我们介绍了 QML 语言的语法,在最后我们遗留了一些问题没有展开,这篇呢,我们就正式开始撰写 Qt Quick 程序,而那 ...

最新文章

  1. centos mysql安装_mysql yum源安装
  2. 这次性能优化, QPS 翻倍了
  3. php将多个页面写在一个页面,php – 将多个标签添加到WooCommerce单个产品页面
  4. Docker快速搭建Oracle12c
  5. 函数 -- 1.模块导入 2.ATM架构 # 14
  6. GoF著作中未提到的设计模式(4):Double Dispatch
  7. 【算法】BloomFilter 与 CuckooFilter
  8. Atmel与ARM合力打造物联网开发平台
  9. 《采访中收集程序猿》学习记录5
  10. Nginx的accept_mutex配置分析
  11. 《别输在不会表达上》— 综合素质提升书籍
  12. 【转】Android Fragment中使用SurfaceView切换时闪一下黑屏的解决办法
  13. docker安装redis提示没有日记写入权限_Docker 从入门到掉坑
  14. 终端出厂后自动化运维方案
  15. 我的世界服务器logo在线制作软件,[树形图制作者]Chestcommands and BossShop首家支持图标显示的编辑器...
  16. jsp超市仓库管理系统myeclipse开发sqlserver数据库
  17. 七日杀服务器无限刷空投,七日杀空投作弊代码 | 手游网游页游攻略大全
  18. requests.exceptions.SSLError: HTTPSConnectionPool(host=‘edith.xiaohongshu.com‘, port=443): Max retri
  19. 【SPI】SPI学习之SPI硬件相关
  20. 《黑客与画家》读书笔记(四)

热门文章

  1. 从福彩官网抓取 双色球历史数据
  2. Android 播放器 mov,Android 调用系统播放器
  3. error: [debug/qrc_resource.cpp] Error 1
  4. Amazon软件开发工程师面试题
  5. ESP8266 - 首次使用点灯科技
  6. 【重磅】聚焦券商终端业务,博睿数据发布新一代券商终端核心业务体验可观测平台
  7. 自动驾驶的疑点重重, 再次印证了科技的「非理性繁荣」
  8. 飞信登录时SIPC认证失败解决
  9. Usability Testing Demystified
  10. crossover程序错误_如何使用Crossover在Linux上安装Windows应用程序