《图解算法》读书笔记(七) 狄克斯特拉算法
章节内容
- 加权图
- 狄克斯特拉算法
- 图中的环,导致狄克斯特拉算法不管用
前言
在之前一个章节中,我们学会了使用广度优先搜索去查找最短路径。但其实在现实问题中,每个路径的长度都是不同的。
所以我们只是找到了一个最少连线的路径。在这一章节,我们将学会使用狄克斯特拉算法去查找加权图的最短路径。
使用狄克斯特拉算法
狄克斯特拉算法包含四个步骤:
- 找出加权最少的邻居
- 更新该节点的邻居的路径
- 重复第二个步骤,直到图中的每个节点都这样做了
- 计算最终路径
术语
权重:每条边上的关联数字
加权图:带权重的图
非加权图:不带权重的图
狄克斯特拉算法只适用于有向无环加权图
负权边
如果边的权有为负数的,也不适合用狄克斯特拉算法算法;
可使用另一种算法:贝尔曼-福德算法(Bellman-Ford)。
为什么不适合用狄克斯特拉算法?
狄克斯特拉算法是基于之前的节点的最短路径计算之后的节点,如果有负权边,则可能影响之前节点的最短路径,那样所有这个节点后的节点计算都存在问题。
实现
首先我们需要一个散列表存储图的数据,和上一章节不同,我们需要存储邻居的节点,以及到这些节点的开销,所以我们采用二维散列表。
比如:
graph = {}
graph["start"] = {}
graph["start"]["a"] = 6
graph["start"]["b"] = 2
然后我们需要另一个散列表来存储从起点到每个节点的开销。对于还不知道开销的节点的值可以设置为无限大,python中可以表示为:
infinity = float("inf")
创建开销表的代码如下:
infinity = float("inf")
costs = {}
costs["a"] = 6
costs["b"] = 2
costs["fin"] = infinity
还需要一个存储父节点的散列表
创建这个散列表的代码如下:
parents = {}
parents["a"] = "start"
parents["b"] = "start"
parents["fin"] = None
最后需要一个散列表存储处理过的节点。
准备工作做好了,下面来看看算法步骤:
- 只要还有要处理的节点
- 获取离起点最近的节点
- 更新其邻居的开销
- 如果有邻居的开销被更新了,同时更新其父节点
- 将该节点标记为处理过
重复以上五个步骤直至所有节点都已处理,代码如下:
node = find_lowest_cost_node(costs)
while node is not None:
cost = costs[node]
neighbors = graph[node]
for n in neighbors.keys():
new_cost = cost + neighbors[n]
if costs[n] > new_cost:
costs[n] = new_cost
parents[n] = node
processed.append(node)
node = find_lowest_cost_node(costs)
def find_lowest_cost_node(costs):
lowest_cost = float("inf")
lowest_cost_node = None
for node in costs:
cost = costs[node]
if cost < lowest_cost and not not in processed:
lowest_cost = cost
lowest_cost_node = node
return lowest_cost_node
小结
- 广度优先搜索用于在非加权图中查找最短路径
- 狄克斯特拉算法用于在加权图中查找最短路径
- 仅当权重为正时狄克斯特拉算法管用
- 如果图中包含负权边,请使用Bellman-Ford算法