Dijkstra 算法2025 年 3 月 17 日 | 阅读 28 分钟 以下教程将教我们 Dijkstra 最短路径算法。我们将通过逐步的图形化解释来理解 Dijkstra 算法的工作原理。 我们将涵盖以下内容:
那么,让我们开始吧。 图的简要介绍图是非线性数据结构,表示元素之间的“连接”。这些元素称为顶点,连接图中任意两个顶点的线或弧称为边。更正式地讲,图由一组顶点 (V) 和一组边 (E) 组成。图表示为 G(V, E)。 图的组成部分
下图显示了图的图形表示 ![]() 图 1:图的图形表示 在上图中,顶点/节点用彩色圆圈表示,边用连接节点的线表示。 图的应用图用于解决许多现实生活中的问题。图用于表示网络。这些网络可能包括电话或电路网络,或城市中的路径。 例如,我们可以使用图来设计交通网络模型,其中顶点显示发送或接收产品的设施,边表示连接它们的道路或路径。以下是相同的图形表示 ![]() 图 2:交通网络的图形表示 图也用于不同的社交媒体平台,如 LinkedIn、Facebook、Twitter 等。例如,Facebook 等平台使用图来存储用户数据,其中每个人都用一个顶点表示,每个人都是一个包含个人 ID、姓名、性别、地址等信息的结构。 图的类型图可以分为两种类型
无向图:边没有方向的图称为无向图。该图的边表示双向关系,其中每条边都可以双向遍历。下图显示了一个简单的无向图,它有四个节点和五条边。 ![]() 图 3:一个简单的无向图 有向图:边有方向的图称为有向图。该图的边表示单向关系,其中每条边只能单向遍历。下图显示了一个简单的有向图,它有四个节点和五条边。 ![]() 图 4:一个简单的有向图 图中边的绝对长度、位置或方向通常没有意义。换句话说,我们可以通过重新排列顶点或扭曲边来以不同的方式可视化同一图,只要图的底层结构不变。 什么是加权图?如果每条边都分配了一个“权重”,则称图是加权的。边的权重可以表示距离、时间或任何能模拟它连接的两个顶点之间“连接”的事物。 例如,在下面的加权图图中,我们可以观察到每条边旁边都有一个蓝色数字。这个数字用于表示相应边的权重。 ![]() 图 5:加权图示例 Dijkstra 算法简介现在我们已经了解了一些基本的图概念,让我们深入理解 Dijkstra 算法的概念。 有没有想过 Google 地图是如何找到两个地点之间最短最快路径的? 答案是 Dijkstra 算法。Dijkstra 算法是一种图算法,用于查找从源顶点到图中所有其他顶点的最短路径(单源最短路径)。它是一种贪婪算法,仅适用于具有正权重的加权图。使用图的邻接矩阵表示,Dijkstra 算法的时间复杂度为 O(V2)。使用图的邻接列表表示,时间复杂度可以降低到 O((V + E) log V),其中 V 是顶点数,E 是图中的边数。 Dijkstra 算法的历史Dijkstra 算法由荷兰计算机科学家、软件工程师、程序员、科学散文家和系统科学家 Edsger W. Dijkstra 博士设计并发表。 在 2001 年接受 Philip L. Frana 为《ACM 通讯》杂志采访时,Edsger W. Dijkstra 博士透露: “从鹿特丹到格罗宁根,一般来说,从一个给定的城市到另一个给定的城市,最短的路线是什么?这就是最短路径算法,我大约花了二十分钟设计出来。一天早上,我和我年轻的未婚妻在阿姆斯特丹购物,我们累了,坐在咖啡馆露台上喝咖啡,我当时只是在想我是否能做到这一点,然后我就设计了最短路径算法。就像我说的,那是二十分钟的发明。事实上,它在 59 年,三年后出版。这篇出版物仍然可读,事实上,它相当不错。它如此不错的原因之一是我没有用铅笔和纸设计它。后来我才知道,不用铅笔和纸设计的好处之一是,你几乎被迫避免所有可避免的复杂性。最终,那个算法令我大为惊讶,成为了我声誉的基石之一。” 1956 年,Dijkstra 在阿姆斯特丹数学中心担任程序员时,为了说明一台名为 ARMAC 的新计算机的功能,他思考了最短路径问题。他的目标是选择一个问题和一个(由计算机生成的)解决方案,让没有计算机背景的人也能理解。他开发了最短路径算法,后来在 ARMAC 上执行,用于荷兰 64 个城市(64 个城市,因此 6 位足以编码城市编号)的模糊缩短的交通地图。一年后,他遇到了该研究所下一台计算机的硬件工程师提出的另一个问题:最小化连接机器背板引脚所需的电线量。作为解决方案,他重新发现了名为 Prim 的最小生成树算法,并于 1959 年发表。 Dijkstra 算法的基本原理以下是 Dijkstra 算法的基本概念
理解 Dijkstra 算法的工作原理Dijkstra 算法需要图和源顶点。该算法建立在贪婪方法之上,因此在算法的每个步骤中都会找到局部最优选择(本例中的局部最小值)。 此算法中的每个顶点都将具有两个属性:
让我们简要了解这些属性。 访问属性
路径属性
最初,我们将所有顶点或节点标记为未访问,因为它们尚未被访问。除了源节点,所有节点的路径也设置为无穷大。此外,源节点的路径设置为零 (0)。 然后我们选择源节点并将其标记为已访问。之后,我们访问源节点的所有邻居节点并对每个节点执行松弛操作。松弛是借助另一个节点降低到达节点成本的过程。 在松弛过程中,每个节点的路径都会更新为该节点的当前路径、到前一个节点的路径之和以及从前一个节点到当前节点的路径中的最小值。 假设 p[n] 是节点 n 的当前路径值,p[m] 是到前一个访问节点 m 的路径值,w 是当前节点与前一个访问节点之间的边的权重(n 和 m 之间的边权重)。 在数学意义上,松弛可以表示为 p[n] = minimum(p[n], p[m] + w) 然后,我们在后续的每一步中,将路径最短的未访问节点标记为已访问,并更新其邻居的路径。 我们重复此过程,直到图中所有节点都标记为已访问。 每当我们向已访问集合添加一个节点时,它所有相邻节点的路径也会相应地改变。 如果任何节点无法到达(不连通组件),其路径将保持为“无穷大”。如果源本身是一个单独的组件,那么到所有其他节点的路径将保持为“无穷大”。 通过示例理解 Dijkstra 算法以下是我们实现 Dijkstra 算法将遵循的步骤 步骤 1: 首先,我们将源节点标记为当前距离为 0,并将其余节点设置为无穷大。 步骤 2: 然后,我们将当前距离最小的未访问节点设置为当前节点,假设为 X。 步骤 3: 对于当前节点 X 的每个邻居 N:我们将 X 的当前距离与连接 X-N 的边的权重相加。如果它小于 N 的当前距离,则将其设置为 N 的新当前距离。 步骤 4: 然后,我们将当前节点 X 标记为已访问。 步骤 5: 如果图中还有未访问的节点,我们将从“步骤 2”重复该过程。 现在让我们借助一个示例来理解算法的实现 ![]() 图 6:给定图
因此,我们得出的最终路径是 Dijkstra 算法的伪代码现在我们将理解 Dijkstra 算法的伪代码。
现在让我们实现上述插图的伪代码 伪代码 说明 在上面的伪代码中,我们定义了一个接受多个参数的函数——由节点组成的图和源节点。在此函数中,我们遍历了图中的每个节点,将其初始距离设置为 INFINITY,并将前一个节点值设置为 NULL。我们还检查了任何选定节点是否不是源节点,并将其添加到优先队列中。此外,我们将源节点的距离设置为 0。然后,我们遍历优先队列中的节点,选择距离最小的节点,并将其标记为已访问。然后,我们遍历所选节点的未访问相邻节点,并相应地执行松弛操作。最后,我们比较了源节点和目标节点之间的两个距离(原始距离和临时距离),用最小值和前一个节点信息更新了结果距离,并返回了包含其前一个节点信息的最终距离列表。 Dijkstra 算法在不同编程语言中的实现现在我们已经成功理解了 Dijkstra 算法的伪代码,是时候看看它在 C、C++、Java 和 Python 等不同编程语言中的实现了。 C 语言中 Dijkstra 算法的代码以下是 Dijkstra 算法在 C 编程语言中的实现 文件:DijkstraAlgorithm.c 输出 Distance from the Source Node to 1: 4 Distance from the Source Node to 2: 12 Distance from the Source Node to 3: 19 Distance from the Source Node to 4: 12 Distance from the Source Node to 5: 8 Distance from the Source Node to 6: 10 说明 在上面的代码片段中,我们包含了 stdio.h 头文件,定义了两个常量值:INF = 9999 和 MAX = 10。我们声明了函数的原型,然后将 Dijkstra 算法的函数定义为 DijkstraAlgorithm,它接受三个参数——由节点组成的图、图中节点的数量和源节点。在此函数中,我们定义了一些数据结构,例如将作为算法的优先队列的 2D 矩阵、用于维护节点之间距离的数组、用于维护前一个节点记录的数组、用于存储已访问节点信息的数组,以及一些整数变量用于存储最小距离值、计数器、下一个节点值等。然后我们使用嵌套 for 循环遍历图中的节点并相应地将它们添加到优先队列中。我们再次使用for 循环从源节点开始遍历优先队列中的元素并更新它们的距离。在循环之外,我们将源节点的距离设置为 0 并在 visited_nodes[] 数组中将其标记为已访问。然后我们将计数器值设置为 1,并使用 while 循环遍历节点数。在此循环中,我们将 minimum_distance 的值设置为 INF,并使用 for 循环用 distance[] 数组中的最小值更新 minimum_distance 变量的值。然后我们使用 for 循环遍历所选节点的未访问相邻节点并执行松弛。然后我们打印了使用 Dijkstra 算法计算的距离的结果数据。 在 main 函数中,我们定义并声明了表示图、节点数量和源节点的变量。最后,我们通过传递所需的参数调用了 DijkstraAlgorithm() 函数。 因此,为用户打印了从源节点到每个节点的所需最短路径。 C++ 中 Dijkstra 算法的代码以下是 Dijkstra 算法在 C++ 编程语言中的实现 文件:DijkstraAlgorithm.cpp 输出 Distance from start: 4 F G D A 说明 在上面的代码片段中,我们包含了 'iostream' 和 'vector' 头文件,并定义了一个常量值 MAX_INT = 10000000。然后我们使用了标准命名空间并原型化了 DijkstraAlgorithm() 函数。然后我们定义了程序的 main 函数,并在其中调用了 DijkstraAlgorithm() 函数。之后,我们声明了一些类来创建顶点和边。我们还原型化了更多函数来查找从源顶点到目标顶点的最短路径,并实例化了 Vertex 和 Edge 类。然后我们定义了这两个类来创建图的顶点和边。然后我们定义了 DijkstraAlgorithm() 函数来创建图并执行不同的操作。在这个函数中,我们声明了一些顶点和边。然后我们设置了图的源顶点,并调用 Dijkstra() 函数来查找最短距离,并调用 Print_Shortest_Route_To() 函数来打印从源顶点到顶点 'F' 的最短距离。然后我们定义了 Dijkstra() 函数来计算从源顶点到所有顶点的最短距离。我们还定义了一些其他函数来查找距离最短的顶点,以返回剩余顶点所有相邻的顶点,以返回两个连接的顶点之间的距离,以检查所选顶点是否存在于图中,并打印从源顶点到目标顶点的最短路径。 因此,为用户打印了从源节点到顶点 'F' 的所需最短路径。 Java 中 Dijkstra 算法的代码以下是 Dijkstra 算法在 Java 编程语言中的实现 文件:DijkstraAlgorithm.java 输出 Distance from Vertex 0 to Vertex 0 is 0 Distance from Vertex 0 to Vertex 1 is 1 Distance from Vertex 0 to Vertex 2 is 1 Distance from Vertex 0 to Vertex 3 is 2 Distance from Vertex 0 to Vertex 4 is 4 Distance from Vertex 0 to Vertex 5 is 4 Distance from Vertex 0 to Vertex 6 is 3 说明 在上面的代码片段中,我们定义了一个公共类 DijkstraAlgorithm()。在此类中,我们定义了一个公共方法 dijkstraAlgorithm() 来查找从源顶点到目标顶点的最短距离。在此方法中,我们定义了一个变量来存储节点数。然后我们定义了一个布尔数组来存储有关已访问顶点的信息,以及一个整数数组来存储它们各自的距离。最初,我们将两个数组中的值分别声明为 False 和 MAX_VALUE。我们还将源顶点的距离设置为零,并使用 for 循环更新源顶点和目标顶点之间与最小距离的距离。然后我们通过执行松弛操作更新了所选顶点的相邻顶点的距离,并打印了每个顶点的最短距离。然后我们定义了一个方法来查找从源顶点到目标顶点的最小距离。然后我们定义了主函数,在其中声明了图的顶点并实例化了 DijkstraAlgorithm() 类。最后,我们调用了 dijkstraAlgorithm() 方法来查找源顶点和目标顶点之间的最短距离。 因此,为用户打印了从源节点到每个节点的所需最短路径。 Python 中 Dijkstra 算法的代码以下是 Dijkstra 算法在 Python 编程语言中的实现 文件:DikstraAlgorithm.py 输出 Distance of A from source node: 0 Distance of B from source node: 3 Distance of C from source node: 1 Distance of D from source node: 2 Distance of E from source node: 2 Distance of F from source node: 4 Distance of G from source node: 3 说明 在上面的代码片段中,我们导入了 sys 模块,并声明了由节点和边的值组成的列表。然后我们定义了一个函数 toBeVisited() 来查找下一个要访问的节点。然后我们找到了图中节点的总数,并设置了每个节点的初始距离。然后我们计算了从源节点到目标节点的最小距离,对相邻节点执行了松弛操作,并更新了列表中的距离。然后我们将这些距离从列表中打印给用户。 因此,为用户打印了从源节点到每个节点的所需最短路径。 Dijkstra 算法的时间和空间复杂度
Dijkstra 算法的优缺点让我们讨论一下 Dijkstra 算法的一些优点
尽管 Dijkstra 算法具有多项优点,但它也有一些缺点,例如
Dijkstra 算法的一些应用Dijkstra 算法有各种实际应用,其中一些如下所述
结论
下一主题Bellman Ford 算法 |
我们请求您订阅我们的新闻通讯以获取最新更新。