グラフ(Graph) は、頂点(ノード, vertex) と 辺(エッジ, edge) からなるデータ構造
:頂点の集合
:辺の集合(頂点のペア)
現実世界の多くの問題をグラフとしてモデル化できる(地図・道路網、SNSの友人関係、タスクの依存関係など)
グラフの種類¶
| 種類 | 説明 |
|---|---|
| 無向グラフ(Undirected) | 辺に向きがない。 と は同じ辺 |
| 有向グラフ(Directed, Digraph) | 辺に向きがある。 と は別の辺 |
| 重み付きグラフ(Weighted) | 各辺にコスト(重み)が付いている |
| DAG(有向非巡回グラフ) | 有向グラフで閉路(サイクル)がないもの |
| 木(Tree) | 連結で閉路のない無向グラフ。$ |
グラフの表現方法¶
隣接行列(Adjacency Matrix)¶
の行列 で表す。(または重み)なら辺 が存在する
辺の存在確認:
隣接頂点の列挙:
空間計算量:(疎なグラフには非効率)
隣接リスト(Adjacency List)¶
各頂点が隣接する頂点のリストを持つ
辺の存在確認:
隣接頂点の列挙:
空間計算量:(疎なグラフに効率的)
from collections import defaultdict, deque
class Graph:
"""隣接リストによる無向グラフ"""
def __init__(self):
self.adj = defaultdict(list)
def add_edge(self, u, v):
self.adj[u].append(v)
self.adj[v].append(u)
def neighbors(self, u):
return self.adj[u]
def vertices(self):
return list(self.adj.keys())
class WeightedGraph:
"""隣接リストによる重み付き有向グラフ"""
def __init__(self):
self.adj = defaultdict(list) # adj[u] = [(v, weight), ...]
def add_edge(self, u, v, weight=1):
self.adj[u].append((v, weight))
def neighbors(self, u):
return self.adj[u]# グラフの構築例
# 0 - 1 - 2
# | |
# 3 ----- 4
g = Graph()
for u, v in [(0, 1), (1, 2), (2, 4), (0, 3), (3, 4)]:
g.add_edge(u, v)
print("頂点0の隣接頂点:", g.neighbors(0))
print("頂点2の隣接頂点:", g.neighbors(2))def bfs(graph: Graph, start):
"""BFS で始点から各頂点への最短距離を返す"""
dist = {start: 0}
queue = deque([start])
while queue:
u = queue.popleft()
for v in graph.neighbors(u):
if v not in dist:
dist[v] = dist[u] + 1
queue.append(v)
return dist
dist = bfs(g, 0)
print("頂点0からの最短距離:", dist)def dfs(graph: Graph, start):
"""DFS で到達できる頂点を返す"""
visited = set()
order = []
def _dfs(u):
visited.add(u)
order.append(u)
for v in graph.neighbors(u):
if v not in visited:
_dfs(v)
_dfs(start)
return order
print("DFS 訪問順(始点=0):", dfs(g, 0))トポロジカルソート¶
DAG(有向非巡回グラフ)の頂点を、すべての辺 において が より前になるよう並べる
タスクの依存関係の解決やビルドシステムなどに使われる
カーン法(Kahn’s algorithm)を使う:入次数(in-degree)が 0 の頂点をキューに入れ、順番に取り出す
計算量:
def topological_sort(adj: dict, vertices: list) -> list | None:
"""カーン法によるトポロジカルソート。閉路があれば None を返す"""
in_degree = defaultdict(int)
for u in vertices:
for v in adj[u]:
in_degree[v] += 1
queue = deque([u for u in vertices if in_degree[u] == 0])
result = []
while queue:
u = queue.popleft()
result.append(u)
for v in adj[u]:
in_degree[v] -= 1
if in_degree[v] == 0:
queue.append(v)
return result if len(result) == len(vertices) else None
# タスクの依存関係: A→C, B→C, C→D
dag_adj = defaultdict(list, {"A": ["C"], "B": ["C"], "C": ["D"], "D": []})
vertices = ["A", "B", "C", "D"]
print("トポロジカルソート:", topological_sort(dag_adj, vertices))import heapq
def dijkstra(graph: WeightedGraph, start):
"""始点から各頂点への最短距離を返す"""
dist = defaultdict(lambda: float("inf"))
dist[start] = 0
heap = [(0, start)] # (距離, 頂点)
while heap:
d, u = heapq.heappop(heap)
if d > dist[u]:
continue
for v, w in graph.neighbors(u):
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
heapq.heappush(heap, (dist[v], v))
return dict(dist)
# 重み付きグラフの例
wg = WeightedGraph()
for u, v, w in [(0, 1, 4), (0, 2, 1), (2, 1, 2), (1, 3, 1), (2, 3, 5)]:
wg.add_edge(u, v, w)
print("頂点0からの最短距離:", dijkstra(wg, 0))まとめ¶
| アルゴリズム | 計算量 | 用途 |
|---|---|---|
| BFS | $O( | V |
| DFS | $O( | V |
| トポロジカルソート(カーン法) | $O( | V |
| ダイクストラ法 | $O(( | V |
グラフ表現の選択
密なグラフ()→ 隣接行列が有利(辺の存在を で確認)
疎なグラフ()→ 隣接リストが有利(空間効率が良い)
競技プログラミングやシステム設計では疎なグラフが多いため、隣接リストが一般的