グラフアルゴリズム

頂点と辺からなるデータ構造を操作するアルゴリズム。

グラフ
頂点と辺からなるデータ構造。
頂点
グラフの点。ノードとも呼ばれる。
頂点を結ぶ線。
隣接リスト
各頂点の隣接する頂点をリストで表現する方法。
隣接行列
頂点間の接続を行列で表現する方法。
幅優先探索
グラフを幅優先で探索するアルゴリズム。
深さ優先探索
グラフを深さ優先で探索するアルゴリズム。
ダイクストラ法
重み付きグラフで最短経路を求めるアルゴリズム。

グラフの表現

地図を描く

グラフは「地図を描く」ようなもの。都市を頂点、道路を辺として表現します。都市間の道路があれば、それらの頂点を線で結びます。これにより、都市間の移動経路を表現できます。

なぜグラフが必要か?

グラフは、現実世界の複雑な関係を表現するのに適しています。ソーシャルネットワーク、経路探索、ウェブページのリンク構造など、多くの実用的な問題で使用されます。

いつ使うか?

  • ソーシャルネットワークの分析
  • 経路探索
  • ウェブページのリンク構造
  • 推薦システム

実践テクニック

隣接リストと隣接行列の使い分け

隣接リストはスパースグラフ(辺が少ないグラフ)に適しており、メモリ効率が良いです。隣接行列はデンスグラフ(辺が多いグラフ)に適しており、辺の存在確認がO(1)でできます。

有向グラフと無向グラフ

有向グラフは、辺に方向があるグラフです。無向グラフは、辺に方向がないグラフです。問題の性質に応じて、適切なグラフを選択する必要があります。

Graph Representation
# グラフの表現方法
# 隣接リスト
graph = {
0: [1, 2],
1: [0, 2, 3],
2: [0, 1, 3],
3: [1, 2]
}
# 隣接行列
import numpy as np
graph = np.array([
[0, 1, 1, 0],
[1, 0, 1, 1],
[1, 1, 0, 1],
[0, 1, 1, 0]
])

幅優先探索

水が広がるように

幅優先探索は「水が広がるように」探索する方法。ある地点から水を注ぎ、四方八方へ広がっていきます。これにより、最短経路を見つけることができます。

なぜ幅優先探索が必要か?

幅優先探索は、最短経路を見つけるのに適しています。時間計算量はO(V + E)で、グラフの頂点数と辺数に比例します。特に、重みなしグラフの最短経路探索に適しています。

いつ使うか?

  • 最短経路を見つける場合
  • レベル順に探索する場合
  • 連結成分を見つける場合
  • 重みなしグラフの場合

実践テクニック

キューを使う

幅優先探索では、キューを使って探索順序を管理します。`collections.deque`を使うことで、効率的なキュー操作が可能です。

訪問済みを管理する

訪問済みの頂点を管理することで、無限ループを回避します。`set`を使うことで、効率的な訪問済み管理が可能です。

Breadth-First Search
# 幅優先探索の実装
from collections import deque
def bfs(graph, start):
visited = set()
queue = deque([start])
visited.add(start)
while queue:
vertex = queue.popleft()
print(vertex)
for neighbor in graph[vertex]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
# 使用例
graph = {
0: [1, 2],
1: [0, 2, 3],
2: [0, 1, 3],
3: [1, 2]
}
bfs(graph, 0)

深さ優先探索

迷路を進む

深さ優先探索は「迷路を進む」ようなもの。ある道をできるだけ奥まで進み、行き止まりになったら戻って別の道を探します。これにより、すべての道を探索できます。

なぜ深さ優先探索が必要か?

深さ優先探索は、すべての頂点を探索するのに適しています。時間計算量はO(V + E)で、グラフの頂点数と辺数に比例します。特に、サイクル検出やトポロジカルソートに適しています。

いつ使うか?

  • すべての頂点を探索する場合
  • サイクル検出
  • トポロジカルソート
  • パスを見つける場合

実践テクニック

再帰と反復の使い分け

深さ優先探索は、再帰と反復の両方で実装できます。再帰は簡潔ですが、スタックオーバーフローのリスクがあります。反復は少し複雑ですが、メモリ効率が良いです。

訪問済みを管理する

訪問済みの頂点を管理することで、無限ループを回避します。`set`を使うことで、効率的な訪問済み管理が可能です。

Depth-First Search
# 深さ優先探索の実装
def dfs(graph, start, visited=None):
if visited is None:
visited = set()
visited.add(start)
print(start)
for neighbor in graph[start]:
if neighbor not in visited:
dfs(graph, neighbor, visited)
# 使用例
graph = {
0: [1, 2],
1: [0, 2, 3],
2: [0, 1, 3],
3: [1, 2]
}
dfs(graph, 0)

ダイクストラ法

最短ルートを見つける

ダイクストラ法は「最短ルートを見つける」アルゴリズム。ある地点から各都市への最短距離を計算します。これにより、最短経路を見つけることができます。

なぜダイクストラ法が必要か?

ダイクストラ法は、重み付きグラフで最短経路を見つけるのに適しています。時間計算量はO((V + E) log V)で、優先度付きキューを使うことで効率的に実装できます。

いつ使うか?

  • 重み付きグラフで最短経路を見つける場合
  • GPSナビゲーション
  • ネットワークルーティング
  • リソース配分

実践テクニック

優先度付きキューを使う

ダイクストラ法では、優先度付きキューを使って探索順序を管理します。`heapq`モジュールを使うことで、効率的な優先度付きキュー操作が可能です。

距離を管理する

各頂点への距離を管理することで、最短経路を計算します。初期値を無限大に設定し、探索中に更新します。

Dijkstra's Algorithm
# ダイクストラ法の実装
import heapq
def dijkstra(graph, start):
distances = {vertex: float('infinity') for vertex in graph}
distances[start] = 0
priority_queue = [(0, start)]
while priority_queue:
current_distance, current_vertex = heapq.heappop(priority_queue)
if current_distance > distances[current_vertex]:
continue
for neighbor, weight in graph[current_vertex].items():
distance = current_distance + weight
if distance < distances[neighbor]:
distances[neighbor] = distance
heapq.heappush(priority_queue, (distance, neighbor))
return distances
# 使用例
graph = {
0: {1: 4, 2: 1},
1: {2: 2, 3: 5},
2: {1: 1, 3: 8},
3: {}
}
distances = dijkstra(graph, 0)
print(distances)

比較

グラフアルゴリズムの特徴を比較する。

特徴 幅優先探索 深さ優先探索 ダイクストラ法
時間計算量 O(V + E) O(V + E) O((V + E) log V)
空間計算量 O(V) O(V) O(V)
重み なし なし あり
最短経路 はい いいえ はい

使用例

グラフアルゴリズムの実際の使用例を確認する。

Use Cases
# グラフアルゴリズムの使用例
# ソーシャルネットワークの分析
social_network = {
"Alice": ["Bob", "Charlie"],
"Bob": ["Alice", "Charlie", "David"],
"Charlie": ["Alice", "Bob", "David"],
"David": ["Bob", "Charlie"]
}
# 幅優先探索で友達の友達を探す
def find_friends_of_friends(graph, person, depth=2):
visited = set()
queue = deque([(person, 0)])
visited.add(person)
friends_of_friends = []
while queue:
current, current_depth = queue.popleft()
if current_depth == depth:
continue
for friend in graph[current]:
if friend not in visited:
visited.add(friend)
queue.append((friend, current_depth + 1))
if current_depth == 1:
friends_of_friends.append(friend)
return friends_of_friends
friends_of_friends = find_friends_of_friends(social_network, "Alice")
print(f"Aliceの友達の友達: {friends_of_friends}")
# 経路探索
def find_shortest_path(graph, start, end):
visited = set()
queue = deque([(start, [start])])
visited.add(start)
while queue:
vertex, path = queue.popleft()
if vertex == end:
return path
for neighbor in graph[vertex]:
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, path + [neighbor]))
return None
shortest_path = find_shortest_path(social_network, "Alice", "David")
print(f"最短経路: {shortest_path}")

合格ライン

グラフを表現できる
幅優先探索を実装できる
深さ優先探索を実装できる
ダイクストラ法を実装できる
グラフアルゴリズムの使い分けができる

演習課題

課題1: グラフの表現
隣接リストと隣接行列の両方でグラフを表現してください。
課題2: 幅優先探索の実装
幅優先探索を実装し、グラフから最短経路を検索してください。
課題3: ダイクストラ法の実装
ダイクストラ法を実装し、重み付きグラフから最短経路を検索してください。

参考文献

この記事は以下の公的ガイドライン/標準に基づいています。