弗洛伊德算法求出最短距离_伟大的Dijkstra算法! (算法图解第三弹)

在介绍这个算法之前,我们一定要说提一下这个伟大的算法的发明人,

Edsger Wybe Dijkstra

1e882ec47ba148002456c05ed80a3ebd.png

这个巨佬中的巨佬可是相当相当的牛逼,

曾在1972年获得过素有计算机科学界的诺贝尔奖之称的图灵奖

与D. E. Knuth并称为我们这个时代最伟大的计算机科学家。

听说这个巨佬中的巨佬曾经是研究数学和物理的,

好奇的我去看了看他的一些研究成果?,

果然,不出我所料,身为一条vegetable dog的我连那些名称都看不懂。。。

970461f129b204bbead275e98a1d90f3.png

介绍完后回到Dijkstra算法,

这个算法可是属于真正的老经典算法了

这学期离散数学里学到的唯一一个算法也就是这个Dijkstra算法,

这个算法实际上就有贪心算法的味道在里面,

即每次都确定一个顶点的最优路径,直到遍历全图。

由于本人水平真的过低,这学期在离散数学中学这个算法的时候真的是花了很多很多时间才弄明白算法原理,曾一度怀疑人生并且无数次想要撕碎这个课本

e9367494676902b838cd2c070647a750.png

我真的搞不懂明明很简单的一个算法,为什么放到教科书上面会给花里胡哨地加上一些专有名词,然后变得极度抽象,我是真的是很无语。。。

个人感觉我们学校订的教科书编的不是很好(纯粹个人感觉)

这边我尽量用最简洁易懂的方式,将这个算法表示清楚。因为我太清楚深受教科书毒害的学生们的感受了。。

首先,我们要明白,这个算法到底有什么用

学一个新东西,要么是知道它有什么用——功利化学习

要么就是很喜欢它,就是想学——去功利化

当然如果你能做到二者兼顾,那最好了

这个算法,是求最短路径的,也就是求一个点到另一个点的最短路径,

这里的最短路径指的是权值大于等于0的路,为什么是这样,这个我会在后文说到。

至于为什么是带权,是为了与BFS广度优先搜索算法区分,BFS也是求最短路径,但是求的是不带权的路,也就是段数最少的路径。

在这之后,我们就可以开始学习这个算法了,

这个算法的核心思想完全可以用一句话概括,

每次都确定初始点到一个顶点的最短路径,直到遍历全部顶点”。

因为如果直接阐述思想,会过于抽象,难以理解,

因此我这边就打算结合例子解释。

这是我从书上找到的一个比较好的例子,这个例子弄懂了基本上这个算法也就掌握了。

c1f937e04a43e11fde6a10559a5716bd.png

首先,我们要明确我们的目标,我们要用Dijkstra算法算出b点到图上所有点的最短路径。

在明确了目标之后,我们要先定义一个map cost,存储从初始点点到各点的当前最短距离(当然这里不一定要用map,思想一样就可以,我这里用map是为了更好地理解),

然后就是让计算机存储图的信息,这里我选择用map > graph

是不是感觉这里有点复杂。。其实完全不是的,这里完全就可以看成一个二维数组,a[x][y],里面存储的值是x到y的距离,

再然后就是我们需要再定义一个map parents,存储最短路径上一个顶点的前一个顶点

也就是存储父节点,用来进行回溯,这样才能把最短路径经过的节点给一一求出来,

如果你不需要求最短路径经过的节点,这个就不需要定义了,

当然,最好还是要求出来,这样才能让这个算法更加elegant。

最后就是定义一个vector< char > over,用来存储已经确定好最短路径的顶点,

这是Dijkstra算法思想中最关键的一环,

就是你每次确定好的那个最短路径的顶点,它的最短路径到程序结束都不会再改变了,

因此将其放到vector中,避免再对它进行操作,

做好了上述三步,就可以开始我们的算法了。

根据上述三步,我们一开始确定的cost散列,第一列是节点,第二列是初始点到各个节点的当前最短距离,

如下

fbcf1ad7470db479e1f10ff84f254fd7.png

这里切记,一开始我们只能确定b点到b点的距离是0(这是由于程序编写的需要),其他点默认正无穷。

 而我们每一次都需要在cost里面找到一个最短的距离,通过这个最短距离确定一个节点。

然后这就算确定了一个顶点的最短路径,也就是确定了b点到b点的最短路径,

将其加入到over容器中,之后就不再需要求b点到b点的最短路径了

这个是不是在我们人看起来很傻。。

但其实我们的目的是让计算机识图,因此要把计算机当成一个蠢的不能再蠢的人看待。

之后我们就要开始更新这个cost散列表了,这也是Dijkstra算法很关键的一步,

就是由于我们已经确定了到b点的最短路径,因此我们可以搜索b点到各个直接相邻的点的权值

也就是相邻边的长度,

让已经确定的cost中b点的最短路径径加上权值,就能求出来b点到各个相邻点的距离

记住,这里是一个距离,不是最短距离,

如果要是最短距离,一定一定要比较才能得出,

此时我们将这个距离和我们存储在cost散列表里的当前最短距离比较,

如果这个距离比当前最短距离小,就进行更新,

将cost里面对应位置的值变成这个距离,然后把b设定为这个节点的父节点。

在更新完后,我们的cost散列表就变成了这样,

c16d53bc3cb3c0e765a33ba347ee9ee7.png

b节点后面加上*就表示它是已经确定的节点了

也就是已经求出了初始点到这个节点的最短路径了,

之后再在cost里面找,到一个顶点的最短路径,就不再需要考虑它了,切记切记!

之后就非常简单了,就是重复上面的步骤。

再从cost里面找cost里面最小的值代表的节点,

除已经确定的节点以外,找出这个节点,然后重复上面步骤就可以。

这次我们找出的节点是c节点,然后cost散列表更新后就变成

534050449be1f51b28b192d7374a445f.png

之后就是重复到所有顶点后面都加上*后,这个算法就算结束了,

然后cost最终存储的值就是初始点到各个点的最短路径,若想具体求出,通过parents回溯即可。

这里我给出参考书上的解析图,

5e29ffd1f8f1c16f12a72da7998180ea.png

这里面每一行代表一次计算,每一次计算确立一个顶点,找到一条最短路径,

然后括号的第一个参数是初始点到这个点的当前最短距离,也就是cost存储的值,

然后第二个参数就是存储这个节点的父节点,λ表示暂未确定父节点。

最终cost的结果如下,

70e090ade87c7bb73c0ef51483507cf6.png

具体路径如下,

9af8753c5aa07c23c81ad67f37d319a9.png

是不是看到这里感觉这个算法难度还好,不是很难。

但是这个算法有几个很有想法的点,如果你有真正的去思考这个算法,你应该可以感受到。

一, 为什么每次确定下来的那个节点,之后就不需要再需要对它进行更新了?

二, 为什么就能很肯定每次确定下来的节点它对应的cost值就是最终的最短路径?

三, 为什么每次只能确定一个节点?

四, 为什么这个算法无法解决有负权边的情况?

五, 为什么要根据每次确定的节点对cost进行更新?

想清楚了这几个问题,你才能能说真正的懂这个算法,要不然永远只能根据框架去套它,一辈子在门外徘徊。

首先,前四个问题其实是统一的,你只要弄清楚其中任何一个,其他几个就豁然开朗了。

我这边就从第一个问题切入,

首先要明白我们每次计算是如何确定那一个节点的?

是在cost当前最短路径中找到目前最快能到达的节点,也就是目前初始节点到这个节点的距离最短,

而当我们找到了这个节点,我们就当在cost中存储的值是初始节点到这个节点的最短路径,

后续就不用再对这个节点在cost中的值进行更新了。

这是为什么?

就是因为我们伟大的Dijkstra算法只应用于权值大于等于0的图,

回到这个问题的一开始,我们每次要确定的节点是在cost当前最短路径中找到目前最快能到达的节点,

如果我之后又能找到其他到这个节点的路径,

那么我能找到的到这个节点的除cost中的最短路径的路径必然是cost中的未确定节点中除这个节点以外的节点对应的值再加上一个连接这俩个节点的边的权值

为什么这里说是cost中的未确定的节点,因为未确定的节点已经更新过了,我们不用再管它,

若是你读了后面问题五的解释,你再回过头来仔细想想,就能明白,

其实经过那些已经确定的节点的最短路径加上一条连接这俩个节点的边得到的值我们已经求过了!,

而我们这次确定的节点中cost的值已经是除未确定节点中最小的了,其他节点再加上权值大于等于0的边,必然会比这个值大的!

这里我要举个例子,因为这个是比较难理解的点,空讲的话就会有点抽象。

b69ee02a15e27959bf40cf1696118480.png

这里我定义的初始节点是家,根据算法,我第一次确定的节点是家,然后第二次确定的节点就是学校,

也就是说到学校的最短路径是2,

那有没有可能将到学校的路径缩短到少于2呢?

答案是完全不可能,

因为你到除家以外的其他节点已经要花比到这个节点更长的距离了,再加上大于等于0的权边,就完全不可能实现了,

因此我上面说的一大段话就是说明这个道理。

至于最后的第五个问题,为什么要根据每次确定的节点对cost进行更新,

这是因为,一个节点的最短路径必然是与它相邻节点的最短路径加上相邻边之后得到的

我们确定的这个节点是我们已经知道了它的最短路径,

然后我们根据这个最短路径进行扩展,试图根据这个最短路径扩展到其他节点,得到到其他节点的路径

但是这个不一定是最短路径,这是很容易犯的错误,我之前也懵逼了很久,

因为原先cost里面存的那个值也是与它相邻节点的最短路径加上相邻边之后得到的,

根据上面我对问题一的解释,我们每次最终确定下来的节点它对应的cost值就是最终的最短路径,

然后我们在确定了一个节点之后就需要确定下一个节点,直到所有节点都被确定,

那么,我们就需要时刻保持cost中存储的是初始节点到各个节点的当前最短路径,

而每当我们确定下一个节点之后,我们就可以利用与这个节点相邻的边的信息,从而得到如上面所说的到其他节点的路径长度,

而将其求出来的路径长度和我们cost里面存储的路径长度比较,更短的那个就是当前最短路径。

因此,从这里可以看出,根据每次确定的节点对cost进行更新的很大一部分原因就是在合理利用所有边的信息的情况下,时刻保持cost中存储的是初始节点到各个节点的当前最短路径。

因为当前最短路径只有俩种可能

一是根据上一个已经确定的节点进行扩展得到的路径,

二是之前已经存储到cost中的当前最短路径——这个是根据上一个之前已经确定的节点进行扩展得到的路径。

一个节点的最短路径必然是它与相邻节点的最短路径加上相邻边之后得到的,这个是最最最最重要的!

如果不是根据确定的节点对cost进行更新的话,那么我们就会将第一个情况变成根据未确定的节点进行扩展得到的路径,

而到未确定的节点的最短路径我们还没有求出来,也就是得到了一条很有可能不是由最短路径加上相邻边得到的路径,

这就会导致算法出错了,使得我们有可能漏掉更短的最短路径,

从而有可能让cost散列表没有处于“时刻存储的是当前最短路径”的情况,最终让算法执行失败。

额,可能是我的水平实在过低,我原本的想法是尽可能简洁易懂,

但是一不小心还是打了这么多字才把我认为核心的地方讲完,可能在某些人看来是啰里啰唆搞了半天吧。。。

不过我还是很开心的,因为我在解释这五个问题的同时,也对我之前学习的知识进行重塑,也让我对这个算法有了进一步的认识,

很多之前没有想清楚的地方也都豁然开朗了,果然,学无止境这个成语真的很有道理。。。

852147a558a8c54904921cd718b2628c.png

真正弄懂了这个算法的思想之后,剩下的就是相对比较简单的代码实现了。我这边就直接给出代码,

c1e66758046466ea600d70435a8a1983.png 6ab2c60462ea336891745145d3f79756.png e437b03869886de15f11e1a8f75a070a.png

这个代码我是根据这道题目打下来的,

主要是为了实现这个算法的代码找的这道题目,

不过我没有仔细做这道题?,只是利用它实现了Dijkstra算法。

5855: 数据结构实验:最短路

时间限制(普通/Java):1000MS/3000MS     内存限制:65536KByte

总提交: 177            测试通过:26

描述

给定n个点的带权有向图,若从顶点x到顶点y之间存在一条路径,那么这条路径的长度定义为路径上各条边的权值之积。

现在请你求出从顶点1到顶点n的最短路径。

输入

第一行为两个正整数n和m(n<=1000,m<=5000),n表示顶点数,m表示边数。

接下来有m行,每行三个正整数x,y,w,表示顶点x到y有一条边权为w的边。

1<=x, y<=n,w不大于10000。

两个顶点之间可能存在多条边。

输出

输出题目定义的最短路径值,由于数可能很大,因此你只需要输出总共有几位数即可。

如果不存在路径,则输出Sorry。

样例输入

3 3

1 2 3

2 3 3

1 3 11

样例输出

1

提示

最短路径为9,1位,因此输出1。

终于把这个算法搞好了,前面几天还是太lang了,终究还是没有达到我预想中的进度,

最近几天要处理一些事情,也逐渐开始忙起来了,

也不知道这个暑假能不能把《算法图解》上面的算法给过一遍,

我之前还天真地想着在看完《算法图解》后开启我的Maya之路,走上人生巅峰,

果然作为小菜狗?的我还是太嫩了

既然人算不如天算,那就只能尽力而为了!

3a611ee5826dc7dfdce7fb686b5ce880.png

-END-

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值