Informatik

Tiefensuche (DFS): Ablauf, Stack und Beispiele

Tiefensuche (DFS): Ablauf, Stack und Beispiele
Tiefensuche (DFS): Ablauf, Stack und Beispiele
Für Quiz, Lückentext, Lernkarten und Fortschritt ist JavaScript nötig. Alle Inhalte und Lösungen bleiben direkt lesbar.

Die Tiefensuche (englisch Depth-First Search, kurz DFS) verfolgt in einem Graphen zuerst einen Weg so weit wie möglich. Trifft sie auf eine Sackgasse, kehrt sie zur letzten offenen Abzweigung zurück. Dieses Zurückgehen heißt Backtracking.

Du lernst hier, eine Tiefensuche von Hand auszuführen, sie mit Rekursion oder Stack zu erklären und zu entscheiden, wann ein anderes Verfahren besser passt.

Deine Lernziele

Hake ab, was du schon kannst — und komm am Ende hierher zurück!

Wie die Tiefensuche denkt

Ein Graph besteht aus Knoten und Kanten. Knoten stellen zum Beispiel Orte dar; Kanten zeigen, welche Orte direkt verbunden sind. In einem gerichteten Graphen darf eine Kante nur in Pfeilrichtung durchlaufen werden.

Definition

Tiefensuche

Die Tiefensuche ist ein Verfahren zum Durchlaufen eines Graphen. Sie besucht einen noch unbekannten Nachbarn, setzt die Suche von dort fort und geht erst bei einer Sackgasse zurück.

Stell dir ein Labyrinth vor: Du folgst einem Gang bis zum Ende. Dann gehst du zur letzten Abzweigung zurück und probierst den nächsten noch nicht untersuchten Gang.

Welche Route DFS konkret nimmt, hängt vom Startknoten und von der Reihenfolge der Nachbarn ab. Darum muss diese Reihenfolge feststehen, wenn du eine Besuchsfolge eindeutig angeben willst.

Merke

Erst tief, dann zurück: DFS verfolgt die zuletzt gewählte Fortsetzung zuerst.

Teste dich
Frage 1 von 1LeichtWas tut DFS an einem Knoten mit mehreren noch unbesuchten Nachbarn?
Lösung: Sie wählt einen Nachbarn und verfolgt diesen Weg zunächst weiter. — DFS kennt ohne zusätzliche Informationen keinen kürzesten Zielweg. Sie folgt einem noch unbesuchten Nachbarn nach der festgelegten Reihenfolge.
So führst du DFS Schritt für Schritt aus

Betrachte diesen gerichteten Graphen. Die Nachbarn stehen bereits in der Reihenfolge, in der DFS sie prüft:

text A: B, C B: D, E C: E D: — E: A

Starte bei A und führe die rekursive DFS aus:

  1. Besuche A und markiere A.
  2. Gehe zum ersten unbesuchten Nachbarn B.
  3. Von B gehst du zuerst zu D. Dort gibt es keine Fortsetzung: zurück zu B.
  4. Der nächste unbesuchte Nachbar von B ist E. Die Kante von E zu A führt zu einem bereits besuchten Knoten, also folgt kein neuer Aufruf.
  5. Zurück bei A bleibt C. Dessen Nachbar E ist bereits besucht.

Die Besuchsreihenfolge lautet damit:

text A, B, D, E, C

Beispiel

Der entscheidende Schritt ist das Backtracking nach D: DFS springt nicht sofort zu C, sondern kehrt zu B zurück, weil dort mit E noch eine offene Abzweigung liegt.

Ein passender rekursiver Ablauf sieht so aus:

text DFS(u): markiere u als besucht für jeden Nachbarn v von u in festgelegter Reihenfolge: wenn v noch nicht besucht ist: DFS(v)

Die Markierung geschieht vor den rekursiven Aufrufen. Sonst könnten Kanten wie E → A eine endlose Aufrufkette erzeugen.

Teste dich
Frage 1 von 2MittelWelcher Knoten wird im Beispiel direkt nach B erstmals besucht?
Lösung: D — In der Nachbarliste von B steht D vor E. Deshalb folgt die Besuchsfolge zunächst A, B, D.
Frage 2 von 2SchwerNur die Nachbarreihenfolge von A wird zu A: C, B geändert. Welche vollständige Besuchsfolge entsteht nun?
Lösung: A, C, E, B, D — Von A führt DFS zuerst zu C und von dort zu E. Danach kehrt sie zu A zurück, besucht B und schließlich dessen noch unbesuchten Nachbarn D.
Rekursion und Stack setzen dieselbe Strategie um

Bei der Rekursion ruft die Funktion sich für einen Nachbarn selbst auf. Die noch nicht beendeten Funktionsaufrufe merken sich automatisch, zu welcher Abzweigung die Suche zurückkehren muss.

Die gleiche Strategie lässt sich ohne rekursive Aufrufe mit einem Stack umsetzen. Ein Stack arbeitet nach dem LIFO-Prinzip: Das zuletzt abgelegte Element wird zuerst entnommen.

```text markiere start als besucht lege start auf den Stack

solange der Stack nicht leer ist: nimm den obersten Knoten u herunter verarbeite u lege unbesuchte Nachbarn auf den Stack markiere sie dabei als besucht ```

Werden Nachbarn schon beim Ablegen markiert, wird derselbe Knoten nicht mehrfach als offen eingeplant. Soll die oben notierte Nachbarreihenfolge gelten, müssen die Nachbarn beim iterativen Verfahren häufig in umgekehrter Reihenfolge auf den Stack gelegt werden: Der zuletzt abgelegte liegt oben und wird als Nächstes entnommen.

Gut zu wissen

Iterative DFS mit einem Stack und iterative Tiefenvertiefung sind nicht dasselbe. Bei der Tiefenvertiefung wird DFS mehrfach mit einer schrittweise erhöhten Tiefengrenze ausgeführt.

Lückentext

Wähle in jeder Lücke die passende Form und prüfe anschließend deine Antworten.

Die rekursive DFS nutzt den . Die iterative DFS nutzt einen expliziten . Wegen des wird das zuletzt abgelegte Element zuerst entnommen. Eine frühe verhindert, dass derselbe Knoten mehrfach als offen eingeplant wird.

Lösungen: Lücke 1: Aufrufstack; Lücke 2: Stack; Lücke 3: LIFO-Prinzips; Lücke 4: Besuchsmarkierung. Rekursion und expliziter Stack speichern die noch offenen Rückkehrpunkte. LIFO bedeutet „zuletzt hinein, zuerst heraus“; die Besuchsmarkierung verhindert unnötige Wiederholungen.
Erreichbarkeit und gefundene Pfade

DFS eignet sich gut für die Frage: Gibt es vom Start einen Pfad zum Ziel? Sobald das Ziel entdeckt ist, kann die Suche beendet werden.

Möchtest du den gefundenen Pfad ausgeben, speicherst du für jeden neu entdeckten Knoten seinen Vorgänger. Im Beispiel wird E von B aus entdeckt. Deshalb gilt:

text Vorgänger von E = B Vorgänger von B = A

Vom Ziel rückwärts gelesen entsteht E ← B ← A. Umgedreht lautet der gefundene Pfad:

text A → B → E

Beispiel

DFS findet hier einen Pfad von A nach E. Sie beweist damit die Erreichbarkeit. Sie beweist aber nicht, dass dieser Pfad unter allen möglichen Pfaden der kürzeste ist.

Auch Flood Fill nutzt dieselbe Idee: Ein Rasterfeld ist ein Knoten, orthogonal benachbarte Felder gleicher alter Farbe sind verbunden. Nach dem Umfärben eines Feldes werden seine vier Nachbarn untersucht. Randprüfungen und die Prüfung der alten Farbe bilden die Abbruchbedingungen.

Teste dich
Frage 1 von 1MittelWarum speichert man bei einer Pfadsuche Vorgänger?
Lösung: Damit man den gefundenen Pfad vom Ziel zum Start zurückverfolgen kann. — Jeder neu entdeckte Knoten verweist auf den Knoten, von dem aus er erreicht wurde. Diese Kette lässt sich rückwärts verfolgen und anschließend umdrehen.
Wann DFS passt – und wann nicht

DFS und Breitensuche (BFS) besuchen erreichbare Knoten, ordnen die offenen Knoten aber verschieden:

FrageTiefensuche (DFS)Breitensuche (BFS)
Welche Fortsetzung kommt zuerst?der zuletzt begonnene, tiefe Zweigzunächst alle Knoten derselben Ebene
Typische DatenstrukturStack oder RekursionWarteschlange
Kürzester Pfad im ungewichteten Graphennicht garantiertnach Kantenzahl garantiert
Typische StärkeErreichbarkeit, vollständiges Durchlaufen, tiefe Suchenahe Ziele, kürzeste Wege nach Kantenzahl

Bei einer Adjazenzliste kann eine vollständige DFS jeden Knoten und jede Kante systematisch durchlaufen. Die Laufzeit liegt dann in O(|V| + |E|): |V| bezeichnet die Anzahl der Knoten, |E| die Anzahl der Kanten. Eine Adjazenzmatrix prüft dagegen für jeden besuchten Knoten eine ganze Zeile; bei |V| Knoten liegt der Aufwand typischerweise in O(|V|²).

Die Besuchsmarkierung sowie der Rekursionszustand oder der explizite Stack können zusammen zusätzlichen Speicher in O(|V|) benötigen.

Gut zu wissen

Für einen endlichen Graphen verhindert die Besuchsmarkierung, dass DFS in einem Zyklus endlos weiterläuft. In einem gerichteten Graphen kann eine Kante zu einem gerade aktiven, noch nicht abgeschlossenen Knoten auf einen Zyklus hinweisen. Eine Kante zu irgendeinem früher besuchten Knoten genügt für diese Aussage nicht immer.

Teste dich
Frage 1 von 2LeichtWelche Struktur erzeugt bei BFS die Verarbeitung nach Ebenen?
Lösung: eine Warteschlange — Die Warteschlange arbeitet nach FIFO: zuerst hinein, zuerst heraus. So werden früher entdeckte Ebenenknoten zuerst verarbeitet.
Frage 2 von 2SchwerDu brauchst in einem ungewichteten Straßennetz garantiert einen Weg mit möglichst wenigen Kanten. Welches Verfahren passt?
Lösung: BFS, weil sie Knoten nach ihrer Entfernung in Kantenzahlen entdeckt. — Die Besuchsmarkierung verhindert Wiederholungen, ändert aber nicht die Suchordnung. Für kürzeste Wege nach Kantenzahl ist BFS geeignet.
Karteikasten
Karteikasten

Überlege zuerst selbst und drehe die Karte anschließend zum Prüfen um.

Alles auf einen Blick
Mindmap
  • Tiefensuche
    • Strategie: erst tief, dann Backtracking
    • Zustand: besucht oder noch unbesucht
    • Umsetzung: Rekursion oder Stack
    • Ergebnis: Erreichbarkeit und gefundener Pfad
    • Reihenfolge: abhängig von Start und Nachbarordnung
    • Grenze: kein garantiert kürzester Pfad
    • Aufwand mit Adjazenzliste: O(|V| + |E|)
Abschluss-Check
Teste dich
Frage 1 von 3LeichtWas muss für eine eindeutige DFS-Besuchsfolge zusätzlich zum Startknoten feststehen?
Lösung: die Reihenfolge der Nachbarn — DFS wählt unter mehreren offenen Nachbarn gemäß der vorgegebenen Reihenfolge. Ohne sie können mehrere Besuchsfolgen korrekt sein.
Frage 2 von 3MittelIn einem endlichen zyklischen Graphen fehlt die Besuchsmarkierung. Was kann passieren?
Lösung: DFS verarbeitet dieselben Knoten immer wieder und terminiert möglicherweise nicht. — Eine Markierung hält fest, welche Knoten bereits entdeckt wurden. Ohne sie kann die Suche einem Zyklus unbegrenzt folgen.
Frage 3 von 3SchwerEine iterative DFS soll bei A: B, C zuerst B bearbeiten. In welcher Reihenfolge legst du die beiden Nachbarn auf einen LIFO-Stack?
Lösung: zuerst C, dann B — Beim LIFO-Stack wird das zuletzt abgelegte Element zuerst entnommen. Deshalb muss B nach C auf den Stack, damit B oben liegt.

Passend dazu