0
点赞
收藏
分享

微信扫一扫

算法基础14 —— 图论入门、最短路算法(Floyed + Dijkstra + Bellman-Ford + SPFA)

花姐的职场人生 2022-02-10 阅读 48

入门概念

  • 带权图:如下图所示,我们把边带有权值的图称为带权图
  • 可以将边的权值理解为两点之间的距离
  • 一张图中任意两点间会有不同的路径相连
  • 最短路径:最短路径就是指连接两点的这些路径中最短的一条
    在这里插入图片描述

Floyed、Dijkstra、Bellman-Ford、SPFA可以有效地解决最短路径问题。
需要注意的是:边的权值可以为负。当出现负边权时,有些算法不适用
最短路径问题是图论中典型的问题,多数模型可以归结为一下三种

  • 管道铺设
  • 线路安装
  • 地图规划

问题引入

国庆期间,7535寝室计划去旅行。在出发前,寝室长查阅了地图,如下图,寝室长希望在出发前知道任意两个城市之间的最短路径。
在这里插入图片描述
那么,如何求解任意两点之间的最短路径呢?

Floyed算法

分析:如果要让任意两点之间(假设是a到b)的路程变短,只能引入第三个点(假设为k),通过点k进行中转即a -> k -> b,才可能缩短原来从顶点a到顶点b的路程。
有时候可能还不只一个中转点,而是经过两个点或者更多的点进行中转会更短,即a -> k1 -> k2 -> b或者a -> k1 -> k2 -> … …-> b。
例如:上图中的4号城市到3号城市的路程原本是e[4][3] = 12,但是如果通过1号城市中转4 -> 1 -> 3,那么路程将缩短为11(e[4][1] + e[1][3] = 5 + 6 = 11)。如果同时通过1号和2号两个城市中转的话,从4号城市到3号城市的路程会进一步缩短为10(e[4][1] + e[1][2] + e[2][3] = 5 + 2 + 3 = 10)。通过以上分析,我们发现每个顶点都有可能使得另外两个顶点之间的路程变短。
模拟该过程:
首先,如何存储这个地图?
可以用一个二维数组e来存储。比如1号城市到2号城市的路程为2,则设e[1][2] = 2。2号城市无法直接到达4号城市, 则设e[2][4] = ∞。另外,约定自己到自己的路程为0。最终,得到表示两个城市之间距离的二维数组:

在这里插入图片描述
假设现在只允许经过1号顶点中转,求任意两点之间(i到j)的最短路径。
只需要判定经过1号顶点中转的距离e[i][1] + e[1][j]是否比 i 到 j 的初始距离e[i][j]小。代码如下:

//经过1号顶点中转
for(i = 1;i <= n;i++)
{
  for(j = 1;j <= n;j++)
  {
  	   //if (i到j的初始距离e[i][j] > 经过1号顶点中转的距离) 更新
       if(e[i][j] > e[i][1] + e[1][j])
          e[i][j] = e[i][1] + e[1][j];
  }
}

在只经过1号中转的情况下,任意两点之间的最短路径更新为下图:

在这里插入图片描述

既然可以允许经过1号结点中转,那么是否可以允许经过2号结点中转?
显然是可以的。
接下来,加入允许2号结点作为中转结点的情况下任意两点之间的最短路径。
怎么求?我们需要在只允许经过1号顶点时任意两点的最短路径的结果下,再判断如果经过2号顶点是否可以使得 i 号顶点到 j 号顶点之间的路程变得更短,即判断经过2号结点之后的路程e[i][2] + e[2][j]是否比不经过2号结点的路程e[i][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号,2号,3号,4号四个顶点的情况下,求任意两点之间的最短路径,代码如下:

//经过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];//经过1号结点中转之后距离变小,更新
    }
//经过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];//经过2号结点中转之后距离变小,更新
    }
//经过3号顶点中转
for(i = 1;i <= n;i++)
    for(j = 1;j <= n;j++)
    {
        if(e[i][j] > e[i][3] + e[3][j])
          e[i][j] = e[i][3] + e[3][j];//经过3号结点中转之后距离变小,更新
    }  
//经过4号顶点中转
for(i = 1;i <= n;i++)
    for(j = 1;j <= n;j++)
    {
        if(e[i][j] > e[i][4] + e[4][j])
          e[i][j] = e[i][4] + e[4][j];//经过4号结点中转之后距离变小,更新
    }

以上过程较繁琐,我们可以将以上过程写成三个for循环,代码如下:

for(k = 1;k <= n;k++)  //依次经过1~n中的n个点进行中转
    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];
        }

需要强调的是:用来循环中间点的变量k必须放在最外面一层循环

最终,得到任意两个结点之间的最短路径,如图所示:

在这里插入图片描述

Floyed算法总结:
Floyed算法可以算出任意两点的最短路径,可以处理带有负权边的图,但不能处理带有“负环”的图
什么是负环?如图所示:
在这里插入图片描述
e[1][1]本应等于0,如果采用Floyed算法更新最短路,e[1][1] = 2 + 3 - 6 = -1

Floyed算法的时间复杂度:O(n^3)

举报

相关推荐

0 条评论