無向グラフをBFSで探索するアルゴリズムを Python で記述します。
以下の続きです。
幅優先探索
幅優先探索(はばゆうせんたんさく、英: breadth first search)はグラフ理論(Graph theory)において木構造(tree structure)やグラフ(graph)の探索に用いられるアルゴリズム。アルゴリズムは根ノードで始まり隣接した全てのノードを探索する。それからこれらの最も近いノードのそれぞれに対して同様のことを繰り返して探索対象ノードをみつける。
出典: フリー百科事典『ウィキペディア(Wikipedia)』
幅優先探索は、Queue を用いて実装します。
Queue は以下を用います。
以下のグラフを探索します。
以前のコードに下記を追加します。
- deque を使うため、1行目で collection を import する。
- visited を DFS、BFS で共用できるよう、29行目に visited フラグを False にする関数を追加し、探索を開始する際に、 visited フラグ を全て False にするようにする。
- 95行目と107行目で BFS を実装する。
import collections
class Vertex(object):
def __init__(self, id):
self.id = id
self.adjacent = {}
# DFS BFS 共用フラグ
self.visited = False
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]
# DFS BFS 共用フラグ
def set_visited(self):
self.visited = True
def unset_visited(self):
self.visited = False
class Graph(object):
def __init__(self):
self.vertex_dict = {}
self.num_vertex = 0
# DFS ループで扱いやすくする。
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)
# 有向グラフの場合は別
self.vertex_dict[to].add_neighbor(self.vertex_dict[frm], 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 dfs(graph, current_vertex, traversal_verticies):
current_vertex.set_visited()
traversal_verticies.append(current_vertex.get_vertex_id())
for neighbor in current_vertex.get_connections():
if not(neighbor.visited):
dfs(graph, neighbor, traversal_verticies)
def dfs_traversal(graph):
# visitedフラグの初期化
for vertex in graph:
vertex.unset_visited()
for current_vertex in graph:
traversal_verticies = []
if not(current_vertex.visited):
dfs(graph, current_vertex, traversal_verticies)
if traversal_verticies:
print(f'DFS start from {current_vertex.get_vertex_id()}:', traversal_verticies)
def bfs(graph, start, traversal_verticies):
queue = collections.deque()
queue.append(start)
while len(queue) > 0:
current_vertex = queue.popleft()
traversal_verticies.append(current_vertex.get_vertex_id())
for neighbor in current_vertex.get_connections():
if not(neighbor.visited) and (neighbor not in queue):
queue.append(neighbor)
current_vertex.visited = True
def bfs_traversal(graph):
# visitedフラグの初期化
for vertex in graph:
vertex.unset_visited()
for start_vertex in graph:
traversal_verticies = []
if not(start_vertex.visited):
bfs(graph, start_vertex, traversal_verticies)
if traversal_verticies:
print(f'BFS start from {start_vertex.get_vertex_id()}:', traversal_verticies)
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_edge('a', 'b', 1)
graph.add_edge('a', 'c', 1)
graph.add_edge('a', 'e', 1)
graph.add_edge('b', 'd', 1)
graph.add_edge('b', 'e', 1)
graph.add_edge('c', 'd', 1)
graph.add_edge('c', 'e', 1)
graph.add_edge('d', 'e', 1)
dfs_traversal(graph)
# start from a: ['a', 'b', 'd', 'c', 'e']
# start from f: ['f']
bfs_traversal(graph)
# start from a: ['a', 'b', 'c', 'e', 'd']
# start from f: ['f']
想定通りの結果になりました。