Tiefensuche (DFS)
Zuletzt aktualisiert
Die Tiefensuche erkundet einen Graphen, indem sie entlang jedes Zweigs so tief wie möglich geht, bevor sie zurückgeht. Ausgehend von einem Startknoten besucht sie einen Nachbarn, dann dessen Nachbarn und so weiter, bis sie in eine Sackgasse gerät (ein Knoten ohne unbesuchte Nachbarn); dann geht sie zurück und probiert den nächsten Zweig. Drücke oben auf Abspielen, um zu sehen, wie sie einen Pfad hinabsteigt, den Grund erreicht und wieder herauskommt.
DFS lässt sich natürlich mit einem Stapel ausdrücken - entweder einem expliziten Stapel (wie hier animiert) oder dem Aufrufstapel über Rekursion. Sie besucht jeden Knoten und jede Kante einmal und läuft daher in O(V + E) Zeit für einen Graphen mit V Knoten und E Kanten.
Zeit- und Speicherkomplexität
| Maß | Komplexität | Anmerkungen |
|---|---|---|
| Zeit | O(V + E) | Jeder Knoten und jede Kante einmal besucht |
| Speicher | O(V) | Stapel + Besucht-Menge, im schlimmsten Fall alle Knoten |
| Traversierung | Tiefstes zuerst | Folgt einem Zweig bis zum Ende vor den anderen |
| Datenstruktur | Stapel (oder Rekursion) | Die LIFO-Reihenfolge treibt das Tiefenverhalten an |
Schritt für Schritt
| Schritt | Was passiert |
|---|---|
| 1 | Lege den Startknoten auf den Stapel. |
| 2 | Nimm einen Knoten herunter; wenn bereits besucht, überspringe ihn. |
| 3 | Markiere ihn als besucht und notiere die Kante, die ihn erreicht hat. |
| 4 | Lege alle seine unbesuchten Nachbarn auf den Stapel. |
| 5 | Wiederhole, bis der Stapel leer ist. |
Durchgerechnetes Beispiel
Iteratives DFS ab A im Graphen A → [B, C], B → [D], C → [E], wobei Nachbarn in der aufgeführten Reihenfolge abgelegt werden (der LIFO-Stapel nimmt das zuletzt Abgelegte zuerst herunter):
| Schritt | Stapel (oben rechts) | Besucht | Aktion |
|---|---|---|---|
| 1 | [A] | {} | Lege die Quelle A ab. |
| 2 | [B, C] | {A} | Nimm A herunter, markiere besucht, lege Nachbarn B dann C ab. |
| 3 | [B, E] | {A, C} | Nimm C herunter, markiere besucht, lege Nachbar E ab. |
| 4 | [B] | {A, C, E} | Nimm E herunter, markiere besucht, keine Nachbarn. |
| 5 | [D] | {A, C, E, B} | Nimm B herunter, markiere besucht, lege Nachbar D ab. |
| 6 | [] | {A, C, E, B, D} | Nimm D herunter, markiere besucht, Stapel leer: fertig. |
Wann man DFS verwendet
| Verwenden, wenn | Vermeiden, wenn |
|---|---|
| Du Zykluserkennung, topologische Sortierung oder das Finden von Zusammenhangskomponenten brauchst. | Du den kürzesten Pfad in einem ungewichteten Graphen brauchst - das leistet BFS, nicht DFS. |
| Der Graph breit und flach ist, sodass ein Stapel weit weniger Speicher braucht als eine Warteschlange mit vielen Randknoten. | Der Graph sehr tief ist und du Rekursion verwendest - der Aufrufstapel kann überlaufen. |
| Du Pfade erschöpfend erkunden willst, etwa beim Lösen von Labyrinthen oder bei der Suche mit Backtracking. | Du Knoten in der Reihenfolge ihrer Entfernung von der Quelle entdecken musst. |
| Du Erreichbarkeit prüfst oder ob ein Pfad zwischen zwei Knoten existiert. | Du die minimale Anzahl an Kanten auf dem gefundenen Pfad garantieren musst. |
Depth-First Search-Code
Eine saubere, lauffähige Depth-First Search-Implementierung in Python, JavaScript, Java, C++, C. Wähle eine Sprache, kopiere den Code oder öffne ihn vorgeladen im Coddy-Playground.
Depth-First Search-Code in Python
1def dfs(graph, node, visited):2 visited.add(node)3 order = [node]4 for neighbor in graph[node]:5 if neighbor not in visited:6 order.extend(dfs(graph, neighbor, visited))7 return order8
9
10graph = {11 "A": ["B", "C"],12 "B": ["D", "E"],13 "C": ["F"],14 "D": [],15 "E": ["F"],16 "F": [],17}18
19order = dfs(graph, "A", set())20print("DFS order:", " -> ".join(order))Depth-First Search-Code in JavaScript
1const graph = {2 A: ["B", "C"],3 B: ["D", "E"],4 C: ["F"],5 D: [],6 E: ["F"],7 F: [],8};9
10function dfs(node, visited = new Set(), order = []) {11 if (visited.has(node)) return order;12 visited.add(node);13 order.push(node);14 for (const next of graph[node]) {15 dfs(next, visited, order);16 }17 return order;18}19
20console.log("DFS from A:", dfs("A").join(" -> "));Depth-First Search-Code in Java
1import java.util.List;2
3public class Main {4 static List<List<Integer>> adj = List.of(5 List.of(1, 2), // neighbors of 06 List.of(0, 3, 4), // neighbors of 17 List.of(0, 5), // neighbors of 28 List.of(1),9 List.of(1, 5),10 List.of(2, 4)11 );12 static boolean[] visited = new boolean[6];13
14 static void dfs(int node) {15 visited[node] = true;16 System.out.print(" " + node);17 for (int next : adj.get(node)) {18 if (!visited[next]) dfs(next);19 }20 }21
22 public static void main(String[] args) {23 System.out.print("DFS from 0:");24 dfs(0);25 System.out.println();26 }27}Depth-First Search-Code in C++
1#include <iostream>2#include <vector>3
4void dfs(int node, const std::vector<std::vector<int>>& adj,5 std::vector<bool>& visited) {6 visited[node] = true;7 std::cout << node << " ";8 // Recurse into every unvisited neighbor9 for (int next : adj[node]) {10 if (!visited[next]) dfs(next, adj, visited);11 }12}13
14int main() {15 // 6-node undirected graph as an adjacency list16 std::vector<std::vector<int>> adj = {17 {1, 2}, // 018 {0, 3, 4}, // 119 {0, 5}, // 220 {1}, // 321 {1, 5}, // 422 {2, 4}, // 523 };24 std::vector<bool> visited(adj.size(), false);25 std::cout << "DFS from node 0: ";26 dfs(0, adj, visited);27 std::cout << "\n";28 return 0;29}Depth-First Search-Code in C
1#include <stdbool.h>2#include <stdio.h>3
4#define N 65
6// 6-node undirected graph as an adjacency matrix7int adj[N][N] = {8 {0, 1, 1, 0, 0, 0},9 {1, 0, 0, 1, 1, 0},10 {1, 0, 0, 0, 0, 1},11 {0, 1, 0, 0, 0, 0},12 {0, 1, 0, 0, 0, 1},13 {0, 0, 1, 0, 1, 0},14};15bool visited[N];16
17void dfs(int node) {18 visited[node] = true;19 printf("%d ", node);20 // Recurse into every unvisited neighbor21 for (int next = 0; next < N; next++) {22 if (adj[node][next] && !visited[next]) dfs(next);23 }24}25
26int main(void) {27 printf("DFS from node 0: ");28 dfs(0);29 printf("\n");30 return 0;31}Häufige Fragen zur Tiefensuche
Wie hoch ist die Zeitkomplexität von DFS?
O(V + E) Zeit, wobei V die Anzahl der Knoten und E die Anzahl der Kanten ist, weil sie jeden Knoten einmal besucht und jede Kante einmal betrachtet. Sie benötigt O(V) Speicher für den Stapel und die Besucht-Menge.Was ist der Unterschied zwischen DFS und BFS?
Ist DFS rekursiv oder iterativ?
Wann sollte ich DFS statt BFS verwenden?
Kann DFS den kürzesten Pfad finden?
Warum braucht DFS eine Besucht-Menge?
O(V + E). Ein häufiger Fehler ist, Knoten erst beim Herunternehmen als besucht zu markieren und trotzdem Duplikate abzulegen; das ist korrekt, kann aber veraltete Einträge auf dem Stapel hinterlassen, die übersprungen werden müssen.