Python で、ダイクストラ法を使って、重み付きの有向グラフの単一始点最短経路問題を解きます。
以下の続きです。
ダイクストラ法
ダイクストラ法(だいくすとらほう、英: Dijkstra’s algorithm)はグラフ理論における辺の重みが非負数の場合の単一始点最短経路問題を解くための最良優先探索によるアルゴリズムである。
出典: フリー百科事典『ウィキペディア(Wikipedia)』
距離の∞は、 sys.maxsize
を用いて表します。
頂点の集合の優先度付きキューは、heapq
を用いて実装します。
優先度付きキューは以下で扱っています。
以下のようなグラフを探索します。
a を出発地点、e を目的地とした場合、最短経路は「a -> c -> b -> e」、移動距離は7になるはずです。
import heapq import sys class Vertex(object): def __init__(self, id): self.id = id self.adjacent = {} # 開始点からの距離 self.distance = sys.maxsize self.visited = False # 前の点を記録し、最短経路を出力する時に使う 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_visited(self): self.visited= True def set_previous(self, previous_vertex): self.previous = previous_vertex def get_previous(self): return self.previous # heapq を使うために必要。定義しないと以下のようなエラーが出る。 # TypeError: '<' not supported between instances of 'Vertex' and 'Vertex' def __lt__(self, other): return self.distance < other.distance 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 dijkstra(graph, source, destination): # ソースの距離を0に設定 source.set_distance(0) # (距離, 頂点)のタプルのリストを作り、heapq で優先度付きキューにする。 priority_queue = [(vertex.get_distance(), vertex) for vertex in graph] heapq.heapify(priority_queue) # while (Q が空集合ではない ) while len(priority_queue): # Qから距離が最小である頂点を取り出す nearest_vrtex = heapq.heappop(priority_queue) current = nearest_vrtex[1] current.set_visited() # for each ( u からの辺がある各頂点 ) for next in current.adjacent: if next.visited: continue # alt = d(u)+length(u,v) new_distance = current.get_distance() + current.get_weight(next) # if ( d(v) > alt ) if next.get_distance() > new_distance: next.set_distance(new_distance) next.set_previous(current) heapq.heappush(priority_queue, (next.get_distance(), next)) for vertex in graph.vertex_dict.values(): print(f'{source.get_vertex_id()} から {vertex.get_vertex_id()} への移動距離: {vertex.get_distance()}') shortest_path = [destination.get_vertex_id()] while destination.previous: shortest_path.append(destination.previous.get_vertex_id()) destination = destination.previous print(f'{source.get_vertex_id()} から {vertex.get_vertex_id()} への最短移動経路:', shortest_path[::-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_edge('a', 'b', 4) graph.add_edge('a', 'c', 1) graph.add_edge('b', 'e', 4) graph.add_edge('c', 'b', 2) graph.add_edge('c', 'd', 4) graph.add_edge('d', 'e', 4) source = graph.get_vertex('a') destination = graph.get_vertex('e') dijkstra(graph, source, destination) # a から a への移動距離: 0 # a から b への移動距離: 3 # a から c への移動距離: 1 # a から d への移動距離: 5 # a から e への移動距離: 7 # a から e への最短移動経路: ['a', 'c', 'b', 'e']
想定通りの結果を得ることができました。