Graphdarstellungen und Vorbereitung der Traversierung
Erstellen Sie gerichtete und ungerichtete Graphen mit Adjazenzlisten, initialisieren Sie BFS mit einer Deque und DFS mit einem Stapel oder Rekursion und verfolgen Sie besuchte Knoten.
Graphdarstellungen und Vorbereitung der Traversierung ist eine kostenlose DSA Interview Prep-Lektion auf CoddyKit. Dies ist Lektion 1 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des DSA Interview Prep-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der DSA Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
Was ist ein Graph?
Ein Graph ist eine Sammlung von Knoten (Vertices), die durch Kanten verbunden sind. Anders als Bäume können Graphen Zyklen, mehrere Pfade zwischen Knoten und nicht verbundene Komponenten enthalten. Graphen modellieren reale Systeme wie soziale Netzwerke, Straßenkarten, Abhängigkeitsbäume und WebseitensLinks. In nahezu jedem anspruchsvollen Systemdesign- und Algorithmus-Interview kommen Graphen vor – ihre Darstellung und Traversierung sicher zu beherrschen, ist daher unerlässlich.
# Graph terminology:
# - V: set of vertices (nodes)
# - E: set of edges
# - Directed graph: edges have direction (A -> B but not B -> A)
# - Undirected graph: edges are bidirectional
# - Weighted graph: edges have costs/weights
# - Cyclic: contains at least one cycle
# - Acyclic: no cycles (DAG = Directed Acyclic Graph)
# - Connected: every node reachable from every other
# - Disconnected: multiple isolated components
print('Graph: nodes + edges, directed/undirected, weighted/unweighted')Darstellung mit Adjazenzliste
Eine Adjazenzliste speichert für jeden Knoten die Liste seiner Nachbarn. Verwenden Sie in Python ein dict, das jeden Knoten einer Liste benachbarter Knoten zuordnet. Dies ist die häufigste Darstellung in Interviewaufgaben: O(V + E) Speicherplatz (effizient für dünn besetzte Graphen), O(degree) zum Durchlaufen der Nachbarn und im Durchschnitt O(1) zum Prüfen der Nachbarschaft mit einer Variante unter Verwendung eines Hash-Sets. Die meisten LeetCode-Graphaufgaben verwenden dieses Format.
from collections import defaultdict
# Build an undirected graph
def build_undirected(edges):
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v)
graph[v].append(u) # both directions
return graph
edges = [(0,1), (0,2), (1,3), (2,3), (3,4)]
graph = build_undirected(edges)
print(dict(graph))
# {0:[1,2], 1:[0,3], 2:[0,3], 3:[1,2,4], 4:[3]}
# Directed graph: only one direction
def build_directed(edges):
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v) # only u -> v
return graphDarstellung mit Adjazenzmatrix
Eine Adjazenzmatrix ist ein zweidimensionales V×V-Array, bei dem matrix[i][j] = 1 (oder das Kantengewicht) gilt, wenn eine Kante von i nach j existiert, und andernfalls 0. Sie bietet einen O(1)-Zugriff auf Kanten, benötigt aber unabhängig von der Anzahl der Kanten O(V²) Speicherplatz – bei dünn besetzten Graphen ist das ineffizient. Sie wird bevorzugt verwendet, wenn der Graph dicht ist (also viele Kanten besitzt) oder schnelle Prüfungen auf das Vorhandensein von Kanten entscheidend sind, etwa beim Floyd-Warshall-Algorithmus für kürzeste Wege zwischen allen Knotenpaaren.
# Adjacency matrix for 5 nodes
V = 5
matrix = [[0] * V for _ in range(V)]
edges = [(0,1), (0,2), (1,3), (2,3), (3,4)]
for u, v in edges:
matrix[u][v] = 1
matrix[v][u] = 1 # undirected
# Print the matrix:
for row in matrix:
print(row)
# Neighbour check: O(1)
print('Edge 0-2:', bool(matrix[0][2])) # True
print('Edge 0-4:', bool(matrix[0][4])) # False
# Space: O(V^2) vs adjacency list O(V+E)
# Dense graph: matrix often better; sparse: list betterDarstellung als Kantenliste
Eine Kantenliste ist die einfachste Darstellung: lediglich eine Liste von Tupeln aus (Quelle, Ziel), optional mit Gewichten. Sie benötigt O(E) Speicherplatz und lässt sich leicht über alle Kanten durchlaufen. Das Ermitteln der Nachbarn eines Knotens erfordert jedoch das Durchsuchen aller Kanten: O(E). Kantenlisten werden in Graphalgorithmen verwendet, die alle Kanten genau durchlaufen, etwa in Bellman-Ford (alle Kanten n-1-mal relaxieren) und im Kruskal-Algorithmus für den minimalen Spannbaum.
# Weighted edge list: (source, destination, weight)
edge_list = [
(0, 1, 4),
(0, 2, 1),
(1, 3, 1),
(2, 3, 5),
(3, 4, 3)
]
# Useful for:
# Bellman-Ford: iterate all edges n-1 times
# Kruskal's MST: sort by weight then union-find
# Sort by weight for Kruskal:
edge_list_sorted = sorted(edge_list, key=lambda e: e[2])
print('Sorted by weight:', edge_list_sorted)
# Finding neighbours: O(E) scan -- inefficient for traversal
node_0_neighbors = [v for u, v, w in edge_list if u == 0]
print('Node 0 neighbors:', node_0_neighbors)BFS-Einrichtung: Warteschlange und Besuchsmenge
BFS (Breitensuche) durchläuft einen Graphen mithilfe einer Warteschlange Ebene für Ebene. Eine Menge besuchter Knoten ist entscheidend, um das erneute Besuchen von Knoten in zyklischen Graphen zu verhindern. Ohne diese Menge würde BFS in einem zyklischen Graphen endlos laufen. Die übliche Einrichtung: Initialisieren Sie die Warteschlange mit dem Quellknoten, markieren Sie ihn als besucht und entfernen Sie dann wiederholt ein Element aus der Warteschlange, verarbeiten Sie es und fügen Sie nicht besuchte Nachbarn hinzu.
from collections import deque
def bfs(graph, start):
visited = {start} # mark source as visited
queue = deque([start]) # initialise queue
order = []
while queue:
node = queue.popleft()
order.append(node)
for neighbour in graph[node]:
if neighbour not in visited:
visited.add(neighbour) # mark BEFORE enqueue
queue.append(neighbour)
return order
from collections import defaultdict
graph = defaultdict(list)
for u, v in [(0,1),(0,2),(1,3),(2,3),(3,4)]:
graph[u].append(v); graph[v].append(u)
print(bfs(graph, 0)) # [0, 1, 2, 3, 4]DFS-Einrichtung: Stapel oder Rekursion
DFS (Tiefensuche) geht entlang jedes Zweigs so weit wie möglich, bevor sie zurückgeht. Implementieren Sie sie rekursiv (unter Verwendung des Aufrufstapels) oder iterativ (unter Verwendung eines expliziten Stapels). Beide Varianten benötigen bei zyklischen Graphen eine Menge besuchter Knoten. Die iterative Variante fügt Nachbarn in umgekehrter Reihenfolge auf den Stapel, damit die Reihenfolge dem rekursiven DFS-Durchlauf entspricht. Die Reihenfolge der Erkundung kann sich zwischen den beiden Implementierungen dennoch unterscheiden.
def dfs_recursive(graph, node, visited=None, order=None):
if visited is None: visited = set(); order = []
visited.add(node)
order.append(node)
for neighbour in graph[node]:
if neighbour not in visited:
dfs_recursive(graph, neighbour, visited, order)
return order
def dfs_iterative(graph, start):
visited = set()
stack = [start]
order = []
while stack:
node = stack.pop()
if node in visited: continue
visited.add(node)
order.append(node)
for neighbour in reversed(graph[node]): # reverse for same order as recursive
if neighbour not in visited:
stack.append(neighbour)
return order
print('Recursive DFS:', dfs_recursive(graph, 0))
print('Iterative DFS:', dfs_iterative(graph, 0))Wann Sie BFS statt DFS verwenden sollten
Wählen Sie BFS, wenn Sie den kürzesten Weg (mit den wenigsten Kanten) in einem ungewichteten Graphen benötigen oder Knoten Ebene für Ebene verarbeiten müssen. Wählen Sie DFS, wenn Sie alle erreichbaren Knoten erkunden, Zyklen erkennen, zusammenhängende Komponenten finden, eine topologische Sortierung durchführen oder alle Wege aufzählen müssen. In der Praxis: BFS für „kürzester Weg/minimale Anzahl von Sprüngen“, DFS für „Existenz/Erreichbarkeit/Aufzählung“.
# BFS use cases:
# - Shortest path in unweighted graph (fewest edges)
# - Level-order traversal
# - Word ladder (minimum transformations)
# - Clone graph
# DFS use cases:
# - Connected components (flood fill)
# - Cycle detection
# - Topological sort
# - All paths between two nodes
# - Maze solving (any path)
# - N-queens, Sudoku (backtracking)
# Both: O(V + E) time, O(V) space for visited
print('BFS: shortest hops | DFS: existence and enumeration')Graphen aus LeetCode-Eingabeformaten
LeetCode-Graphaufgaben verwenden unterschiedliche Eingabeformate. Kantenliste: [[0,1],[0,2]] – bauen Sie eine Adjazenzliste auf. Indexbasierte Adjazenzliste: graph[i] ist die Liste der Nachbarn von i. Gitter/Matrix: ein zweidimensionales m×n-Array, in dem Zellen Knoten sind und benachbarte Zellen (oben/unten/links/rechts) als Nachbarn gelten. Knoten mit Kindknoten: benutzerdefinierte Klassen wie Node(val, neighbors). Erkennen Sie diese Formate und wandeln Sie sie als ersten Schritt in eine Adjazenzliste um.
# Format 1: edge list -> adjacency list
def edges_to_adj(n, edges):
graph = [[] for _ in range(n)]
for u, v in edges:
graph[u].append(v)
graph[v].append(u)
return graph
# Format 2: 2D grid -> adjacency (implicit)
# Neighbours of (r, c): (r-1,c), (r+1,c), (r,c-1), (r,c+1)
DIRS = [(-1,0),(1,0),(0,-1),(0,1)]
def grid_neighbours(grid, r, c):
rows, cols = len(grid), len(grid[0])
return [(r+dr, c+dc) for dr, dc in DIRS
if 0 <= r+dr < rows and 0 <= c+dc < cols]
grid = [[1,1,0],[0,1,1],[1,0,0]]
print('Neighbours of (0,0):', grid_neighbours(grid, 0, 0))
print('Neighbours of (1,1):', grid_neighbours(grid, 1, 1))Besuchte Zellen in Gittern markieren
Bei Gitteraufgaben gibt es zwei Möglichkeiten, besuchte Zellen zu verfolgen. Option A: Verwenden Sie eine separate visited-Menge aus (row, col)-Tupeln – O(m*n) zusätzlicher Speicherplatz. Option B: Verändern Sie das Gitter direkt, indem Sie besuchte Zellen mit einem Platzhalterwert (z. B. '#' oder 2) markieren, und stellen Sie sie anschließend bei Bedarf wieder her. Die direkte Änderung benötigt O(1) zusätzlichen Speicherplatz und ist bei Flood-Fill- und Number-of-Islands-Aufgaben üblich.
def num_islands(grid):
if not grid:
return 0
rows, cols = len(grid), len(grid[0])
count = 0
def dfs(r, c):
if r < 0 or r >= rows or c < 0 or c >= cols:
return
if grid[r][c] != '1':
return
grid[r][c] = '#' # mark as visited (in-place)
dfs(r+1, c); dfs(r-1, c)
dfs(r, c+1); dfs(r, c-1)
for r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
dfs(r, c)
count += 1
return count
grid = [['1','1','0','0'],
['1','1','0','0'],
['0','0','1','0'],
['0','0','0','1']]
print(num_islands(grid)) # 3BFS mit mehreren Startknoten initialisieren
BFS mit mehreren Quellen beginnt gleichzeitig bei mehreren Knoten, indem die Warteschlange mit allen Quellknoten initialisiert und diese als besucht markiert werden. Dieses Verfahren wird bei Aufgaben wie „Entfernung zur nächsten 0“, „rottende Orangen“ und „Walls and Gates“ verwendet, wenn die kürzeste Entfernung von einem beliebigen Quellknoten benötigt wird. BFS mit mehreren Quellen läuft in O(V + E) – genauso wie BFS mit nur einer Quelle –, weil jeder Knoten weiterhin höchstens einmal besucht wird.
from collections import deque
def rotting_oranges(grid):
rows, cols = len(grid), len(grid[0])
queue = deque()
fresh = 0
# Multi-source: all rotten oranges start at time=0
for r in range(rows):
for c in range(cols):
if grid[r][c] == 2:
queue.append((r, c, 0)) # (row, col, time)
elif grid[r][c] == 1:
fresh += 1
dirs = [(0,1),(0,-1),(1,0),(-1,0)]
time = 0
while queue:
r, c, t = queue.popleft()
for dr, dc in dirs:
nr, nc = r+dr, c+dc
if 0<=nr<rows and 0<=nc<cols and grid[nr][nc]==1:
grid[nr][nc] = 2 # mark rotten
fresh -= 1
queue.append((nr, nc, t+1))
time = t + 1
return time if fresh == 0 else -1
print(rotting_oranges([[2,1,1],[1,1,0],[0,1,1]])) # 4Graphdichte und Wahl der Darstellung
Die Wahl zwischen Adjazenzliste und -matrix hängt von der Dichte des Graphen ab – dem Verhältnis E/V². Ein dünn besetzter Graph (E << V²) profitiert von Adjazenzlisten: O(V+E) Speicherplatz gegenüber O(V²) bei einer Matrix. Ein dichter Graph (E ≈ V²) profitiert von Adjazenzmatrizen: O(1)-Zugriff auf Kanten gegenüber O(degree) bei Listen. Bei Interviewaufgaben sind Adjazenzlisten fast immer die richtige Wahl, da die meisten Aufgaben dünn besetzte Graphen betreffen.
# Graph density comparison:
# Sparse: social network (V=1B users, avg 200 friends)
# E = 200 * 1B = 200B << V^2 = 10^18 -> adjacency list
# Dense: complete graph (every node connected to every other)
# E = V*(V-1)/2 ≈ V^2 -> adjacency matrix
# Interview rule of thumb:
# - Default to adjacency list (defaultdict(list))
# - Use matrix only when asked about dense graph or O(1) edge lookup
# - Grid problems: use implicit adjacency (4-directional neighbours)
print('Sparse graph (E << V^2): use adjacency list')
print('Dense graph (E ~ V^2): consider adjacency matrix')Schnelltest
Testen Sie Ihr Verständnis der Konzepte aus dieser Lektion in Data Structures & Algorithms — Coding Interview Prep.
Lektionsrückblick
In dieser Lektion haben Sie Folgendes gelernt: drei Darstellungen von Graphen (Adjazenzliste, Matrix, Kantenliste) und wann Sie welche wählen sollten, die Einrichtung von BFS und DFS mit Besuchsmengen, um Endlosschleifen in zyklischen Graphen zu vermeiden, sowie praktische Muster wie die direkte Markierung von Gitterzellen und BFS mit mehreren Quellen. Als Nächstes wenden wir BFS an, um kürzeste Wege und Ebenendurchläufe zu finden.
Häufig gestellte Fragen
Ist die Lektion „Graphdarstellungen und Vorbereitung der Traversierung“ kostenlos?
Ja — der vollständige Text von „Graphdarstellungen und Vorbereitung der Traversierung“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des DSA Interview Prep-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der DSA Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Graphdarstellungen und Vorbereitung der Traversierung“?
Erstellen Sie gerichtete und ungerichtete Graphen mit Adjazenzlisten, initialisieren Sie BFS mit einer Deque und DFS mit einem Stapel oder Rekursion und verfolgen Sie besuchte Knoten. Du übst DSA Interview Prep mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um DSA Interview Prep zu starten?
Keine Vorkenntnisse erforderlich. DSA Interview Prep auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 1 von 4.
Wie lange dauert die Lektion „Graphdarstellungen und Vorbereitung der Traversierung“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser DSA Interview Prep-Lektion Code schreiben und ausführen?
Ja. Jede DSA Interview Prep-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- Graphdarstellungen und Vorbereitung der Traversierung
- BFS: kürzester Pfad und Ebenendurchlauf
- DFS: Zusammenhangskomponenten und Flood Fill
- Zykluserkennung in gerichteten und ungerichteten Graphen