1 算法简介
戴克斯特拉算法(英语:Dijkstra’s algorithm,又译迪杰斯特拉算法)由荷兰计算机科学家艾兹赫尔·戴克斯特拉在1956年提出。戴克斯特拉算法使用了广度优先搜索解决赋权有向图的单源最短路径问题。该算法存在很多变体;戴克斯特拉的原始版本找到两个顶点之间的最短路径,但是更常见的变体固定了一个顶点作为源节点然后找到该顶点到图中所有其它节点的最短路径,产生一个最短路径树。该算法常用于路由算法或者作为其他图算法的一个子模块。
2 Dijkstra & 广度优先搜索(BFS)
广度优先搜索是寻找变数最少的路径,Dijkstra算法是寻找权重和最小的路径。当路径权重一致时,两种算法解决的问题是一致的。
3 算法实现
我们以实现下图为例实现Dijkstra算法:
在寻找最短路径时,可以知道,最短路径的子路径也是最优路径。为了实现Dijkstra算法,我们需要三步,第一步是构造三个模块,分别表示图、到任一一节点的最短路径、最短路径对应的父节点。第二步是更新起点到各个点的最短路径,包括终点。第三部是输出与实现最短路径查找。接下来我们分模块来进行实现
3.1 构造三大模块
首先是实现图,对于有权重的图,我们一般采用三元组来实现,如(A,F,1),在python中采用字典的嵌套来实现,上述图可以表示为
graph = {}
graph['S'] = {}
graph['S']['A'] = 6
graph['S']['B'] = 2
graph['A'] = {}
graph['A']['F'] = 1
graph['B'] = {}
graph['B']['A'] = 3
graph['B']['F'] = 5
graph['F'] = {}
其次是实现到任意节点的最短路径,这里使用cost表示路径权值和,根据具体实例构建为
# 从S点开始时,到三点依次为6 2 和无穷(还不能到达)
infinity = float("inf")
costs = {}
costs['A'] = 6
costs['B'] = 2
costs['F'] = infinity
第三个模块是构建最短路径条件下的节点关系,使用字典表示,构建父子节点键值对,其中key表示子节点,value表示父节点
# 存储父节点
parents = {}
parents['A'] = 'S'
parents['B'] = 'S'
parents['F'] = None
3.2 构建查询最小权值点
在算法中,进行下一步建立在前一步是最短路径的基础上,因此在向下一个node迈进时,需要查询当前权值和最小的点,代码实现如下
def find_lowest_cost_node(costs): # 在costs中找到现在权值和最小的node
lowest_cost = float("inf") # 先将最小cost的点设为无且权值最大
lowest_cost_node = None
for node in costs: # 遍历现在每一个cost中的node,如果小则替换掉
cost = costs[node]
if cost < lowest_cost and node not in processed:
lowest_cost = cost
lowest_cost_node = node
return lowest_cost_node # 返回找出最小cost的node点
3.3 路径更新
为了实现路径更新,需要对最短路径进行处理。处理方法是当前最短路径的node想其邻居node进行移动,并且找到在所有邻居node中权值和最小的node,同时对三大模块中存储的数据进行更新。具体代码实现如下:
# 建立已处理的node
processed = []
node = find_lowest_cost_node(costs)
while node is not None:
cost = costs[node] # cost表示当前权值和最小的node的权值
neighbors = graph[node] # 找到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)
3.4 最短路径查询
经过上述处理,我们知道在costs字典中存储着从起点到各个node的最短路径权值,parents中存储着最优路径下父子节点的关系,因此需要对parents进行处理,确保能够输入一个node作为参数时,能够输出最短路径移动过程。具体代码实现如下
def find_path(node):
path = [] # 建立路径生成的空列表
while node in parents.keys(): # 确定node在parent中则将其加入path
path.append(node)
node = parents[node]
path.append('S') # 加入起点
for i in range(len(path)):
if i != len(path)-1:
print("{} --> ".format(path[-i-1]),end = '')
else:
print(path[0])
4 整体代码测试
下面的代码是对上面具体用例的汇总,并进行了简单的测试。完整Dijkstra算法代码为:
graph = {}
graph['S'] = {}
graph['S']['A'] = 6
graph['S']['B'] = 2
graph['A'] = {}
graph['A']['F'] = 1
graph['B'] = {}
graph['B']['A'] = 3
graph['B']['F'] = 5
graph['F'] = {}
# 从S点开始时,到三点依次为6 2 和无穷(还不能到达)
infinity = float("inf")
costs = {}
costs['A'] = 6
costs['B'] = 2
costs['F'] = infinity
# 存储父节点
parents = {}
parents['A'] = 'S'
parents['B'] = 'S'
parents['F'] = None
def find_lowest_cost_node(costs): # 在costs中找到现在权值和最小的node
lowest_cost = float("inf") # 先将最小cost的点设为无且权值最大
lowest_cost_node = None
for node in costs: # 遍历现在每一个cost中的node,如果小则替换掉
cost = costs[node]
if cost < lowest_cost and node not in processed:
lowest_cost = cost
lowest_cost_node = node
return lowest_cost_node # 返回找出最小cost的node点
# 建立已处理的node
processed = []
node = find_lowest_cost_node(costs)
while node is not None:
cost = costs[node] # cost表示当前权值和最小的node的权值
neighbors = graph[node] # 找到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_path(node):
path = [] # 建立路径生成的空列表
while node in parents.keys(): # 确定node在parent中则将其加入path
path.append(node)
node = parents[node]
path.append('S') # 加入起点
for i in range(len(path)):
if i != len(path)-1:
print("{} --> ".format(path[-i-1]),end = '')
else:
print(path[0])
print(costs)
find_path('F')
输出结果为:
{'A': 5, 'B': 2, 'F': 6}
S --> B --> A --> F