迪杰斯特拉的关键理念:找出图中开销最低的节点,并保证没有到该节点开销更低的路径
以下面的图为例:
要解决这个问题,需要三个散列表:
起点 | A | 6 |
B | 2 | |
A | final | 1 |
B | A | 3 |
final | 5 | |
final | - |
A | 6 |
B | 2 |
终点 | ∞ |
A | start |
B | start |
终点 | - |
随着算法的进行,你将不断更新散列表costs和parents
迪杰斯特拉算法的4个步骤:
- 找出开销最小的节点,即可在最短时间内前往的节点
- 对于该节点的邻居,检查是否有前往它们的更短路径,如果有,就更新其开销
- 重复这个过程,直到对图中的每个节点都这样做了
- 计算最终路径
算法流程图
Python代码:
# -*- encoding: utf-8 -*-
# 找出开销最低的节点
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 node not in processed: # 如果当前节点的开销更低且未处理过
lowest_cost = cost # 将其视为开销最低的节点
lowest_cost_node = node
return lowest_cost_node
if __name__ == "__main__":
# 构造有向赋权图 graph 存储所有节点的邻居
graph = {}
graph["start"] = {} # 起点
graph["start"]["a"] = 6
graph["start"]["b"] = 2
graph["a"] = {} # a节点
graph["a"]["final"] = 1
graph["b"] = {} # b节点
graph["b"]["a"] = 3
graph["b"]["final"] = 5
graph["final"] = {} # 终点没有任何邻居
# 构造散列表costs 存储每个节点的开销
# 节点的开销是指的从起点出发前往该节点需要多长时间
# 对于还不知道的开销,你将其设置为无穷大
infinity = float ("inf")
costs = {}
costs["a"] = graph["start"]["a"]
costs["b"] = graph["start"]["b"]
costs["final"] = infinity
# 还需要一个存储父节点的散列表
parents = {}
parents["a"] = "start"
parents["b"] = "start"
parents["final"] = None
# 最后你需要一个数组,记录处理过的节点
processed = []
node = find_lowest_cost_node(costs) # 找出开销最低的节点
while node is not None:
cost = costs[node] # 获取该节点的开销
neighbors = graph[node] # 获取该节点的邻居 neighbors是一个散列表
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)
child = "final"
parent = ["final"]
while child != "start":
parent.append(parents[child])
child = parents[child]
# print(parent)
print("最短路径为:", parent[::-1])
print("最短路径长度为:%d" % costs["final"])
注意:不能将迪杰斯特拉算法用于包含负权边的图。因为迪杰斯特拉算法这样假设:对于处理过的节点,没有前往该节点的更短路径,负权边会导致这个算法不管用。负权边的图中,要找出最短路径,可以使用另一种算法——贝尔曼-福德算法(Bellman-Ford algorithm)。