图的搜索(二):贝尔曼-福特算法、狄克斯特拉算法和A*算法

2023-12-12 10:12

本文主要是介绍图的搜索(二):贝尔曼-福特算法、狄克斯特拉算法和A*算法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

图的搜索(二):贝尔曼-福特算法、狄克斯特拉算法和A*算法

贝尔曼-福特算法

贝尔曼-福特(Bellman-Ford)算法是一种在图中求解最短路径问题的算法。最短路径问题就是在加权图指定了起点和终点的前提下,寻找从起点到终点的路径中权重总和最小的那条路径。

在这里插入图片描述

设置A为起点,G为终点。

在这里插入图片描述

首先设置各个顶点的初始权重 :起点为 0,其他顶点为无穷大(∞)。这个权重表示的是从 A 到该顶点的最短路径的暂定距离。随着计算往下进行,这个值会变得越来越小,最终收敛到正确的数值。

在这里插入图片描述

选中候补顶点,分别计算这条边从一端到另一端的权重,计算方法为:“顶点原本的权重+边的权重”

在这里插入图片描述

计算权重,如果计算结果小于顶点的值,就更新这个值。

如图,计算A到B的权重:顶点 B 的权重是无穷大,比 9 大,所以把它更新为 9。更新时需要记录计算的是从哪个顶点到该顶点的路径。

再次计算B到A的权重:B 的权重为 9,从 B 到 A 的权重便为 9+9=18。与顶点 A 现在的值 0 进行比较,因为现在的值更小,所以不更新。

A到C的路径计算同理。接下来计算B到C的路径。

在这里插入图片描述

在进行B-C计算时,发现A-C-B的路径比A-B的路径更短,于是更新如下:

在这里插入图片描述

接着对所有的边进行更新操作

在这里插入图片描述

更新完所有的边后,第 1 轮更新就结束了。接着,重复对所有边的更新操作,直到权重不能被更新为止。

在这里插入图片描述

第二轮更新后,顶点 B 的权重从 8 变成了 7,顶点 E 的权重从 9 变成了 8。接着进行第三轮更新。发现第三轮更新后,所有顶点的权重不再更新,操作结束。算法的搜索流程也就此结束,我们找到了从起点到其余各个顶点的最短路径。

在这里插入图片描述

根据搜索结果可知,从起点 A 到终点 G 的最短路径是 A-C-D-F-G,权重为 14。

将图的顶点数设为 n、边数设为 m。该算法经过 n 轮更新操作后就会停止,而在每轮更新操作中都需要对各个边进行 1 次确认,因此 1 轮更新所花费的时间就是 O(m),整体的时间复杂度就是 O(nm)。

有向图与以上步骤相同,只需按照边所指向的方向来计算即可。

计算最短路径时,边的权重代表的通常都是时间、距离或者路费等,因此基本都是非负数。不过,即便权重为负,贝尔曼 - 福特算法也可以正常运行。

如果闭环中有负数权重,就不存在最短路径。

狄克斯特拉算法

狄克斯特拉( Dijkstra)算法也是求解最短路径问题的算法,使用它可以求得从起点到终点的路径中权重总和最小的那条路径。

在这里插入图片描述

仍然设A为起点,G为终点。

在这里插入图片描述

与贝尔曼-福特算法相同,将起点设置为0,其他顶点设置为无穷大。设置从A出发,寻找可以从目前所在的顶点直达且尚未被搜索过的顶点,此处为顶点 B 和顶点 C,将它们设为下一步的候补顶点。

在这里插入图片描述

计算后结果如上图。计算方法是“目前所在顶点的权重+目前所在顶点到候补顶点的权重”。与贝尔曼-福特算法类似。

在这里插入图片描述

**从候补顶点中选出权重最小的顶点。**此处 B 的权重最小,那么路径 A-B 就是从起点 A 到顶点 B 的最短路径。确定了最短路径,移动到顶点B。

在这里插入图片描述

将可以从顶点B直达的顶点设为新的候补顶点,于是顶点 D 和顶点 E 也成为了候补。目前有三个候补顶点 C、D、E。

在这里插入图片描述

同理。在计算B到各顶点值后,比较各点值大小。其中B-C点的权重为8>5,所以不更新。确认了最短路径,移动到顶点D。计算D-E的权重为7>5,发现并不需要更新它。现在,有两个候补顶点(C和E)权重均为5,选择哪一个向下计算都可以。以下先选择C。

在这里插入图片描述

算出F点的权重后,回到E进行计算。

在这里插入图片描述

此时算出G点的权重为14。再次回到F,对F-G的权重进行计算得20>14。故G的最小权重为14。

在这里插入图片描述

最终得到的这颗橙色的树就 是最短路径树,它表示了起点到达各个顶点的最短路径。

比起需要对所有的边都重复计算权重和更新权重的贝尔曼 - 福特算法,狄克斯特拉算法多了一步选择顶点的操作,这使得它在求最短路径上更为高效。

将图的顶点数设为 n、边数设为 m,那么如果事先不进行任何处理,该算法的时 间复杂度就是 O( n²)。不过,如果对数据结构进行优化,那么时间复杂度就会变为 O(m + nlogn)。

有负数权重时不能使用狄克斯特拉算法

不存在负数权重时,更适合使用效率较高的狄克斯特拉算法,而存 在负数权重时,即便较为耗时,也应该使用可以得到正确答案的贝尔曼 - 福特算法。

A*算法

A*(A-Star)算法也是一种在图中求解最短路径问题的算法,由狄克斯特拉算法发展而来。

狄克斯特拉算法会从离起点近的顶点开始,按顺序求出起点到各个顶点的最短路径。也就是说,一些离终点较远的顶点的最短路径也会被计算出来,但这部分其实是无用的。与之不同,A* 就会预先估算一个值,并利用这个值来省去一些无用的计算。

在这里插入图片描述

先使用狄克斯特拉算法来求解以上迷宫的最短路径。

将迷宫看作是一个图,其中每个方块都是一个顶点,各顶点间的距离(权重)都为 1。

在这里插入图片描述

用狄克斯特拉算法求最短路径的结果会如上图所示,方块中的数字表示从起点到该顶点的距离(权重),蓝色和橙色的方块表示搜索过的区域,橙色方块同时还表示从 S 到 G 的最短路径。

狄克斯特拉算法只根据起点到候补顶点的距离来决定下一个顶点。因此,它无法发现蓝色箭头所指的这两条路径其实离终点越来越远,同 样会继续搜索。

在这里插入图片描述

而A* 算法不仅会考虑从起点到候补顶点的距离, 还会考虑从当前所在顶点到终点的估算距离。 这个估算距离可以自由设定,此处我们用的是将顶点到终点的直线距离四舍五入后的值。

由人工预先设定的估算距离被称为**“距离估算值”。如果事先根据已知信息设定合适的距离估算值,再将它作为启发信息辅助计算,搜索就会变得更加高效。这样的算法也成为启发式算法**。

在这里插入图片描述

从起点开始搜索。分别计算起点周围每个顶点的权重。计算方法是“从起点到该顶点的距离”(方块左下)加上 “距离估算值”(方块右下)。

在这里插入图片描述
选择一个权重最小的顶点,用橙色表示,并继续向后搜索。

在这里插入图片描述

按照顺序继续向下搜索。

在这里插入图片描述

在这里插入图片描述

搜索完毕如上图。可以看出基本不回去计算离终点太远的区域。

如果我们能得到一些启发信息,即各个顶点到终点的大致距离(这个距离不需是准确的值)我们就能使用 A* 算法。当然,有时这类信息是完全无法估算的,这时就不能使用 A* 算法。

距离估算值越接近当前顶点到终点的实际值,A* 算法的搜索效率也就越高;反过来,如果距离估算值与实际值相差较大,那么该算法的效率可能会比狄克斯特拉算法的还要低。如果差距再大一些,甚至可能无法得到正确答案。

不过,当距离估算值小于实际距离时,是一定可以得到正确答案的(只是如果没有设定合适的距离估算值,效率会变差)。

A* 算法在游戏编程中经常被用于计算敌人追赶玩家时的行动路线等,但由于该算法的计算量较大,所以可能会使游戏整体的运行速度变慢。因此在实际编程时,需要考虑结合其他算法,或者根据具体的应用场景做出相应调整。

参考资料:我的第一本算法书 (石田保辉 宮崎修一)

这篇关于图的搜索(二):贝尔曼-福特算法、狄克斯特拉算法和A*算法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/484230

相关文章

openCV中KNN算法的实现

《openCV中KNN算法的实现》KNN算法是一种简单且常用的分类算法,本文主要介绍了openCV中KNN算法的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录KNN算法流程使用OpenCV实现KNNOpenCV 是一个开源的跨平台计算机视觉库,它提供了各

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

如何通过Golang的container/list实现LRU缓存算法

《如何通过Golang的container/list实现LRU缓存算法》文章介绍了Go语言中container/list包实现的双向链表,并探讨了如何使用链表实现LRU缓存,LRU缓存通过维护一个双向... 目录力扣:146. LRU 缓存主要结构 List 和 Element常用方法1. 初始化链表2.

Python使用DeepSeek进行联网搜索功能详解

《Python使用DeepSeek进行联网搜索功能详解》Python作为一种非常流行的编程语言,结合DeepSeek这一高性能的深度学习工具包,可以方便地处理各种深度学习任务,本文将介绍一下如何使用P... 目录一、环境准备与依赖安装二、DeepSeek简介三、联网搜索与数据集准备四、实践示例:图像分类1.

golang字符串匹配算法解读

《golang字符串匹配算法解读》文章介绍了字符串匹配算法的原理,特别是Knuth-Morris-Pratt(KMP)算法,该算法通过构建模式串的前缀表来减少匹配时的不必要的字符比较,从而提高效率,在... 目录简介KMP实现代码总结简介字符串匹配算法主要用于在一个较长的文本串中查找一个较短的字符串(称为

通俗易懂的Java常见限流算法具体实现

《通俗易懂的Java常见限流算法具体实现》:本文主要介绍Java常见限流算法具体实现的相关资料,包括漏桶算法、令牌桶算法、Nginx限流和Redis+Lua限流的实现原理和具体步骤,并比较了它们的... 目录一、漏桶算法1.漏桶算法的思想和原理2.具体实现二、令牌桶算法1.令牌桶算法流程:2.具体实现2.1

Python中的随机森林算法与实战

《Python中的随机森林算法与实战》本文详细介绍了随机森林算法,包括其原理、实现步骤、分类和回归案例,并讨论了其优点和缺点,通过面向对象编程实现了一个简单的随机森林模型,并应用于鸢尾花分类和波士顿房... 目录1、随机森林算法概述2、随机森林的原理3、实现步骤4、分类案例:使用随机森林预测鸢尾花品种4.1

C# ComboBox下拉框实现搜索方式

《C#ComboBox下拉框实现搜索方式》文章介绍了如何在加载窗口时实现一个功能,并在ComboBox下拉框中添加键盘事件以实现搜索功能,由于数据不方便公开,作者表示理解并希望得到大家的指教... 目录C# ComboBox下拉框实现搜索步骤一步骤二步骤三总结C# ComboBox下拉框实现搜索步骤一这