Eine rekursive Funktion ruft sich selbst mit einer kleineren Version des Problems auf, bis sie auf einen Basisfall trifft, der die Kette stoppt.
Jede Rekursion benötigt zwei Dinge:
def factorial(n):
if n <= 1: # base case
return 1
return n * factorial(n - 1) # recursive case
Jeder rekursive Aufruf schiebt einen Frame auf den Aufrufstapel. Wenn der Basisfall zurückkehrt, werden die Frames in umgekehrter Reihenfolge angezeigt.
factorial(4)
→ factorial(3)
→ factorial(2)
→ factorial(1) → returns 1
← returns 2
← returns 6
← returns 24
Wenn kein Basisfall vorliegt, läuft der Stapel über. Das Standard-Rekursionslimit von Python beträgt 1.000 Frames.
Das naive rekursive Fibonacci ist O(2ⁿ), weil es dieselben Teilprobleme neu berechnet. Durch Auswendiglernen wird dies in O(n) Zeit und Raum behoben.
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
Backtracking ist eine Rekursion mit einer Wendung: Sie treffen eine Wahl, rekursieren und machen dann die Wahl rückgängig, bevor Sie die nächste Option ausprobieren. Stellen Sie sich vor, Sie erkunden ein Labyrinth: Gehen Sie vorwärts, geraten Sie in eine Sackgasse, gehen Sie zurück, versuchen Sie den nächsten Weg.
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
Anmelden an der Diskussion teilnehmen
Dieses dreistufige Muster – „Auswählen, Erkunden, Auswahl aufheben“ – ist das Rückgrat fast aller Backtracking-Probleme.
Warum machen wir in der Backtracking-Vorlage die Auswahl nach dem rekursiven Aufruf rückgängig?
Generieren Sie alle Bestellungen von [1, 2, 3]. Wählen Sie auf jeder Ebene eine nicht verwendete Zahl aus, führen Sie eine Rekursion durch und heben Sie die Auswahl dann auf.
[]
/ | \
[1] [2] [3]
/ \ ...
[1,2] [1,3]
| |
[1,2,3] [1,3,2] ... (6 total)
Es gibt n! Permutationen, daher beträgt die Zeitkomplexität O(n × n!).
Kombinationen (n wähle k): Fügen Sie es bei jedem Element ein oder überspringen Sie es, aber rekursieren Sie nur vorwärts, um Duplikate zu vermeiden.
Teilmengen: Dieselbe Idee ohne Größenbeschränkung – jeder Knoten im Rekursionsbaum ist eine gültige Teilmenge. Es gibt 2ⁿ Teilmengen.
Platzieren Sie N Damen auf einem N×N-Brett, sodass sich keine gegenseitig angreift. Für N=4:
. Q . .
. . . Q
Q . . .
. . Q .
Strategie: Platziere eine Königin pro Reihe. Probieren Sie für jede Zeile jede Spalte aus. Überprüfen Sie Spalten-, Hauptdiagonalen- und Antidiagonalkonflikte. Wenn sicher, platzieren Sie es und kehren Sie zur nächsten Zeile zurück. Wenn Sie nicht weiterkommen, machen Sie einen Rückzieher.
Verfolgen Sie Konflikte mit drei Sätzen: cols, diag (Zeile − Spalte) und anti_diag (Zeile + Spalte).
Wie können wir beim N-Queens-Problem effizient Diagonalkonflikte prüfen?
Suchen Sie eine leere Zelle und probieren Sie die Ziffern 1–9 aus. Überprüfen Sie für jede Ziffer die Zeilen-, Spalten- und 3×3-Box-Einschränkungen. Wenn gültig, platzieren und rekursieren. Wenn keine Ziffer funktioniert, machen Sie den Vorgang rückgängig und gehen Sie zurück. Die Beschränkung auf Einschränkungen macht es trotz des riesigen Suchraums handhabbar.
Beginnen Sie bei einer 2D-Buchstabentafel und einem Zielwort in jeder Zelle und führen Sie DFS in vier Richtungen durch, wobei Sie jeweils ein Zeichen nach dem anderen abgleichen. Markieren Sie Zellen während der Rekursion als besucht und heben Sie die Markierung beim Zurückverfolgen auf, um andere Pfade zuzulassen.
Die Kosten hängen vom Verzweigungsfaktor (Auswahlmöglichkeiten pro Schritt) und der Tiefe (Schritte zu einer Lösung) ab. Für b-Zweige und d-Tiefe: O(bᵈ).
| Problem | Verzweigung | Tiefe | Komplexität | |---------|-----------|-------|------------| | Permutationen | n, n−1, … | n | O(n!) | | Teilmengen | 2 | n | O(2ⁿ) | | N-Queens | ~n | n | O(n!) schlimmster Fall |
Beschneiden bedeutet, Zweige zu überspringen, von denen Sie wissen, dass sie scheitern werden. Beispiele:
Ein guter Schnitt kann aus einer unpraktischen Suche eine schnelle machen.
Was bewirkt das Beschneiden in einem Backtracking-Algorithmus?