BFS: kortste pad en traversal per niveau
Gebruik BFS om het kortste pad in een ongewogen graaf te vinden, los word-ladder niveau voor niveau op en kloon een graaf met een hashmap.
BFS: kortste pad en traversal per niveau is een gratis Voorbereiding op programmeerinterviews-les op CoddyKit. Dit is les 2 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Voorbereiding op programmeerinterviews. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Voorbereiding op programmeerinterviews bevat in totaal 4 lessen.
BFS en het kortste pad in ongewogen grafen
BFS vindt het kortste pad (het minste aantal kanten) in een ongewogen graaf, omdat het knopen doorloopt in volgorde van oplopende afstand vanaf de bron. De eerste keer dat een knoop tijdens BFS wordt bereikt, gebeurt dat via een zo kort mogelijk pad. Deze eigenschap geldt niet voor DFS. Gebruik voor gewogen grafen met niet-negatieve gewichten in plaats daarvan het algoritme van Dijkstra — BFS behandelt alle kanten impliciet alsof ze gewicht 1 hebben.
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)Het daadwerkelijke kortste pad bijhouden
Om het daadwerkelijke pad te reconstrueren (niet alleen de lengte), houd je een woordenboek met voorgangers bij waarin staat via welke knoop elke knoop is bereikt. Zodra je de bestemming bereikt, volg je vanaf het einde de verwijzingen in deze voorgangerstabel terug naar het begin en draai je het resultaat om. Dit kost O(V) ruimte voor de voorgangerstabel, maar levert na afloop van BFS het volledige pad in O(pad_lengte)-tijd.
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]Woordladder: BFS op een impliciete graaf
Woordladder (LeetCode #127) vraagt naar het minimumaantal wijzigingen van één teken om een beginwoord in een eindwoord te veranderen, waarbij elk tussenliggend woord in een woordenboek moet staan. Dit is BFS op een impliciete graaf waarin woorden knopen zijn en kanten woorden verbinden die één letter verschillen. Genereer alle wijzigingen van één letter en controleer of ze in de woordverzameling staan. BFS garandeert de minimale reeks transformaties.
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'])) # 5Niveaudoorloop: afstand bijhouden
Een niveaudoorloop groepeert knopen op basis van hun afstand tot de bron, wat direct nuttig is voor opgaven die verwerking per niveau vereisen. Houd de afstand bij door deze in het wachtrijelement op te slaan als een tupel (node, dist), of gebruik de techniek met de wachtrijgrootte (sla de wachtrijgrootte vóór elk niveau op, verwerk precies zoveel knopen en verhoog daarna een niveauteller). Beide aanpakken geven identieke resultaten.
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}Graaf klonen
Graaf klonen (LeetCode #133) maakt een diepe kopie van een verbonden ongerichte graaf. Gebruik BFS en een hashtabel die oorspronkelijke knopen aan hun klonen koppelt. Wanneer je een knoop voor het eerst bezoekt, maak je de kloon ervan en voeg je die aan de tabel toe. Zoek bij het verwerken van buren hun klonen op of maak ze aan en verbind de kanten. De hashtabel heeft twee functies: bezochte knopen bijhouden en oorspronkelijke knopen aan kopieën koppelen.
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]Bidirectionele BFS
Bidirectionele BFS start gelijktijdig vanaf de bron en de bestemming en breidt vanaf beide uiteinden telkens één niveau uit. Wanneer de twee zoekfronten elkaar ontmoeten, heb je het kortste pad gevonden. Voor grote grafen verkleint dit de zoekruimte van O(b^d) naar O(2 * b^(d/2)), waarbij b de vertakkingsfactor is en d de padlengte — een enorme verbetering voor sterk vertakte grafen, zoals een woordladder met grote woordenboeken.
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 voor gewogen grafen
0-1 BFS verwerkt grafen waarin kantgewichten alleen 0 of 1 zijn. Gebruik in plaats van een gewone wachtrij een deque: voeg kanten met gewicht 1 achteraan toe (volgend niveau) en kanten met gewicht 0 vooraan (zelfde niveau). Hiermee bereken je kortste paden in O(V + E) — sneller dan Dijkstra's O((V+E) log V) wanneer gewichten binair zijn. Dit komt vaak voor in rasteropgaven waarin sommige zetten gratis zijn en 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]Muren en poorten (BFS met meerdere bronnen)
Muren en poorten vult elke lege kamer met de afstand tot de dichtstbijzijnde poort. Gebruik BFS met meerdere bronnen: initialiseer de wachtrij gelijktijdig met alle poorten (waarde 0) en breid vanuit daar naar buiten uit. De waarde van elke cel wordt ingesteld op het niveau waarop die cel voor het eerst wordt bereikt. Deze O(mn)-oplossing is efficiënter dan BFS afzonderlijk vanuit elke lege kamer uitvoeren, wat O(m²n²) zou kosten.
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, 2BFS voor slangen en ladders
Slangen en ladders (LeetCode #909) is een probleem over kortste paden met BFS op een getallenraster. Modelleer het bord als een ongewogen graaf waarin je vanaf elk vakje 1-6 kunt gooien en op een slang of ladder kunt landen die je teleporteert. BFS vindt het minimale aantal dobbelsteenworpen. De belangrijkste uitdaging is de omzetting tussen een eendimensionale positie en tweedimensionale bordcoördinaten, rekening houdend met de boustrophedonindeling (afwisselende richting per rij).
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')Complexiteit en optimalisaties van BFS
De tijdcomplexiteit van BFS is O(V + E), omdat elke knoop één keer aan de wachtrij wordt toegevoegd en elke kant een constant aantal keer wordt onderzocht. De ruimtecomplexiteit is O(V) voor de verzameling van bezochte knopen en de wachtrij. Voor rastergrafen geldt V = m*n en E = 4*m*n (elke cel heeft 4 buren), dus BFS op een raster is O(mn). Belangrijke optimalisatie: gebruik een verzameling voor bezochte knopen (opzoeken in O(1)), geen lijst (opzoeken in O(n)). Markeer een knoop als bezocht wanneer je die aan de wachtrij toevoegt, niet wanneer je die eruit haalt.
# 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')Dichtstbijzijnde 0 in een binaire matrix
01-matrix (LeetCode #542) vindt de afstand van elke cel tot de dichtstbijzijnde 0. BFS met meerdere bronnen vanaf alle nullen tegelijk geeft de optimale oplossing in O(mn). Initialiseer de wachtrij met alle 0-cellen op afstand 0 en alle 1-cellen op oneindige afstand. BFS verspreidt de afstanden vanuit de nullen naar buiten en stelt de afstand van elke 1-cel in wanneer die voor het eerst wordt bereikt (wat gegarandeerd de kortste afstand is).
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]]Korte controle
Toets je begrip van de concepten uit Data Structures & Algorithms — Coding Interview Prep in deze les.
Samenvatting van de les
In deze les heb je geleerd: BFS voor kortste paden in ongewogen grafen, met het bijhouden van voorgangers om routes te reconstrueren, woordladder als een kenmerkend voorbeeld van BFS op een impliciete graaf, bidirectionele BFS voor grote grafen en BFS met meerdere bronnen voor opgaven met meerdere startpunten. Hierna passen we DFS toe op verbonden componenten en vlakvulling.
Leer Voorbereiding op programmeerinterviews met een AI-tutor — gratis
Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.
- Cursussen
- 90
- Lessen
- 360
Veelgestelde vragen
Is de les “BFS: kortste pad en traversal per niveau” gratis?
Ja — de volledige tekst van “BFS: kortste pad en traversal per niveau” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus Voorbereiding op programmeerinterviews wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus Voorbereiding op programmeerinterviews bevat in totaal 4 lessen.
Wat leer ik in “BFS: kortste pad en traversal per niveau”?
Gebruik BFS om het kortste pad in een ongewogen graaf te vinden, los word-ladder niveau voor niveau op en kloon een graaf met een hashmap. Je oefent met Voorbereiding op programmeerinterviews door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.
Heb ik ervaring nodig om met Voorbereiding op programmeerinterviews te beginnen?
Ervaring vooraf is niet nodig. Voorbereiding op programmeerinterviews op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 2 van 4.
Hoe lang duurt de les “BFS: kortste pad en traversal per niveau”?
De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.
Kan ik code schrijven en uitvoeren in deze les over Voorbereiding op programmeerinterviews?
Ja. Elke les over Voorbereiding op programmeerinterviews bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.
Alle lessen in deze cursus
- Graafrepresentaties en voorbereiding van traversals
- BFS: kortste pad en traversal per niveau
- DFS: verbonden componenten en flood fill
- Cycli detecteren in gerichte en ongerichte grafen