一、在一个无向图中使用dijkstra算法,输出一个已知点所求点的最小距离,以及输出到该点的路径。
以该图为例,从点A出发,求到某点的最短路径和经过的点名称。
python代码如下,
def DFS(dic:dict,node:str,graph): # 在图中深搜dijkstra算出的结果
if node == 'A': # 当遍历到节点A时,返回结果
return
stack.append(dic[node][0])
DFS(dic,stack[-1],graph)
class Mintree(object):
def creat_graph(self, graph, vertex: int, data: [], weight: []): # graph:图类 vertex: 顶点数 data: 顶点名字 weight:边权值
for i in range(vertex):
graph.data[i] = data[i]
for j in range(vertex):
graph.weight[i][j] = weight[i][j]
def dijkstra(self, graph,s:str): # s为要求的点
index = graph.data.index(s) # 索引该点在结果中的位置
re = {}
result = graph.weight[0] # 最终结果
result1 = [] # 每行的数据(待处理的)
Min_name = [0] # 用于添加每行最小值的序号
a = 0 # 每行最小值的序号
for p in range(1,graph.vertex):
if result[p] < 10000:
re[graph.data[p]] = ['A']
else:
re[graph.data[p]] = []
for i in range(graph.vertex-2):
result1 = graph.weight[a].copy() # 添加每一行数据
for m in range(len(Min_name)):
result1[Min_name[m]] = 10000 # 将已松弛的点记为无穷,即不再选取此点为最小值
result1[i] = 10000 # 将该点到自身的距离记为无穷,因为考虑到自身的距离为0无意义
minimum = min(result1)
for j in range(graph.vertex):
if result1[j] == minimum:
a = j
Min_name.append(a) # 找到处理完后每行的最小值,并记录该点,准备进行下一步松弛
for k in range(graph.vertex): # 最重要的一步:松弛; 即确定一个点后,将该点到其余点的距离是否能够缩短,并记录
if (graph.weight[a][k] + result[a]) < result[k]:
result[k] = graph.weight[a][k] + result[a]
re[graph.data[k]] = [graph.data[a]] # 建立一个字典,存储每个节点到起点最近的上一个点
print(result[index])
return re
class Graph(object): #创建一个图的类
def __init__(self, vertex):
self.vertex = vertex
self.data = [0] * vertex
self.weight = [[0 for row in range(vertex)]for col in range(vertex)]
if __name__ == '__main__':
vertex_data = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
weights = [[0, 4, 10000, 10000, 7, 10000, 11, 10000],
[4, 0, 2, 10000, 6, 10000, 10000, 10000],
[10000, 2, 0, 9, 1, 8, 10000, 10000],
[10000, 10000, 9, 0, 10000, 1, 10000, 3],
[7, 6, 1, 10000, 0, 10, 9, 4],
[10000, 10000, 8, 1, 10, 0, 10000, 2],
[11, 10000, 10000, 10000, 9, 10000, 0, 5],
[10000, 10000, 10000, 3, 4, 2, 5, 0]]
length = len(vertex_data)
g = Graph(length)
tree = Mintree()
tree.creat_graph(g, length, vertex_data, weights)
res = tree.dijkstra(g,'D')
print(res)
stack = ['D']
DFS(res,'D',g)
print(stack[::-1]) # 输出到该点的路径
具体的松弛思想在网上可以找到完整的解释,在此就不赘述了,本文只记录思路及在python上面的解决方案。
二、若有多条最短路,如何输出所有最短路径
python代码如下
def DFS(dic:dict,node:str,graph):
fl = 0
if node == 'D': # 从起点开始,寻找终点,到终点(此处为’D‘)截止并回溯
print(['A'] + stack) # 加上起点,完整输出,并且只输出成功到达终点的过程
stack.pop(-1) # 退出栈中的终点,继续递归
return
for i in graph.data[1:]: # 遍历所有节点,找到可行的节点并继续递归
if node in dic[i]:
stack.append(i)
fl = 1
DFS(dic,stack[-1],graph)
if fl == 0:
stack.pop(-1) # 若下一个对应点不在最短路径中,即最终结果不含该点,则使其出栈
stack.clear() # 这一行代码就想了半天,意为:每次递归结束清除栈中所有点,即可清楚冗余的递归过程,只记录成功到达终点的一次递归
class Mintree(object):
def creat_graph(self, graph, vertex: int, data: [], weight: []): # graph:图类 vertex: 顶点数 data: 顶点名字 weight:边权值
for i in range(vertex):
graph.data[i] = data[i]
for j in range(vertex):
graph.weight[i][j] = weight[i][j]
def dijkstra(self, graph, s:str): # s为所求点
index = graph.data.index(s) # 索引该点
re = {}
result = graph.weight[0] # 最终结果
result1 = [] # 每行的数据(待处理的)
Min_name = [0] # 用于添加每行最小值的序号
a = 0 # 每行最小值的序号
for p in range(1,graph.vertex):
if result[p] < 10000:
re[graph.data[p]] = ['A']
else:
re[graph.data[p]] = []
for i in range(graph.vertex-2):
result1 = graph.weight[a].copy() # 添加每一行数据
for m in range(len(Min_name)):
result1[Min_name[m]] = 10000 # 将已松弛的点记为无穷,即不再选取此点为最小值
result1[i] = 10000 # 将该点到自身的距离记为无穷,因为考虑到自身的距离为0无意义
minimum = min(result1)
for j in range(graph.vertex):
if result1[j] == minimum:
a = j
Min_name.append(a) # 找到处理完后每行的最小值,并记录该点,准备进行下一步松弛
for k in range(graph.vertex): # 最重要的一步:松弛; 即确定一个点后,将该点到其余点的距离是否能够缩短,并记录
if (graph.weight[a][k] + result[a]) < result[k]:
result[k] = graph.weight[a][k] + result[a]
re[graph.data[k]] = [graph.data[a]]
elif (graph.weight[a][k] + result[a]) == result[k]: # 建立一个字典,存储每个节点到起点最近的上一个点,此处若有相等的距离也存储,因此用字典加列表
if graph.weight[a][k] != 0:
re[graph.data[k]].append(graph.data[a])
print(result[index])
return re
class Graph(object):
def __init__(self, vertex):
self.vertex = vertex
self.data = [0] * vertex
self.weight = [[0 for row in range(vertex)]for col in range(vertex)]
if __name__ == '__main__':
vertex_data = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
weights = [[0, 4, 10000, 10000, 7, 10000, 11, 10000],
[4, 0, 2, 10000, 6, 10000, 10000, 10000],
[10000, 2, 0, 9, 1, 8, 10000, 10000],
[10000, 10000, 9, 0, 10000, 1, 10000, 3],
[7, 6, 1, 10000, 0, 10, 9, 4],
[10000, 10000, 8, 1, 10, 0, 10000, 2],
[11, 10000, 10000, 10000, 9, 10000, 0, 5],
[10000, 10000, 10000, 3, 4, 2, 5, 0]]
length = len(vertex_data)
g = Graph(length)
tree = Mintree()
tree.creat_graph(g, length, vertex_data, weights)
res = tree.dijkstra(g,'D')
print(res)
stack = []
DFS(res,'A',g)
过程都写在注释里了,可以看代码并理解一下。
三、反思
其实,如果看了(二)的代码,就能发现:如果去掉(二)中的这三行代码:
elif (graph.weight[a][k] + result[a]) == result[k]: if graph.weight[a][k] != 0: re[graph.data[k]].append(graph.data[a])
一样可以做普通的最短路输出(只输出一组)。但是我(一)中代码却并不是按照这个思路去写的。一开始做只输出一组最短路问题,可以从起点往终点搜索。但是,如果记录多组点
stack.append(dic[node][0])
该行代码中的索引过程就无法实现了,而且按照我写的数据结构(值为列表的字典),似乎无法实现从起点向终点的搜索。
因此经过多日的苦思冥想(悲),将搜索过程改为从终点向起点DFS,便终于完成了这项任务。
30行的dijkstra主程序想了三天,14行的DFS过程更是想了一个星期!感慨一下。
可是我觉得,输出多条最短路的算法相比于普通的输出唯一最短路的问题,实际应用广泛得多,因为它可以在最短距离的基础上考虑更多其他要素,因此给人提供了更多的选择,更多的可能性。
其中的数据结构和算法,如果还有值得推敲的地方,可以评论探讨。