8.3.2 Uniform Cost Search算法的基本思想
实例8-4:罗马尼亚之旅”的路径规划程序(codes/8/london)
(1)文件UK_cities.json包含了英国城市之间的距离信息。每个城市都有一个键值对,其中键是城市名称,值是一个字典,表示从当前城市到其他城市的距离。
{
"aberdeen": {
"aberystwyth": {"weight":427},
"edinburgh": {"weight":115},
"penzance": {"weight":663},
"portsmouth": {"weight":550},
"sheffield": {"weight":338},
"swansea": {"weight":502},
"york": {"weight":299}
},
"aberystwyth": {
"brighton": {"weight":249} ,
"bristol": {"weight":121} ,
"carlisle": {"weight":219} ,
"dover": {"weight":284} ,
"newcastle": {"weight":254} ,
"nottingham": {"weight":154} ,
"oxford": {"weight":156} ,
"penzance": {"weight":304} ,
"sheffield": {"weight":154} ,
"swansea": {"weight":75} ,
"york": {"weight":190}
},
"birmingham": {
"brighton": {"weight":159},
"bristol": {"weight":86},
"cambridge": {"weight":97},
"cardiff": {"weight":100},
"carlisle": {"weight":198},
"portsmouth": {"weight":141},
"sheffield": {"weight":75},
"swansea": {"weight":125},
"york": {"weight":125},
"london": {"weight":110}
},
//省略部分代码
"portsmouth":{
"sheffield": {"weight":214},
"york": {"weight":256}
},
"sheffield":{
"swansea": {"weight":196}
},
"swansea":{
"york": {"weight":248}
}
}
在上述代码的每个城市的值字典中,键是目标城市的名称,值是一个包含"weight"键的字典,表示到目标城市的距离。例如,城市"aberdeen"到其他城市的距离信息如下:
- "aberystwyth":距离为427
- "edinburgh":距离为115
- "penzance":距离为663
- "portsmouth":距离为550
- "sheffield":距离为338
- "swansea":距离为502
- "york":距离为299
以此类推,其他城市之间的距离信息也以类似的格式给出。这种数据结构可以被用来构建一个图,其中城市是节点,距离是边,用来解决路径规划问题。
(2)文件graphCreation.py包含如下所示的两个功能:
- 函数show_weighted_graph用于绘制带权重的图形,其中节点代表城市,边代表城市之间的连接,边的权重表示连接的距离。
- 函数load_graph_from_file的功能是从给定的 JSON 文件中加载城市之间的距离信息,并创建一个带权重的图对象。
import networkx as nx
import matplotlib.pyplot as plt
import json
def show_weighted_graph(networkx_graph, node_size, font_size, fig_size):
# 为了给每个节点分配足够的空间,使用给定的fig_size创建图形
plt.figure(num=None, figsize=fig_size, dpi=80)
plt.axis('off')
# 计算每个顶点的位置,以便漂亮地显示它们
nodes_position = nx.spring_layout(networkx_graph)
# 可以根据图形更改不同的布局,取图形中每条边对应的权重
edges_weights = nx.get_edge_attributes(networkx_graph,'weight')
# 绘制节点(您可以更改颜色)
nx.draw_networkx_nodes(networkx_graph, nodes_position, node_size=node_size,
node_color=["orange"] * networkx_graph.number_of_nodes())
# 仅绘制边
nx.draw_networkx_edges(networkx_graph, nodes_position, edgelist=list(networkx_graph.edges), width=2)
# 添加权重
nx.draw_networkx_edge_labels(networkx_graph, nodes_position, edge_labels=edges_weights)
# 添加节点的标签
nx.draw_networkx_labels(networkx_graph, nodes_position, font_size=font_size,
font_family='sans-serif')
plt.axis('off')
plt.show()
def load_graph_from_file(filename):
with open(filename) as uk_cities:
dict_cities = json.load(uk_cities)
return nx.Graph(dict_cities)
(3)文件ucs.py实现了Uniform Cost Search图搜索算法,用于在加权图中找到从起点到目标点的最低成本路径。此外,在文件ucs.py中还实现了函数show_weighted_graph,用于可视化加权图。在代码的末尾,使用load_graph_from_file从JSON文件中加载加权图,并使用uniformCostSearch函数寻找从伦敦到阿伯丁的最低成本路径,并通过show_weighted_graph函数可视化加权图。
def uniformCostSearch(graph, origin, goal):
frontier = [] # 前沿队列
frontierIndex = {} # 保存前沿队列中元素的字典
node = (0, origin, [origin]) # 起始节点,格式为(路径成本, 节点, 访问路径)
frontierIndex[node[1]] = [node[0], node[2]] # 在字典中保存起始节点的信息
heapq.heappush(frontier, node) # 将起始节点放入前沿队列
explored = set() # 已探索节点集合
while frontier:
if len(frontier) == 0:
print("No solution found.") # 若未找到路径,输出提示信息
return None
node = heapq.heappop(frontier) # 弹出路径成本最低的节点
del frontierIndex[node[1]] # 从字典中删除已弹出的节点信息
if node[1] == goal: # 检查是否找到目标节点
print("Goal node reached.") # 输出找到目标节点的信息
return node
explored.add(node[1]) # 将节点标记为已探索
neighbours = list(graph.neighbors(node[1])) # 获取节点的邻居节点列表
path = node[2]
for child in neighbours:
path.append(child)
childNode = (node[0] + graph.get_edge_data(node[1], child)["weight"], child, path) # 计算邻居节点的路径成本
if child not in explored and child not in frontierIndex: # 如果邻居节点未探索且不在前沿队列中
heapq.heappush(frontier, childNode) # 将邻居节点加入前沿队列
frontierIndex[child] = [childNode[0], childNode[2]] # 在字典中保存邻居节点信息
elif child in frontierIndex: # 如果邻居节点已在前沿队列中
if childNode[0] < frontierIndex[child][0]: # 检查新路径成本是否更低
nodeToRemove = (frontierIndex[child][0], child, frontierIndex[child][1])
frontier.remove(nodeToRemove) # 删除原来的节点
heapq.heapify(frontier)
del frontierIndex[child]
heapq.heappush(frontier, childNode) # 将更新后的节点加入前沿队列
frontierIndex[child] = [childNode[0], childNode[2]]
path = path[:-1]
print("Explored nodes: {}".format(explored)) # 打印已探索节点集合
def draw_path(graph, path):
pos = nx.spring_layout(graph) # 计算节点的布局
nx.draw(graph, pos, with_labels=True, node_color="orange", node_size=1000) # 绘制节点
nx.draw_networkx_nodes(graph, pos, nodelist=path, node_color='orange', node_size=1000) # 绘制路径节点
edges = [(path[i], path[i + 1]) for i in range(len(path) - 1)] # 提取路径中的边
nx.draw_networkx_edges(graph, pos, edgelist=edges, edge_color='red', width=2) # 绘制路径边
plt.axis('off') # 关闭坐标轴
plt.show() # 显示图形
uk_map = gc.load_graph_from_file("UK_cities.json") # 从JSON文件中加载图
solution = uniformCostSearch(uk_map, "london", "aberdeen") # 使用统一代价搜索算法寻找最低成本路径
if solution:
print("SOLUTION: {}".format(solution))
path_nodes = solution[2] # 获取最低成本路径节点列表
print("Path nodes: {}".format(path_nodes)) # 打印最低成本路径节点列表
draw_path(uk_map, path_nodes) # 绘制最低成本路径
上述代码的实现流程如下所示。
- 首先,实现了Uniform Cost Search算法函数uniformCostSearch,用于在给定图中寻找两个节点之间的最低成本路径。算法使用了前沿队列来管理待探索的节点,并使用字典来追踪前沿队列中的节点信息。在搜索过程中,算法从前沿队列中弹出路径成本最低的节点,并将其邻居节点加入队列,直到找到目标节点或者搜索完成。
- 接着,加载了地图数据,并调用统一代价搜索算法寻找从伦敦到阿伯丁的最低成本路径。在搜索过程中,会打印输出如下所示的详细的探索信息,包括探索的节点和路径成本。
Explored nodes: {'london'}
Explored nodes: {'london', 'brighton'}
Explored nodes: {'cambridge', 'london', 'brighton'}
Explored nodes: {'oxford', 'cambridge', 'london', 'brighton'}
Explored nodes: {'oxford', 'dover', 'brighton', 'cambridge', 'london'}
Explored nodes: {'oxford', 'dover', 'brighton', 'cambridge', 'london', 'portsmouth'}
Explored nodes: {'oxford', 'dover', 'brighton', 'cambridge', 'london', 'birmingham', 'portsmouth'}
Explored nodes: {'oxford', 'dover', 'brighton', 'bristol', 'cambridge', 'london', 'birmingham', 'portsmouth'}
Explored nodes: {'oxford', 'dover', 'brighton', 'nottingham', 'bristol', 'cambridge', 'london', 'birmingham', 'portsmouth'}
Explored nodes: {'oxford', 'dover', 'brighton', 'nottingham', 'bristol', 'cambridge', 'cardiff', 'london', 'birmingham', 'portsmouth'}
Explored nodes: {'oxford', 'dover', 'brighton', 'nottingham', 'bristol', 'cambridge', 'cardiff', 'london', 'birmingham', 'portsmouth', 'sheffield'}
Explored nodes: {'oxford', 'dover', 'brighton', 'nottingham', 'bristol', 'cambridge', 'cardiff', 'london', 'birmingham', 'portsmouth', 'sheffield', 'exeter'}
Explored nodes: {'oxford', 'dover', 'brighton', 'nottingham', 'bristol', 'cambridge', 'hull', 'cardiff', 'london', 'birmingham', 'portsmouth', 'sheffield', 'exeter'}
Explored nodes: {'oxford', 'dover', 'brighton', 'nottingham', 'bristol', 'cambridge', 'leeds', 'hull', 'cardiff', 'london', 'birmingham', 'portsmouth', 'sheffield', 'exeter'}
Explored nodes: {'oxford', 'dover', 'brighton', 'nottingham', 'bristol', 'cambridge', 'leeds', 'hull', 'cardiff', 'liverpool', 'london', 'birmingham', 'portsmouth', 'sheffield', 'exeter'}
Explored nodes: {'oxford', 'dover', 'brighton', 'nottingham', 'bristol', 'cambridge', 'leeds', 'hull', 'manchester', 'cardiff', 'liverpool', 'london', 'birmingham', 'portsmouth', 'sheffield', 'exeter'}
Explored nodes: {'oxford', 'dover', 'brighton', 'nottingham', 'bristol', 'cambridge', 'leeds', 'hull', 'manchester', 'cardiff', 'liverpool', 'london', 'birmingham', 'portsmouth', 'swansea', 'sheffield', 'exeter'}
Explored nodes: {'oxford', 'dover', 'york', 'brighton', 'nottingham', 'bristol', 'cambridge', 'leeds', 'hull', 'manchester', 'cardiff', 'liverpool', 'london', 'birmingham', 'portsmouth', 'swansea', 'sheffield', 'exeter'}
Explored nodes: {'oxford', 'nottingham', 'bristol', 'manchester', 'liverpool', 'leeds', 'london', 'portsmouth', 'sheffield', 'york', 'brighton', 'hull', 'aberystwyth', 'birmingham', 'dover', 'cambridge', 'cardiff', 'exeter', 'swansea'}
Explored nodes: {'oxford', 'nottingham', 'bristol', 'manchester', 'newcastle', 'liverpool', 'leeds', 'london', 'portsmouth', 'sheffield', 'york', 'brighton', 'hull', 'aberystwyth', 'birmingham', 'dover', 'cambridge', 'cardiff', 'exeter', 'swansea'}
Explored nodes: {'oxford', 'nottingham', 'bristol', 'manchester', 'newcastle', 'liverpool', 'carlisle', 'leeds', 'london', 'portsmouth', 'sheffield', 'york', 'brighton', 'hull', 'aberystwyth', 'birmingham', 'dover', 'cambridge', 'cardiff', 'exeter', 'swansea'}
Explored nodes: {'oxford', 'nottingham', 'bristol', 'manchester', 'newcastle', 'liverpool', 'penzance', 'carlisle', 'leeds', 'london', 'portsmouth', 'sheffield', 'york', 'brighton', 'hull', 'aberystwyth', 'birmingham', 'dover', 'cambridge', 'cardiff', 'exeter', 'swansea'}
Explored nodes: {'oxford', 'edinburgh', 'nottingham', 'bristol', 'manchester', 'newcastle', 'liverpool', 'penzance', 'carlisle', 'leeds', 'london', 'portsmouth', 'sheffield', 'york', 'brighton', 'hull', 'aberystwyth', 'birmingham', 'dover', 'cambridge', 'cardiff', 'exeter', 'swansea'}
Explored nodes: {'oxford', 'edinburgh', 'nottingham', 'bristol', 'manchester', 'newcastle', 'liverpool', 'penzance', 'glasgow', 'carlisle', 'leeds', 'london', 'portsmouth', 'sheffield', 'york', 'brighton', 'hull', 'aberystwyth', 'birmingham', 'dover', 'cambridge', 'cardiff', 'exeter', 'swansea'}
Goal node reached.
SOLUTION: (502, 'aberdeen', ['london', 'cambridge', 'york', 'aberdeen'])
Path nodes: ['london', 'cambridge', 'york', 'aberdeen']
- 最后,可视化展示地图数据信息,并将找到的最低成本路径用红色线条标识出来,以便直观地观察到路径。如图8-3所示。
图8-3 地图数据和路径可视化图