付録B: アルゴリズム実装例
本書で学習する理論的アルゴリズムのPython実装例と複雑性解析を示します。
第1章: 数学的基礎
1.1 最大公約数(ユークリッドアルゴリズム)
def gcd(a, b):
"""
ユークリッドアルゴリズムによる最大公約数の計算
時間複雑性: O(log(min(a, b)))
空間複雑性: O(log(min(a, b))) (再帰による)
"""
if b == 0:
return a
return gcd(b, a % b)
def gcd_iterative(a, b):
"""
反復版ユークリッドアルゴリズム
時間複雑性: O(log(min(a, b)))
空間複雑性: O(1)
"""
while b != 0:
a, b = b, a % b
return a
# 使用例
print(f"gcd(48, 18) = {gcd(48, 18)}") # 6
print(f"gcd_iterative(48, 18) = {gcd_iterative(48, 18)}") # 6
1.2 拡張ユークリッドアルゴリズム
def extended_gcd(a, b):
"""
拡張ユークリッドアルゴリズム
ax + by = gcd(a, b) を満たす x, y を求める
返り値: (gcd, x, y)
時間複雑性: O(log(min(a, b)))
"""
if b == 0:
return a, 1, 0
gcd_val, x1, y1 = extended_gcd(b, a % b)
x = y1
y = x1 - (a // b) * y1
return gcd_val, x, y
# 使用例
gcd_val, x, y = extended_gcd(48, 18)
print(f"gcd(48, 18) = {gcd_val}")
print(f"48 * {x} + 18 * {y} = {gcd_val}") # 48 * (-1) + 18 * 3 = 6
1.3 べき乗計算(高速べき乗)
def fast_power(base, exp, mod=None):
"""
高速べき乗アルゴリズム
時間複雑性: O(log exp)
空間複雑性: O(1)
"""
result = 1
base = base % mod if mod else base
while exp > 0:
if exp & 1: # expが奇数の場合
result = (result * base) % mod if mod else result * base
exp >>= 1 # exp = exp // 2
base = (base * base) % mod if mod else base * base
return result
# 使用例
print(f"2^10 = {fast_power(2, 10)}") # 1024
print(f"2^10 mod 1000 = {fast_power(2, 10, 1000)}") # 24
第3章: 形式言語とオートマトン理論
3.1 有限オートマトンシミュレーター
class FiniteAutomaton:
"""
決定性有限オートマトン(DFA)のシミュレーター
"""
def __init__(self, states, alphabet, transitions, start_state, accept_states):
self.states = set(states)
self.alphabet = set(alphabet)
self.transitions = transitions # {(state, symbol): next_state}
self.start_state = start_state
self.accept_states = set(accept_states)
def process_string(self, input_string):
"""
文字列を処理して受理するかどうかを判定
時間複雑性: O(|input_string|)
空間複雑性: O(1)
"""
current_state = self.start_state
for symbol in input_string:
if symbol not in self.alphabet:
return False
if (current_state, symbol) not in self.transitions:
return False
current_state = self.transitions[(current_state, symbol)]
return current_state in self.accept_states
# 使用例: (ab)*を受理するDFA
dfa = FiniteAutomaton(
states=['q0', 'q1'],
alphabet=['a', 'b'],
transitions={
('q0', 'a'): 'q1',
('q1', 'b'): 'q0'
},
start_state='q0',
accept_states=['q0']
)
test_strings = ['', 'ab', 'abab', 'aba', 'ba']
for s in test_strings:
result = dfa.process_string(s)
print(f"'{s}': {'Accepted' if result else 'Rejected'}")
3.2 非決定性有限オートマトン(NFA)
class NonDeterministicFiniteAutomaton:
"""
非決定性有限オートマトン(NFA)のシミュレーター
"""
def __init__(self, states, alphabet, transitions, start_state, accept_states):
self.states = set(states)
self.alphabet = set(alphabet)
self.transitions = transitions # {(state, symbol): [next_states]}
self.start_state = start_state
self.accept_states = set(accept_states)
def epsilon_closure(self, states):
"""
ε遷移の閉包を計算
時間複雑性: O(|states|^2)
"""
closure = set(states)
stack = list(states)
while stack:
state = stack.pop()
if (state, '') in self.transitions: # ε遷移
for next_state in self.transitions[(state, '')]:
if next_state not in closure:
closure.add(next_state)
stack.append(next_state)
return closure
def process_string(self, input_string):
"""
文字列を処理して受理するかどうかを判定
時間複雑性: O(|input_string| * |states|^2)
空間複雑性: O(|states|)
"""
current_states = self.epsilon_closure({self.start_state})
for symbol in input_string:
if symbol not in self.alphabet:
return False
next_states = set()
for state in current_states:
if (state, symbol) in self.transitions:
next_states.update(self.transitions[(state, symbol)])
current_states = self.epsilon_closure(next_states)
return bool(current_states & self.accept_states)
第5章: 計算複雑性理論
5.1 サブセット和問題(NP完全問題の例)
def subset_sum_brute_force(numbers, target):
"""
サブセット和問題の総当たり解法
時間複雑性: O(2^n)
空間複雑性: O(n)
"""
n = len(numbers)
# すべての部分集合を生成
for mask in range(1 << n): # 2^n通り
subset_sum = 0
subset = []
for i in range(n):
if mask & (1 << i):
subset_sum += numbers[i]
subset.append(numbers[i])
if subset_sum == target:
return True, subset
return False, []
def subset_sum_dp(numbers, target):
"""
サブセット和問題の動的計画法による解法
時間複雑性: O(n * target)
空間複雑性: O(n * target)
"""
n = len(numbers)
# dp[i][j] = i番目までの数で和jが作れるか
dp = [[False] * (target + 1) for _ in range(n + 1)]
# 和0は常に作れる(空集合)
for i in range(n + 1):
dp[i][0] = True
for i in range(1, n + 1):
for j in range(target + 1):
# i番目の数を使わない場合
dp[i][j] = dp[i-1][j]
# i番目の数を使う場合
if j >= numbers[i-1]:
dp[i][j] = dp[i][j] or dp[i-1][j - numbers[i-1]]
return dp[n][target]
# 使用例
numbers = [3, 34, 4, 12, 5, 2]
target = 9
result, subset = subset_sum_brute_force(numbers, target)
print(f"Brute force: {result}, subset: {subset}")
result_dp = subset_sum_dp(numbers, target)
print(f"Dynamic programming: {result_dp}")
5.2 3-SAT問題(NP完全問題)
def evaluate_3sat_clause(clause, assignment):
"""
3-SAT節の評価
clause: [(variable, negated), ...]
assignment: {variable: boolean}
"""
for var, negated in clause:
value = assignment.get(var, False)
if negated:
value = not value
if value: # 節が真になる
return True
return False
def solve_3sat_brute_force(clauses, variables):
"""
3-SAT問題の総当たり解法
時間複雑性: O(2^n * m) where n=|variables|, m=|clauses|
空間複雑性: O(n)
"""
n = len(variables)
# すべての真偽値割当を試す
for mask in range(1 << n):
assignment = {}
for i, var in enumerate(variables):
assignment[var] = bool(mask & (1 << i))
# すべての節が満足されるかチェック
all_satisfied = True
for clause in clauses:
if not evaluate_3sat_clause(clause, assignment):
all_satisfied = False
break
if all_satisfied:
return True, assignment
return False, {}
# 使用例: (x1 ∨ ¬x2 ∨ x3) ∧ (¬x1 ∨ x2 ∨ ¬x3) ∧ (x1 ∨ x2 ∨ x3)
clauses = [
[('x1', False), ('x2', True), ('x3', False)], # x1 ∨ ¬x2 ∨ x3
[('x1', True), ('x2', False), ('x3', True)], # ¬x1 ∨ x2 ∨ ¬x3
[('x1', False), ('x2', False), ('x3', False)] # x1 ∨ x2 ∨ x3
]
variables = ['x1', 'x2', 'x3']
satisfiable, assignment = solve_3sat_brute_force(clauses, variables)
print(f"3-SAT satisfiable: {satisfiable}")
if satisfiable:
print(f"Assignment: {assignment}")
第6章: アルゴリズムの数学的解析
6.1 マージソート
def merge_sort(arr):
"""
マージソート
時間複雑性: O(n log n)
空間複雑性: O(n)
"""
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(left, right):
"""
ソート済み配列のマージ
時間複雑性: O(n)
"""
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
# 使用例
arr = [64, 34, 25, 12, 22, 11, 90]
sorted_arr = merge_sort(arr)
print(f"Original: {arr}")
print(f"Sorted: {sorted_arr}")
6.2 クイックソート
import random
def quicksort(arr, low=0, high=None):
"""
クイックソート
時間複雑性: 平均 O(n log n), 最悪 O(n^2)
空間複雑性: 平均 O(log n), 最悪 O(n)
"""
if high is None:
high = len(arr) - 1
if low < high:
pivot_index = randomized_partition(arr, low, high)
quicksort(arr, low, pivot_index - 1)
quicksort(arr, pivot_index + 1, high)
def randomized_partition(arr, low, high):
"""
ランダム化分割
最悪ケースの確率を下げる
"""
random_index = random.randint(low, high)
arr[random_index], arr[high] = arr[high], arr[random_index]
return partition(arr, low, high)
def partition(arr, low, high):
"""
分割操作
"""
pivot = arr[high]
i = low - 1
for j in range(low, high):
if arr[j] <= pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i + 1], arr[high] = arr[high], arr[i + 1]
return i + 1
# 使用例
arr = [64, 34, 25, 12, 22, 11, 90]
print(f"Original: {arr}")
quicksort(arr)
print(f"Sorted: {arr}")
6.3 動的計画法:最長共通部分列(LCS)
def lcs_length(X, Y):
"""
最長共通部分列の長さを求める
時間複雑性: O(mn)
空間複雑性: O(mn)
"""
m, n = len(X), len(Y)
# dp[i][j] = X[0:i]とY[0:j]のLCSの長さ
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if X[i-1] == Y[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[m][n]
def lcs_sequence(X, Y):
"""
最長共通部分列を構築
時間複雑性: O(mn)
空間複雑性: O(mn)
"""
m, n = len(X), len(Y)
dp = [[0] * (n + 1) for _ in range(m + 1)]
# DPテーブルを構築
for i in range(1, m + 1):
for j in range(1, n + 1):
if X[i-1] == Y[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
# LCSを再構築
lcs = []
i, j = m, n
while i > 0 and j > 0:
if X[i-1] == Y[j-1]:
lcs.append(X[i-1])
i -= 1
j -= 1
elif dp[i-1][j] > dp[i][j-1]:
i -= 1
else:
j -= 1
return ''.join(reversed(lcs))
# 使用例
X = "ABCDGH"
Y = "AEDFHR"
print(f"X: {X}")
print(f"Y: {Y}")
print(f"LCS length: {lcs_length(X, Y)}")
print(f"LCS: {lcs_sequence(X, Y)}")
第8章: グラフ理論とネットワーク
8.1 深さ優先探索(DFS)
from collections import defaultdict
class Graph:
def __init__(self, directed=False):
self.graph = defaultdict(list)
self.directed = directed
def add_edge(self, u, v):
self.graph[u].append(v)
if not self.directed:
self.graph[v].append(u)
def dfs(self, start, visited=None):
"""
深さ優先探索
時間複雑性: O(V + E)
空間複雑性: O(V)
"""
if visited is None:
visited = set()
visited.add(start)
print(start, end=' ')
for neighbor in self.graph[start]:
if neighbor not in visited:
self.dfs(neighbor, visited)
def dfs_iterative(self, start):
"""
反復版深さ優先探索
時間複雑性: O(V + E)
空間複雑性: O(V)
"""
visited = set()
stack = [start]
while stack:
vertex = stack.pop()
if vertex not in visited:
visited.add(vertex)
print(vertex, end=' ')
# 隣接頂点を逆順でスタックに追加
for neighbor in reversed(self.graph[vertex]):
if neighbor not in visited:
stack.append(neighbor)
# 使用例
g = Graph()
g.add_edge(0, 1)
g.add_edge(0, 2)
g.add_edge(1, 2)
g.add_edge(2, 0)
g.add_edge(2, 3)
g.add_edge(3, 3)
print("DFS (recursive):")
g.dfs(2)
print()
print("DFS (iterative):")
g.dfs_iterative(2)
print()
8.2 幅優先探索(BFS)
from collections import deque
class Graph:
def __init__(self, directed=False):
self.graph = defaultdict(list)
self.directed = directed
def add_edge(self, u, v):
self.graph[u].append(v)
if not self.directed:
self.graph[v].append(u)
def bfs(self, start):
"""
幅優先探索
時間複雑性: O(V + E)
空間複雑性: O(V)
"""
visited = set()
queue = deque([start])
visited.add(start)
while queue:
vertex = queue.popleft()
print(vertex, end=' ')
for neighbor in self.graph[vertex]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
def shortest_path(self, start, end):
"""
最短経路(重みなしグラフ)
時間複雑性: O(V + E)
空間複雑性: O(V)
"""
if start == end:
return [start]
visited = set()
queue = deque([(start, [start])])
visited.add(start)
while queue:
vertex, path = queue.popleft()
for neighbor in self.graph[vertex]:
if neighbor == end:
return path + [neighbor]
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, path + [neighbor]))
return None # パスが存在しない
# 使用例
g = Graph()
g.add_edge(0, 1)
g.add_edge(0, 2)
g.add_edge(1, 2)
g.add_edge(2, 0)
g.add_edge(2, 3)
g.add_edge(3, 3)
print("BFS:")
g.bfs(2)
print()
path = g.shortest_path(0, 3)
print(f"Shortest path from 0 to 3: {path}")
8.3 ダイクストラ法
import heapq
from collections import defaultdict
import sys
class WeightedGraph:
def __init__(self, directed=True):
self.graph = defaultdict(list)
self.directed = directed
def add_edge(self, u, v, weight):
self.graph[u].append((v, weight))
if not self.directed:
self.graph[v].append((u, weight))
def dijkstra(self, start):
"""
ダイクストラ法による単一始点最短経路
時間複雑性: O((V + E) log V)
空間複雑性: O(V)
"""
distances = defaultdict(lambda: sys.maxsize)
distances[start] = 0
predecessor = {}
visited = set()
# 優先度付きキュー(最小ヒープ)
pq = [(0, start)]
while pq:
current_distance, current_vertex = heapq.heappop(pq)
if current_vertex in visited:
continue
visited.add(current_vertex)
for neighbor, weight in self.graph[current_vertex]:
distance = current_distance + weight
if distance < distances[neighbor]:
distances[neighbor] = distance
predecessor[neighbor] = current_vertex
heapq.heappush(pq, (distance, neighbor))
return dict(distances), predecessor
def get_path(self, predecessor, start, end):
"""
最短経路を再構築
"""
path = []
current = end
while current != start:
if current not in predecessor:
return None # パスが存在しない
path.append(current)
current = predecessor[current]
path.append(start)
return list(reversed(path))
# 使用例
g = WeightedGraph()
g.add_edge('A', 'B', 4)
g.add_edge('A', 'C', 2)
g.add_edge('B', 'C', 1)
g.add_edge('B', 'D', 5)
g.add_edge('C', 'D', 8)
g.add_edge('C', 'E', 10)
g.add_edge('D', 'E', 2)
distances, predecessor = g.dijkstra('A')
print("Shortest distances from A:")
for vertex, distance in sorted(distances.items()):
print(f" {vertex}: {distance}")
print("\\nShortest paths from A:")
for vertex in sorted(distances.keys()):
if vertex != 'A':
path = g.get_path(predecessor, 'A', vertex)
print(f" A to {vertex}: {' -> '.join(path)} (distance: {distances[vertex]})")
実行時間測定とベンチマーク
import time
import random
import matplotlib.pyplot as plt
def benchmark_sorting_algorithms():
"""
ソートアルゴリズムのベンチマーク
"""
sizes = [100, 500, 1000, 2000, 5000]
merge_times = []
quick_times = []
for size in sizes:
# ランダムデータ生成
data = [random.randint(1, 1000) for _ in range(size)]
# マージソート
data_copy = data.copy()
start_time = time.time()
merge_sort(data_copy)
merge_times.append(time.time() - start_time)
# クイックソート
data_copy = data.copy()
start_time = time.time()
quicksort(data_copy)
quick_times.append(time.time() - start_time)
return sizes, merge_times, quick_times
# ベンチマーク実行(実際の実装では matplotlib が必要)
# sizes, merge_times, quick_times = benchmark_sorting_algorithms()
# print("Sorting algorithm benchmark completed")
このように、理論で学習したアルゴリズムを実際に実装することで、理論と実践の橋渡しができます。各実装には時間・空間複雑性も明記しており、理論的解析と実装の関係を理解できるようになっています。