4.1 メモ化の定義と主要なコンセプト
メモ化は、コストが高い関数の結果を保存して、同じ引数での次回の呼び出しで再利用するための最適化手法だよ。これによって、再計算の回数が減り、パフォーマンスが向上するんだ。
主要なコンセプト:
1. キャッシュ:
関数の結果を辞書や配列などのデータ構造に保存して、同じ引数での再呼び出し時に計算せずに保存された結果を返すことができるんだ。
2. 検索テーブル (Lookup Table):
関数の過去の呼び出し結果を保存するためのデータ構造だよ。これはハッシュテーブル(辞書)や配列で実現できるよ。
3. 再帰呼び出し:
メモ化は特に再帰的な関数に役立つんだ。同じサブタスクが同じパラメータで何度も実行される場合があるからね。
最適化されたアルゴリズムの時間と空間の複雑さ:
時間の複雑さ:
多くの再帰的な問題では、メモ化によって再計算の回数を減らすことで時間の複雑さを下げることができるんだ。例えば、フィボナッチ数の再帰的な計算は時間の複雑さがO(2^n)
だけど、メモ化を使えばO(n)
になるよ。
空間の複雑さ:
結果をキャッシュに保存することで空間の複雑さが増えるんだ。通常これはO(n)
だよ、メモ化が必要な問題の場合ね。
まとめ:
メモ化は再帰アルゴリズムの最適化に非常に強力な手法で、再計算の回数を減らすことでパフォーマンスを大いに向上させることができるんだ。
特に、同じサブタスクが同じパラメータで何度も実行される
ような問題において有用だよ。メモ化のコンセプトを理解し、実践的に応用することで、計算負荷の高い問題を効率的に解くことができるんだ。
4.2 最適化の例
メモ化を使用した再帰的アルゴリズムの最適化の例だよ
例1: フィボナッチ数
フィボナッチ数の再帰的な計算は、メモ化なしでは指数的な時間の複雑さを持つんだ。メモ化を使用することで、パフォーマンスが大いに向上するよ。
def fibonacci(n, memo=None):
if memo is None:
memo = {}
if n in memo:
return memo[n]
if n <= 1:
return n
memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo)
return memo[n]
# 使用例:
print(fibonacci(10)) # 出力: 55
重要!
memo=None
をデフォルト値として使用し、memo
が渡されなかった場合に関数内で空の辞書を作成することに注意してね。これは、デフォルト値として可変オブジェクトを使用する問題を避けるためだよ。
例2: 部分集合の合計
与えられた集合の部分集合で、要素の合計が指定された値になるものが存在するかを判定する必要があるんだ。
def is_subset_sum(arr, n, sum_value, memo=None):
if memo is None:
memo = {}
if (n, sum_value) in memo:
return memo[(n, sum_value)]
if sum_value == 0:
return True
if n == 0 and sum_value != 0:
return False
if arr[n - 1] > sum_value:
memo[(n, sum_value)] = is_subset_sum(arr, n - 1, sum_value, memo)
return memo[(n, sum_value)]
memo[(n, sum_value)] = is_subset_sum(arr, n - 1, sum_value, memo) or is_subset_sum(arr, n - 1, sum_value - arr[n - 1], memo)
return memo[(n, sum_value)]
# 使用例:
arr = [3, 34, 4, 12, 5, 2]
sum_value = 9
n = len(arr)
print(is_subset_sum(arr, n, sum_value)) # 出力: True
メモ化は実質的にサブタスクの結果をキャッシュすることだよ。
例えば、フィボナッチ数F(10) == F(9) + F(8)
だけど、F(9)
を計算するにはF(8)
とF(7)
を計算する必要があるんだ。つまりF(8)
は二回計算しなければならない。F(9)
の最初の項としてとF(10)
の第二項としてね。これを避けるために、最初に計算した後にそれをキャッシュする必要があるんだ。
GO TO FULL VERSION