0Pricing
DSA Interview Prep · Lektion

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 DSA 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 DSA Interview Prep-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der DSA 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']))  # 5

Ebenendurchlauf: 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']))  # 5

0-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, 2

Snakes 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 DSA Interview Prep-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der DSA 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 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 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 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

  1. Graphdarstellungen und Vorbereitung der Traversierung
  2. BFS: kürzester Pfad und Ebenendurchlauf
  3. DFS: Zusammenhangskomponenten und Flood Fill
  4. Zykluserkennung in gerichteten und ungerichteten Graphen
← Zurück zu DSA Interview Prep