我们有时需要找出两个不同角落之间的联系,这时我们需要图形。在一个简单的例子中,如果我们想从一个地方到另一个地方,那么谷歌地图会显示所有不同的方式,但突出显示到达你的目的地的最短路径。但是,如果你在使用谷歌地图时改变了你的路径,那么它就会根据你当前的位置预测出一条新的路径来到达你的目的地。因此,所有这些都是通过我们称之为Dijkstra算法的算法发生的。Dijkstra算法找到节点之间的最短路径。
在图中,有节点和边。节点是数值,边是在两个节点之间建立联系的路径或线条。在Python中,我们可以使用嵌套字典来实现节点和边。我们可以把节点表示为一个键,把从该节点到其他节点的所有路径表示为该特定键的值。
Dijkstra算法被用来寻找源节点和目标节点之间的最短路径。这种算法使用的方法被称为贪婪法。因此,在这篇文章中,我们将了解Dijkstra算法的概念,以及我们如何使用Python编程实现它。
正如我们之前所说,Dijkstra算法使用的是贪婪方法的概念。我们可以用一个普通的术语来理解贪婪的方法,即从可用的选项中找到最优的解决方案。
算法步骤
- 我们首先初始化源节点的值为0,其他相邻节点的值为无限。
- 将源节点和距离值作为一对<节点,距离>插入字典中。例如,源节点值对将是<S,0>。S代表节点名称,0是距离值。值为0是因为我们初始化源节点值为0。
- 循环将继续,直到DICTIONARY,不为空(或不为空)。
- 我们根据以下条件将DICTIONARY中的任何一对<node, dist>分配给current_source_node。
该节点的距离值应小于可用节点的距离值。之后,从字典列表中删除该节点,因为该节点现在是 current_source_node。
- 对于current_source_node的每个adjacent_link_node做
- 如果( dist [ adjacent_link_node ] > 从current_source_node到相邻节点的边缘值+ dist [ current_source_node ] )
- dist [ adjacent_link_node ] = current_source_node 到相邻节点的边值 + dist [ current_source_node ] 。
- 然后用<adjacent_link_node_name, dist [ adjacent_link_node,] >更新DICTIONARY。
迪克斯特拉算法的步骤
Dijkstra算法是用来寻找源节点和目标节点之间的最短路径的。
步骤1:为此,我们必须首先将源节点初始化为0,其他节点初始化为∞。然后我们在字典中插入一对<节点,来自原始源节点的节点距离>。我们的第一对将是<0,0>,因为从源节点到源节点本身的距离是0,如下图和表所示。
| 源节点 | 目的地节点 | 与源节点的距离 | 词典 |
|---|---|---|---|
| 0 | 0 | 0 | [0, 0] |
| 0 | 1 | ∞ | |
| 0 | 2 | ∞ | |
| 0 | 3 | ∞ | |
| 0 | 4 | ∞ | |
| 0 | 5 | ∞ |
步骤:2在字典中,只有一对<0, 0>。因此,我们将其作为当前源节点,并放松从当前源节点开始的所有相邻节点的边的权重(0)。
| 当前源节点 | 相邻节点 | 从源节点(0)到相邻节点的距离 | 是否更新egde的权重 |
|---|---|---|---|
| 0 | 1 | dist [ 1 ] = ∞ | dist[ 1 ] > dist_between [ 0 - 1 ] + dist [ 0 ] 即∞ > 5 + 0 所以,我们要更新距离。更新dist =>dist [ 1 ] = 5并更新dict <1, 5>中的一对 |
| 0 | 2 | dist [ 2 ] = ∞ | dist [ 2 ] > dist_between [ 0 - 2 ] + distance [ 0 ] 即∞ > 1 + 0 所以,我们要更新距离。更新dist =>dist [ 2 ] = 1,并更新dict <2 ,1>中的一对。 |
| 0 | 3 | dist [ 3 ] = ∞ | dist [ 3 ] > dist_between [ 0 - 3 ] + dist [ 0 ] 所以,我们要更新距离。 即∞ > 4 + 0 更新dist,即dist [ 3 ]= 4,并更新dict <3, 4>中的一对。 |
| 源节点 | 目的地节点 | 与源节点的距离 | 词典 |
|---|---|---|---|
| 0 | 0 | 0 | [1, 5] |
| [2, 1] | |||
| [3, 4] | |||
| 0 | 1 | 5 | |
| 0 | 2 | 1 | |
| 0 | 3 | 4 | |
| 0 | 4 | ∞ | |
| 0 | 5 | ∞ |
第3步:现在,我们从字典中删除当前_source_node的下一对。但条件是,我们必须选择最小距离值的节点。因此,我们从字典中选择<2, 1>作为current_source_node,并放松current_source_node的所有相邻节点的边的权重(2)。
| 当前源节点 | 相邻节点 | 从源节点(0)到相邻节点的距离 | 是否更新egde的权重 |
|---|---|---|---|
| 2 | 0 | 距离 [ 0 ] = 0 | dist [ 0 ] < dist_between [ 2 - 0 ] + dist [ 2 ] 即 0 < 1 + 1 不需要更新权重。 dist [ 1 ] > dist_between [ 2 - 1 ] + dist [ 2 ] 即 5 > 3 + 1 |
| 2 | 1 | 距离 [ 1 ] = 5 | 所以,我们要更新距离。更新dist ==>dist [ 1 ] = 4并更新dict <1, 4>中的一对dist [ 3 ] > dist_between [ 2 - 3 ] + dist [ 2 ]即4 > 2 + 1 |
| 2 | 3 | 距离 [ 3 ] = 4 | 所以,我们要更新这个距离。更新dist => dist [ 3 ] = 3并更新dict中的一对 <3, 3> dist [ 4 ] > dist_between [ 2 - 4 ] + dist [ 2 ] 即∞ > 1 + 1 |
| 2 | 4 | 距离 [ 4 ] = ∞ | 所以,我们要更新这个距离。更新dist =>dist [ 4 ] = 2更新dict中的一对<4, 2>。 |
| 源节点 | 目的地节点 | 与源节点的距离 | 词典 |
|---|---|---|---|
| 2 | 0 | 0 | [1, 4] |
| [3, 3] | |||
| [4, 2] | |||
| 2 | 1 | 4 | |
| 2 | 2 | 1 | |
| 2 | 3 | 3 | |
| 2 | 4 | 2 | |
| 2 | 5 | ∞ |
第4步:现在,我们从字典中删除下一对<4,2>,以选择current_source_node,并从current_source_node放松所有相邻节点的边的权重(4)。
| 当前源节点 | 相邻节点 | 从源节点(0)到相邻节点的距离 | 是否更新egde的权重 |
|---|---|---|---|
| 4 | 1 | dist [ 1 ] = 4 | dist [ 1 ] < dist_between [ 4 - 1 ] + dist [ 4 ] 即 4 < 8 + 2 不需要更新权重。 |
| 4 | 2 | dist [ 2 ] = 1 | dist [ 2 ] < dist_between [ 4 - 2 ] + dist [ 4 ] 即 1 < 1 + 2 不需要更新权重。 |
| 4 | 3 | dist [ 3 ] = 3 | dist [ 3 ] < dist_between [ 4 - 3 ] + dist [ 4 ] 即 3 < 2 + 2 无需进行权重更新。 |
| 4 | 5 | dist [ 5 ] = ∞ | 所以,我们要更新距离。更新dist => dist [ 5 ] = 5更新dict中的一对<5, 5>。 |
| 源节点 | 目的地节点 | 与源节点的距离 | 词典 |
|---|---|---|---|
| 4 | 0 | 0 | [1, 4] |
| [3, 3] | |||
| [5, 5] | |||
| 4 | 1 | 4 | |
| 4 | 2 | 1 | |
| 4 | 3 | 3 | |
| 4 | 4 | 2 | |
| 4 | 5 | 5 |
第5步:我们从字典中删除下一对<3, 3>来选择current_source_node,并从current_source_node(3)中放松所有相邻节点的边的权重。
| 当前源节点 | 相邻节点 | 从源节点(0)到相邻节点的距离 | 是否更新egde的权重 |
|---|---|---|---|
| 3 | 0 | dist [ 0 ] = 0 | dist [ 0 ] < dist_between [ 3 - 0 ] + dist [ 3 ] 即 0 < 4 + 3 不需要更新权重。 |
| 3 | 2 | dist [ 2 ] = 1 | dist [ 2 ] < dist_between [ 3 - 2 ] + dist [ 3 ] 即 1 < 2 + 3 无需进行权重更新。 |
| 3 | 4 | dist [ 4 ] = 2 | dist [ 4 ] < dist_between [ 3 - 4 ] + dist [ 3 ] 即 2 < 2 + 3 无需进行权重更新。 |
| 3 | 5 | dist [ 5 ] = 5 | 所以,我们要更新距离。更新dist =>dist [ 5 ] = 4更新字典中的一对<5, 4>。 |
| 源节点 | 目的地节点 | 与源节点的距离 | 词典 |
|---|---|---|---|
| 3 | 0 | 0 | [1, 4] |
| [5, 4] | |||
| 3 | 1 | 4 | |
| 3 | 2 | 1 | |
| 3 | 3 | 3 | |
| 3 | 4 | 2 | |
| 3 | 5 | 4 |
第6步:我们从字典中删除下一对<1, 4>来选择current_source_node,并从current_source_node(1)放松所有相邻节点的边的权重。
| 当前源节点 | 相邻节点 | 从源节点(0)到相邻节点的距离 | 是否更新egde的权重 |
|---|---|---|---|
| 1 | 0 | 距离 [ 0 ] = 0 | distance [ 0 ] < distance_between [ 1 - 0 ] + distance [ 1 ] 即既然 0 < 5 + 4 不需要更新权重。 |
| 1 | 2 | dist [ 2 ] = 1 | dist [ 2 ] < dist_between [ 1 - 2 ] + dist [ 1 ] 即 1 < 3 + 4 不需要更新权重。 |
| 1 | 4 | dist [ 4 ] = 2 | dist[ 4 ] < dist_between [ 1 - 4 ] + dist [ 1 ] 即 2 < 8 + 4 不需要进行权重更新。 |
| 源节点 | 目的地节点 | 与源节点的距离 | 词典 |
|---|---|---|---|
| 1 | 0 | 0 | [5, 4] |
| 1 | 1 | 4 | |
| 1 | 2 | 1 | |
| 1 | 3 | 3 | |
| 1 | 4 | 2 | |
| 1 | 5 | 4 |
第7步:现在,我们从字典中删除下一对<5, 4>来选择current_source_node,并从current_source_node(5)放松所有相邻节点的边的权重。
| 当前源节点 | 相邻节点 | 从源节点(0)到相邻节点的距离 | 是否更新egde的权重 |
|---|---|---|---|
| 5 | 3 | dist [ 3 ] = 3 | ddist [ 3 ] < dist_between [ 5 - 3 ] + dist [ 5 ] 即 3 < 1 + 4 不需要更新权重。 |
| 5 | 4 | dist [ 4 ] = 2 | dist [ 4 ] < dist_between [ 5 - 4 ] + dist [ 5 ] 即 2 < 3 + 4 不需要进行权重更新。 |
现在,字典是空的,没有留下任何一对。所以,我们的算法现在已经停止了。我们得到了从主源节点(S)到所有其他节点的最短路径,如下所示。
| 源节点 | 目的地节点 | 离源节点的距离 | 词典 |
|---|---|---|---|
| 0 | 0 | 0 | |
| 0 | 1 | 4 | |
| 0 | 2 | 1 | |
| 0 | 3 | 3 | |
| 0 | 4 | 2 | |
| 0 | 5 | 4 |
Python代码。下面是上述算法的实现。
1 # Dijkstra's Algorithm in Python
2 from collections import defaultdict
3
4 class Node_Dist:
5 def __init__(self, name, dist) :
6 self.name = name
7 self.dist = dist
8
9 class DijkstraAlgorithm:
10
11 def __init__(self, node_count) :
12 self.adjacentlist = defaultdict(list)
13 self.node_count = node_count
14
15 def Adjacent_nodelist(self, src, node_dist) :
16 self.adjacentlist[src].append(node_dist)
17
18 def Dijkstras_Shortest_Path(self, source_node) :
19 # Initialize the nodes with infinite value and source
20 # node with 0
21 dist = [999999999999] * self.node_count
22 dist[source_node] = 0
23
24 # we are creating a dict as said in the alogrithm with the
25 # value pair
26 dict_of_node_dist = {source_node: 0}
27
28 while dict_of_node_dist :
29
30 # Now, we are going to assing a pair to a
31 # current_source_node but condition that dist value
32 # should be the minimum value
33 current_source_node = min(dict_of_node_dist,
34 key = lambda k: dict_of_node_dist[k])
35
36 # After assign that pair to the current_source_node,
37 # delete that item from the dict
38 del dict_of_node_dist[current_source_node]
39
40 for node_dist in self.adjacentlist[current_source_node] :
41 adjacentNode = node_dist.name
42 source_to_adjacentNode_dist = node_dist.dist
43
44 # We are doing here edge relaxation of the adjacent node
45 if dist[adjacentNode] > dist[current_source_node] + \
46 source_to_adjacentNode_dist :
47 dist[adjacentNode] = dist[current_source_node] + \
48 source_to_adjacentNode_dist
49 dict_of_node_dist[adjacentNode] = dist[adjacentNode]
50
51 for i in range(self.node_count) :
52 print("Distance From Source Node ("+str(source_node)+") => to "
53 "Destination Node(" + str(i) + ") => " + str(dist[i]))
54
55 def main() :
56
57 graph = DijkstraAlgorithm(6)
58 graph.Adjacent_nodelist(0, Node_Dist(1, 5))
59 graph.Adjacent_nodelist(0, Node_Dist(2, 1))
60 graph.Adjacent_nodelist(0, Node_Dist(3, 4))
61
62 graph.Adjacent_nodelist(1, Node_Dist(0, 5))
63 graph.Adjacent_nodelist(1, Node_Dist(2, 3))
64 graph.Adjacent_nodelist(1, Node_Dist(4, 8))
65
66 graph.Adjacent_nodelist(2, Node_Dist(0, 1))
67 graph.Adjacent_nodelist(2, Node_Dist(1, 3))
68 graph.Adjacent_nodelist(2, Node_Dist(3, 2))
69 graph.Adjacent_nodelist(2, Node_Dist(4, 1))
70
71 graph.Adjacent_nodelist(3, Node_Dist(0, 4))
72 graph.Adjacent_nodelist(3, Node_Dist(2, 2))
73 graph.Adjacent_nodelist(3, Node_Dist(4, 2))
74 graph.Adjacent_nodelist(3, Node_Dist(5, 1))
75
76 graph.Adjacent_nodelist(4, Node_Dist(1, 8))
77 graph.Adjacent_nodelist(4, Node_Dist(2, 1))
78 graph.Adjacent_nodelist(4, Node_Dist(3, 2))
79 graph.Adjacent_nodelist(4, Node_Dist(5, 3))
80
81 graph.Adjacent_nodelist(5, Node_Dist(3, 1))
82 graph.Adjacent_nodelist(5, Node_Dist(4, 3))
83
84 graph.Dijkstras_Shortest_Path(0)
85
86
87 if __name__ == "__main__" :
88 main()
第9行至第53行:该类的解释见下文。
第9行。我们创建了一个名为DijkstraAlgorithm的类。
第11到16行:我们初始化了 adjacentlist 和 node_count。adjacentlist是一个dict,我们用它来存储节点和它们所有的相邻节点,比如Node 0: 。如果你打印这段代码,将显示如下结果。
defaultdict(<class 'list'>, {})
defaultdict(<class 'list'>, {0: [<__main__.Node_Dist object at 0x7f2b0a05abe0>]})
上面的结果显示,我们正在创建一个dict,它拥有一个特定节点及其相邻节点的所有细节。
第21至22行:按照我们的算法,我们用无穷大的值初始化所有节点,用0初始化源节点。
第26行:我们按照我们的算法初始化dict_of_node_dist,这是我们的第一对。
第28行至第50行:我们按照算法第4至第8行实现。
第57行至第83行:我们创建了一个DijkstraAlgorithm类的对象并传递了图中的节点数。然后我们使用对象图调用Adjacent_nodelist方法,将所有的节点与它们的相邻节点组成一个dict。节点将是关键,相邻节点和距离将是它们的值。
第83行:我们使用对象图调用了Dijkstras_Shortest_Path(0)方法。
输出:Python Dijkstra's Algorithm.py
- 从源节点的距离(0) => 到目的地节点(0) => 0
- 从源节点的距离(0) => 到目的地节点(1) => 4
- 从源节点的距离(0) => 到目的地节点(2) => 1
- 从源节点的距离 (0) => 到目的地节点(3) => 3
- 从源节点的距离 (0) => 到目的地节点(4) => 2
- 从源节点的距离 (0) => 到目的地节点(5) => 4
总结
在这篇文章中,我们已经逐步研究了Dijkstra的算法。我们还研究了贪婪方法的想法。我们没有包括关于贪婪方法的细节,因为我们很快就会回到这个(贪婪方法)主题。