再帰関数は、チェーンを停止する 基本ケースに達するまで、問題の小さいバージョンで自分自身を呼び出します。
すべての再帰には次の 2 つのことが必要です。
def factorial(n):
if n <= 1: # base case
return 1
return n * factorial(n - 1) # recursive case
各再帰呼び出しは、フレームを呼び出しスタックにプッシュします。基本ケースに戻ると、フレームは逆の順序でポップされます。
factorial(4)
→ factorial(3)
→ factorial(2)
→ factorial(1) → returns 1
← returns 2
← returns 6
← returns 24
基本ケースがない場合、スタックはオーバーフローします。 Python のデフォルトの再帰制限は 1,000 フレームです。
単純な再帰フィボナッチは、同じ部分問題を再計算するため、O(2ⁿ) です。メモ化はこれを O(n) 時間と空間で修正します。
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
バックトラッキングは、ひねりを加えた再帰です。選択を行い、再帰し、次のオプションを試す前に選択を元に戻します。迷路を探索するようなものだと考えてください。前に歩いて、行き止まりに突き当たり、戻って、次の道を試してください。
def backtrack(state, choices):
if is_solution(state):
result.append(state.copy())
return
for choice in choices:
if is_valid(choice):
state.add(choice) # choose
backtrack(state, ...) # explore
state.remove(choice) # un-choose
この 3 ステップのパターン (選択、探索、選択を解除) は、ほぼすべてのバックトラッキング問題の根幹です。
バックトラッキング テンプレートで、再帰呼び出しの後に選択を取り消すのはなぜですか?
[1, 2, 3] のすべての順序を生成します。各レベルで、未使用の番号を選択し、再帰的に選択し、選択を解除します。
[]
/ | \
[1] [2] [3]
/ \ ...
[1,2] [1,3]
| |
[1,2,3] [1,3,2] ... (6 total)
ログイン ディスカッションに参加
n! 個の順列があるため、時間計算量は O(n × n!) になります。
組み合わせ (n は k を選択): 各要素で、それを含めるかスキップしますが、重複を避けるために前方へのみ再帰します。
サブセット: サイズ制約のない同じ考え方 - 再帰ツリー内のすべてのノードは有効なサブセットです。 2ⁿ のサブセットがあります。
N×NのボードにN個のクイーンを配置し、お互いに攻撃しないようにします。 N=4の場合:
. Q . .
. . . Q
Q . . .
. . Q .
戦略: 1 列に 1 つのクイーンを配置します。各行について、すべての列を試してください。列、主対角線、対対角線の競合を確認します。安全であれば、配置して次の行に再帰します。行き詰まったら後戻りしてください。
cols、diag (行 − 列)、および anti_diag (行 + 列) の 3 つのセットとの競合を追跡します。
N-Queens 問題では、対角線の競合を効率的にチェックするにはどうすればよいでしょうか?
空のセルを見つけて、1 ~ 9 の数字を試します。各桁について、行、列、および 3×3 ボックスの制約を確認します。有効な場合は、配置して再帰します。どの数字も機能しない場合は、元に戻して元に戻します。制約を取り除くことで、巨大な検索スペースにもかかわらず扱いやすくなります。
文字の 2D ボードとターゲット単語が与えられた場合、すべてのセルから開始して 4 方向に DFS を実行し、一度に 1 文字ずつ照合します。再帰中にセルを訪問済みとしてマークし、バックトラックのマークを解除して他のパスを許可します。
コストは、分岐係数 (ステップごとの選択肢) と 深さ (解決までのステップ) によって異なります。 b 個の分岐と d 個の深さの場合: O(bᵈ)。
|問題 |分岐 |深さ |複雑さ | |-----------|-----------|-------|-----------| |順列 | n、n−1、… | n |お(ん!) | |サブセット | 2 | n | O(2ⁿ) | | N-クイーンズ | ~n | n | O(n!) 最悪の場合 |
剪定とは、失敗するとわかっている枝をスキップすることを意味します。例:
適切な枝刈りを行うと、非現実的な検索を迅速な検索に変えることができます。
バックトラッキング アルゴリズムで枝刈りは何を行うのでしょうか?