Python で重み無しの有向グラフの単一始点最短経路問題を解きます。
以下の続きです。
最短経路問題
グラフ理論における最短経路問題(さいたんけいろもんだい、英: shortest path problem)とは、重み付きグラフの与えられた2つのノード間を結ぶ経路の中で、重みが最小の経路を求める最適化問題である。
出典: フリー百科事典『ウィキペディア(Wikipedia)』
辺の重みのない有向グラフの単一始点からの最短経路問題は、幅優先探索を使って解くことができます。
無向グラフの幅優先探索は、以下で扱っています。
有向グラフでは、スタート地点からの距離と移動前の点をそれぞれの点が属性として持ち、幅優先探索の際にこの属性を更新していくことで、最終的に最短経路を得ることができます。
以下のようなグラフを探索します。
import collections
class Vertex(object):
def __init__(self, id):
self.id = id
self.adjacent = {}
# 開始点からの距離
self.distance = -1
# 前の点を記録し、最短経路を出力する時に使う
self.previous = None
def add_neighbor(self, neighbor, weight=0):
self.adjacent[neighbor] = weight
def get_neighbors(self):
return self.adjacent
def get_connections(self):
return self.adjacent.keys()
def get_vertex_id(self):
return self.id
def get_weight(self, neighbor):
return self.adjacent[neighbor]
def set_distance(self, distance):
self.distance = distance
def get_distance(self):
return self.distance
def set_previous(self, previous_vertex):
self.previous = previous_vertex
def get_previous(self):
return self.previous
class Graph(object):
def __init__(self):
self.vertex_dict = {}
self.num_vertex = 0
def __iter__(self):
return iter(self.vertex_dict.values())
def add_vertex(self, id):
self.num_vertex = self.num_vertex + 1
new_vertex = Vertex(id)
self.vertex_dict[id] = new_vertex
return new_vertex
def get_vertex(self, id):
if id in self.vertex_dict:
return self.vertex_dict[id]
else:
return None
def add_edge(self, frm, to, weight=0):
if frm not in self.vertex_dict:
self.add_vertex(frm)
if to not in self.vertex_dict:
self.add_vertex(to)
self.vertex_dict[frm].add_neighbor(self.vertex_dict[to], weight)
def get_vertices(self):
return self.vertex_dict.keys()
def get_edges(self):
edges = []
for v in self.vertex_dict.values():
for w in v.get_connections():
vid = v.get_vertex_id()
wid = w.get_vertex_id()
edges.append((vid, wid, v.get_weight(w)))
return edges
def solve_shortest_path_unweighted_graph(graph, start):
start = graph.get_vertex(start)
start.set_distance(0)
start.set_previous(None)
queue = collections.deque()
queue.append(start)
while queue:
current_vertex = queue.popleft()
for neighbor in current_vertex.get_connections():
if neighbor.get_distance() == -1:
neighbor.set_distance(current_vertex.get_distance() + 1)
neighbor.set_previous(current_vertex)
queue.append(neighbor)
for vertex in graph.vertex_dict.values():
if vertex.get_distance() == -1:
print(f'{start.get_vertex_id()} から {vertex.get_vertex_id()} へは移動できません。')
continue
elif vertex.get_distance() == 0:
continue
else:
print(f'{start.get_vertex_id()} から {vertex.get_vertex_id()} の最短距離は {vertex.get_distance()}。', end='')
# 最短経路の出力
if vertex.get_distance()>0:
route = [vertex.get_vertex_id()]
current_vertex = vertex
while current_vertex.get_previous():
route.append(current_vertex.get_previous().get_vertex_id())
current_vertex = current_vertex.get_previous()
print('移動ルート', route[::-1])
if __name__ == '__main__':
graph = Graph()
graph.add_vertex('a')
graph.add_vertex('b')
graph.add_vertex('c')
graph.add_vertex('d')
graph.add_vertex('e')
graph.add_vertex('f')
graph.add_vertex('g')
graph.add_edge('a', 'b', 1)
graph.add_edge('a', 'd', 1)
graph.add_edge('b', 'd', 1)
graph.add_edge('b', 'e', 1)
graph.add_edge('c', 'a', 1)
graph.add_edge('c', 'f', 1)
graph.add_edge('d', 'g', 1)
graph.add_edge('e', 'g', 1)
graph.add_edge('g', 'f', 1)
solve_shortest_path_unweighted_graph(graph, 'a')
# a から b の最短距離は 1。移動ルート ['a', 'b']
# a から c へは移動できません。
# a から d の最短距離は 1。移動ルート ['a', 'd']
# a から e の最短距離は 2。移動ルート ['a', 'b', 'e']
# a から f の最短距離は 3。移動ルート ['a', 'd', 'g', 'f']
# a から g の最短距離は 2。移動ルート ['a', 'd', 'g']
想定通りの結果を得ることができました。