BFS: kürzester Pfad und Ebenendurchlauf
Verwenden Sie BFS, um den kürzesten Pfad in einem ungewichteten Graphen zu finden, lösen Sie word-ladder Ebene für Ebene und klonen Sie einen Graphen mit einer Hash-Map.
BFS: kürzester Pfad und Ebenendurchlauf ist eine kostenlose Coding Interview Prep-Lektion auf CoddyKit. Dies ist Lektion 2 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 Coding Interview Prep-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Coding Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
BFS und kürzeste Wege in ungewichteten Graphen
BFS findet den kürzesten Weg (mit den wenigsten Kanten) in einem ungewichteten Graphen, da die Knoten in der Reihenfolge zunehmender Entfernung von der Quelle erkundet werden. Wenn ein Knoten während der BFS zum ersten Mal erreicht wird, geschieht dies über den kürzest möglichen Weg. Diese Eigenschaft gilt nicht für DFS. Verwenden Sie bei gewichteten Graphen mit nichtnegativen Gewichten stattdessen den Dijkstra-Algorithmus – BFS behandelt implizit alle Kanten so, als hätten sie das Gewicht 1.
from collections import deque, defaultdict
def shortest_path(graph, start, end):
if start == end:
return 0
visited = {start}
queue = deque([(start, 0)]) # (node, distance)
while queue:
node, dist = queue.popleft()
for neighbour in graph[node]:
if neighbour == end:
return dist + 1
if neighbour not in visited:
visited.add(neighbour)
queue.append((neighbour, dist + 1))
return -1 # no path found
graph = defaultdict(list)
for u, v in [(0,1),(1,2),(2,3),(0,3),(1,4)]:
graph[u].append(v); graph[v].append(u)
print(shortest_path(graph, 0, 3)) # 1 (direct edge)
print(shortest_path(graph, 0, 4)) # 2 (0->1->4)Den tatsächlichen kürzesten Weg verfolgen
Um den tatsächlichen Weg (nicht nur seine Länge) zu rekonstruieren, verwalten Sie eine Elternzuordnung, die festhält, wie jeder Knoten erreicht wurde. Wenn Sie das Ziel erreichen, verfolgen Sie die Elternzuordnung vom Ende zurück zum Anfang und kehren Sie das Ergebnis um. Dafür werden O(V) Speicherplatz für die Elternzuordnung benötigt, anschließend steht der vollständige Weg in O(path_length) Zeit zur Verfügung.
from collections import deque, defaultdict
def shortest_path_with_route(graph, start, end):
parent = {start: None}
queue = deque([start])
while queue:
node = queue.popleft()
if node == end:
break
for nb in graph[node]:
if nb not in parent:
parent[nb] = node
queue.append(nb)
if end not in parent:
return [] # no path
# Reconstruct path by tracing back
path = []
node = end
while node is not None:
path.append(node)
node = parent[node]
return path[::-1] # reverse
graph = defaultdict(list)
for u, v in [(0,1),(1,2),(2,3),(0,4),(4,3)]:
graph[u].append(v); graph[v].append(u)
print(shortest_path_with_route(graph, 0, 3)) # [0, 4, 3] or [0, 1, 2, 3]Word Ladder: BFS auf einem impliziten Graphen
Word Ladder (LeetCode #127) verlangt die minimale Anzahl einzelner Zeichenänderungen, um ein Startwort in ein Endwort umzuwandeln, wobei jedes Zwischenwort in einem Wörterbuch enthalten sein muss. Dies ist eine BFS auf einem impliziten Graphen, in dem Wörter Knoten sind und Kanten Wörter verbinden, die sich in einem Buchstaben unterscheiden. Erzeugen Sie alle Mutationen mit einem geänderten Buchstaben und prüfen Sie, ob sie in der Wortmenge enthalten sind. BFS garantiert die kürzeste Umwandlungsfolge.
from collections import deque
def word_ladder(begin_word, end_word, word_list):
word_set = set(word_list)
if end_word not in word_set:
return 0
queue = deque([(begin_word, 1)])
visited = {begin_word}
while queue:
word, steps = queue.popleft()
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
new_word = word[:i] + c + word[i+1:]
if new_word == end_word:
return steps + 1
if new_word in word_set and new_word not in visited:
visited.add(new_word)
queue.append((new_word, steps + 1))
return 0
print(word_ladder('hit', 'cog', ['hot','dot','dog','lot','log','cog'])) # 5Ebenendurchlauf: Entfernung verfolgen
Beim Ebenendurchlauf werden Knoten nach ihrer Entfernung von der Quelle gruppiert. Das ist direkt für Aufgaben nützlich, bei denen eine Verarbeitung pro Ebene erforderlich ist. Speichern Sie die Entfernung entweder im Warteschlangenelement als Tupel (node, dist) oder verwenden Sie die Technik über die Warteschlangengröße (erfassen Sie die Warteschlangengröße vor jeder Ebene, verarbeiten Sie genau so viele Knoten und erhöhen Sie anschließend einen Ebenenzähler). Beide Ansätze liefern identische Ergebnisse.
from collections import deque, defaultdict
def bfs_levels(graph, start):
levels = {}
visited = {start}
queue = deque([start])
dist = 0
while queue:
# Process all nodes at current distance
for _ in range(len(queue)):
node = queue.popleft()
levels[node] = dist
for nb in graph[node]:
if nb not in visited:
visited.add(nb)
queue.append(nb)
dist += 1
return levels
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_levels(graph, 0)) # {0:0, 1:1, 2:1, 3:2, 4:3}Clone Graph
Clone Graph (LeetCode #133) erstellt eine tiefe Kopie eines zusammenhängenden ungerichteten Graphen. Verwenden Sie BFS und eine Hash-Map, die Originalknoten ihren Klonen zuordnet. Wenn Sie einen Knoten zum ersten Mal besuchen, erstellen Sie seinen Klon und fügen ihn der Zuordnung hinzu. Beim Verarbeiten der Nachbarn schlagen Sie deren Klone nach oder erstellen sie und verbinden anschließend die Kanten. Die Hash-Map erfüllt zwei Aufgaben: Sie verfolgt besuchte Knoten und ordnet Originale ihren Kopien zu.
from collections import deque
class Node:
def __init__(self, val=0, neighbors=None):
self.val = val
self.neighbors = neighbors if neighbors is not None else []
def clone_graph(node):
if not node:
return None
old_to_new = {node: Node(node.val)}
queue = deque([node])
while queue:
curr = queue.popleft()
for nb in curr.neighbors:
if nb not in old_to_new:
old_to_new[nb] = Node(nb.val)
queue.append(nb)
old_to_new[curr].neighbors.append(old_to_new[nb])
return old_to_new[node]
# Build a simple graph: 1 -- 2 -- 3 -- 4 -- 1
n1 = Node(1); n2 = Node(2); n3 = Node(3); n4 = Node(4)
n1.neighbors = [n2, n4]; n2.neighbors = [n1, n3]
n3.neighbors = [n2, n4]; n4.neighbors = [n3, n1]
cloned = clone_graph(n1)
print(cloned.val, [n.val for n in cloned.neighbors]) # 1 [2, 4]Bidirektionale BFS
Bidirektionale BFS startet gleichzeitig eine BFS an der Quelle und am Ziel und erweitert von beiden Enden jeweils eine Ebene. Wenn sich die beiden Suchfronten treffen, wurde der kürzeste Weg gefunden. Bei großen Graphen reduziert dies den Suchraum von O(b^d) auf O(2 * b^(d/2)), wobei b der Verzweigungsfaktor und d die Weglänge ist – eine deutliche Verbesserung bei tief verbundenen Graphen wie Word Ladder mit großen Wörterbüchern.
from collections import defaultdict
def word_ladder_bidir(begin, end, word_list):
word_set = set(word_list)
if end not in word_set:
return 0
front, back = {begin}, {end}
visited = {begin, end}
steps = 1
while front and back:
# Always expand the smaller frontier
if len(front) > len(back):
front, back = back, front
next_front = set()
for word in front:
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
nw = word[:i] + c + word[i+1:]
if nw in back: # frontiers met!
return steps + 1
if nw in word_set and nw not in visited:
visited.add(nw)
next_front.add(nw)
front = next_front
steps += 1
return 0
print(word_ladder_bidir('hit','cog',['hot','dot','dog','lot','log','cog'])) # 50-1-BFS für gewichtete Graphen
0-1-BFS verarbeitet Graphen, deren Kantengewichte ausschließlich 0 oder 1 sind. Verwenden Sie statt einer regulären Warteschlange eine Deque: Fügen Sie Kanten mit Gewicht 1 hinten an (nächste Ebene) und Kanten mit Gewicht 0 vorne (gleiche Ebene). Damit lässt sich der kürzeste Weg in O(V + E) berechnen – schneller als Dijkstras O((V+E) log V), wenn die Gewichte binär sind. Dieses Verfahren ist bei Gitteraufgaben üblich, bei denen einige Bewegungen kostenlos sind und andere 1 kosten.
from collections import deque
def zero_one_bfs(graph, start, n):
# graph: list of (neighbour, weight) where weight is 0 or 1
dist = [float('inf')] * n
dist[start] = 0
dq = deque([start])
while dq:
node = dq.popleft()
for nb, w in graph[node]:
if dist[node] + w < dist[nb]:
dist[nb] = dist[node] + w
if w == 0:
dq.appendleft(nb) # same level
else:
dq.append(nb) # next level
return dist
# Simple test:
graph = [[(1, 0), (2, 1)], # node 0: free to 1, cost 1 to 2
[(3, 1)], # node 1: cost 1 to 3
[(3, 0)], # node 2: free to 3
[]]
print(zero_one_bfs(graph, 0, 4)) # [0, 0, 1, 1]Walls and Gates (BFS mit mehreren Quellen)
Walls and Gates füllt jeden leeren Raum mit der Entfernung zum nächstgelegenen Tor. Verwenden Sie BFS mit mehreren Quellen: Initialisieren Sie die Warteschlange gleichzeitig mit allen Toren (Wert 0) und breiten Sie sich nach außen aus. Der Wert jeder Zelle wird auf die Ebene gesetzt, auf der sie zum ersten Mal erreicht wird. Diese Lösung in O(mn) ist effizienter als eine separate BFS von jedem leeren Raum aus, die O(m²n²) benötigen würde.
from collections import deque
def walls_and_gates(rooms):
if not rooms:
return
rows, cols = len(rooms), len(rooms[0])
INF = float('inf')
queue = deque()
# Multi-source: all gates at distance 0
for r in range(rows):
for c in range(cols):
if rooms[r][c] == 0: # gate
queue.append((r, c))
dirs = [(0,1),(0,-1),(1,0),(-1,0)]
while queue:
r, c = queue.popleft()
for dr, dc in dirs:
nr, nc = r+dr, c+dc
if 0<=nr<rows and 0<=nc<cols and rooms[nr][nc]==INF:
rooms[nr][nc] = rooms[r][c] + 1
queue.append((nr, nc))
rooms = [[float('inf'),-1,0,float('inf')],
[float('inf'),float('inf'),float('inf'),-1],
[float('inf'),-1,float('inf'),-1],
[0,-1,float('inf'),float('inf')]]
walls_and_gates(rooms)
print(rooms[0][0], rooms[1][1]) # 3, 2Snakes and Ladders mit BFS
Snakes and Ladders (LeetCode #909) ist ein BFS-Problem zur Suche nach dem kürzesten Weg auf einem Zahlenraster. Modellieren Sie das Spielfeld als ungewichteten Graphen, in dem Sie von jedem Feld aus 1–6 Felder würfeln können und möglicherweise auf einer Schlange oder Leiter landen, die Sie teleportiert. BFS findet die minimale Anzahl an Würfen. Die zentrale Herausforderung besteht darin, zwischen der eindimensionalen Position und den zweidimensionalen Spielfeldkoordinaten umzurechnen und dabei die Boustrophedon-Anordnung (abwechselnde Zeilenrichtung) zu berücksichtigen.
from collections import deque
def snakes_and_ladders(board):
n = len(board)
def get_board(pos):
r, c = divmod(pos - 1, n)
if r % 2 == 1: c = n - 1 - c # alternating direction
return board[n - 1 - r][c]
visited = {1}
queue = deque([(1, 0)])
while queue:
pos, moves = queue.popleft()
for dice in range(1, 7):
next_pos = pos + dice
if next_pos > n * n:
break
val = get_board(next_pos)
if val != -1:
next_pos = val # snake or ladder
if next_pos == n * n:
return moves + 1
if next_pos not in visited:
visited.add(next_pos)
queue.append((next_pos, moves + 1))
return -1
print('BFS models game as an unweighted shortest-path problem')BFS-Komplexität und Optimierungen
Die Zeitkomplexität von BFS beträgt O(V + E), weil jeder Knoten einmal in die Warteschlange eingereiht wird und jede Kante eine konstante Anzahl von Malen untersucht wird. Die Komplexität des Speicherbedarfs beträgt O(V) für die Besuchsmenge und die Warteschlange. Für Gittergraphen gilt V = m*n und E = 4*m*n (jede Zelle hat 4 Nachbarn), daher ist BFS in einem Gitter O(mn). Eine wichtige Optimierung: Verwenden Sie für visited eine Menge (O(1)-Suche) statt einer Liste (O(n)-Suche). Markieren Sie Knoten beim Einreihen, nicht beim Entfernen aus der Warteschlange.
# BFS on a graph with V vertices and E edges:
# Time: O(V + E) -- each vertex and edge visited once
# Space: O(V) -- visited set + queue
# BFS on an m x n grid:
# V = m*n cells
# E <= 4*m*n edges (4 directions, max)
# Time: O(m*n)
# Space: O(m*n)
# Common pitfalls:
# 1. Marking visited on dequeue (not enqueue) -> same node queued multiple times
# 2. Using a list for visited -> O(n) membership check -> O(V*E) total
# 3. Not handling disconnected graph -> BFS from single source misses components
print('O(V+E) time, O(V) space -- mark visited on enqueue')Nächstgelegenes 0 in einer Binärmatrix
01 Matrix (LeetCode #542) ermittelt die Entfernung jeder Zelle zur nächstgelegenen 0. Eine BFS mit mehreren Quellen, die gleichzeitig von allen 0-Zellen startet, liefert die optimale Lösung in O(mn). Initialisieren Sie die Warteschlange mit allen 0-Zellen in Entfernung 0 und allen 1-Zellen mit unendlicher Entfernung. BFS breitet die Entfernungen von den 0-Zellen nach außen aus und setzt die Entfernung jeder 1-Zelle beim ersten Erreichen fest (dies ist garantiert die kürzeste Entfernung).
from collections import deque
def update_matrix(mat):
rows, cols = len(mat), len(mat[0])
dist = [[float('inf')] * cols for _ in range(rows)]
queue = deque()
for r in range(rows):
for c in range(cols):
if mat[r][c] == 0:
dist[r][c] = 0
queue.append((r, c))
dirs = [(0,1),(0,-1),(1,0),(-1,0)]
while queue:
r, c = queue.popleft()
for dr, dc in dirs:
nr, nc = r+dr, c+dc
if 0<=nr<rows and 0<=nc<cols:
if dist[r][c] + 1 < dist[nr][nc]:
dist[nr][nc] = dist[r][c] + 1
queue.append((nr, nc))
return dist
mat = [[0,0,0],[0,1,0],[1,1,1]]
result = update_matrix(mat)
for row in result: print(row) # [[0,0,0],[0,1,0],[1,2,1]]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: BFS für kürzeste Wege in ungewichteten Graphen mit Verfolgung der Elternzuordnung zur Rekonstruktion von Wegen, Word Ladder als kanonische BFS auf einem impliziten Graphen, bidirektionale BFS für große Graphen und BFS mit mehreren Quellen für Aufgaben mit mehreren Startpunkten. Als Nächstes wenden wir DFS auf zusammenhängende Komponenten und Flood Fill an.
Häufig gestellte Fragen
Ist die Lektion „BFS: kürzester Pfad und Ebenendurchlauf“ kostenlos?
Ja — der vollständige Text von „BFS: kürzester Pfad und Ebenendurchlauf“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Coding Interview Prep-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Coding Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „BFS: kürzester Pfad und Ebenendurchlauf“?
Verwenden Sie BFS, um den kürzesten Pfad in einem ungewichteten Graphen zu finden, lösen Sie word-ladder Ebene für Ebene und klonen Sie einen Graphen mit einer Hash-Map. Du übst Coding 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 Coding Interview Prep zu starten?
Keine Vorkenntnisse erforderlich. Coding 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 2 von 4.
Wie lange dauert die Lektion „BFS: kürzester Pfad und Ebenendurchlauf“?
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 Coding Interview Prep-Lektion Code schreiben und ausführen?
Ja. Jede Coding 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