Grafen doorzoeken: BFS en DFS
In 1736 toonde Leonhard Euler aan dat geen wandeling elk van de zeven bruggen van Koningsbergen precies eenmaal oversteekt. Met dat antwoord begon de grafentheorie.
Punten en lijnen
Een graaf is een verzameling punten, knopen genoemd, verbonden door lijnen, kanten genoemd. Het idee is eenvoudig en past bij enorm veel dingen: steden verbonden door wegen, webpagina's door links, mensen door vriendschappen en doolhofvakjes verbonden waar geen muur is. Zodra je een probleem als graaf tekent, kunnen dezelfde paar algoritmen vragen beantwoorden zoals ‘kan ik van hier naar daar?’ en ‘wat is de kortste weg?’
Een graaf opslaan in code
Een gebruikelijke manier om een graaf op te slaan is een burenlijst: voor elke knoop een lijst van zijn buren. De notities bij MIT 6.006 gebruiken precies dit: een array of woordenboek Adj waarin Adj[u] de met u verbonden knopen opsomt. Voor de graaf hierboven ziet dat er zo uit:
const graph = {
A: ["B", "C"],
B: ["A", "D"],
C: ["A", "D", "E"],
D: ["B", "C", "F"],
E: ["C", "F"],
F: ["D", "E"],
};Breedte-eerst zoeken: ring voor ring
Breedte-eerst zoeken (BFS) verkent een graaf in ringen rond de start. Eerst de start zelf, op afstand 0. Dan alle buren, op afstand 1. Dan elke nieuwe knoop naast die buren, op afstand 2, enzovoort. Hiervoor houdt het een wachtrij bij, waarin wie eerst binnenkomt er eerst uitgaat. Neem de voorste knoop, bezoek die en voeg nog ongeziene buren achteraan toe. Vanaf A bezoekt BFS in het voorbeeld A, dan B en C, dan D en E en dan F. Omdat het ring voor ring naar buiten gaat, vindt BFS bij de eerste keer bereiken van een knoop een pad met de minste kanten. F ligt 3 kanten van A, bijvoorbeeld A, B, D, F.
function bfs(graph, start) {
const seen = new Set([start]);
const queue = [start];
const order = [];
while (queue.length) {
const u = queue.shift(); // first in, first out
order.push(u);
for (const v of graph[u]) {
if (!seen.has(v)) { seen.add(v); queue.push(v); }
}
}
return order; // A B C D E F
}Diepte-eerst zoeken: zo ver mogelijk, dan terug
Diepte-eerst zoeken (DFS) gedraagt zich als iemand die in een doolhof één hand aan de muur houdt. Het volgt één pad zo diep mogelijk. Bij een doodlopend einde of al bezochte plek gaat het terug naar het recentste keuzepunt en probeert de volgende optie. Het gebruikt een stapel in plaats van een wachtrij, laatst erin, eerst eruit, en is heel natuurlijk recursief te schrijven, waarbij de aanroepstapel het onthouden doet. Vanaf A bezoekt recursieve DFS A, B, D, C, E, F. DFS bereikt elke knoop die BFS bereikt, maar het gevolgde pad is niet noodzakelijk het kortste.
function dfs(graph, u, seen = new Set(), order = []) {
seen.add(u);
order.push(u);
for (const v of graph[u]) if (!seen.has(v)) dfs(graph, v, seen, order);
return order; // A B D C E F
}Sleep om te draaien. Scroll of knijp om te zoomen.
Welke kies je?
In het eerste doolhof van het lab bezoekt BFS 105 vakjes en vindt een pad van 22 zetten, het kortst mogelijke. DFS gaat toevallig richting het doel en bezoekt maar 31 vakjes, maar het pad kost 28 zetten. Geen van beide is altijd goedkoper. Beide verwerken elke knoop en elke kant een vast aantal keren en hebben dus looptijd O(V + E) voor V knopen en E kanten. Kies BFS als je de minste stappen nodig hebt: de minste zetten in een puzzel of de minste verbindingen tussen twee mensen. Kies DFS als je alles wilt verkennen of keuzes tot het einde wilt volgen: controleren of elke doolhofkamer verbonden is of puzzelzetten één reeks tegelijk proberen en teruggaan als je vastloopt. Als kanten verschillende lengten hebben, zoals wegen, is geen van beide op zichzelf genoeg; methoden zoals het algoritme van Dijkstra breiden het BFS-idee uit met afstanden.
| graaf | Knopen verbonden door kanten |
| knoop | Een punt van een graaf |
| kant | Een lijn die twee knopen verbindt |
| burenlijst | Voor elke knoop de lijst met buren |
| wachtrij | Eerst erin, eerst eruit; gebruikt door BFS |
| BFS | Breedte-eerst zoeken: ring voor ring; vindt paden met de minste kanten |
| DFS | Diepte-eerst zoeken: eerst diep, dan terug; gebruikt een stapel |
| O(V + E) | De looptijd van BFS en DFS |
- Hoeveel kanten telt het kortste pad van A naar F in de voorbeeldgraaf?
- Hoeveel kanten heeft de voorbeeldgraaf?
- Welke gegevensstructuur gebruikt breedte-eerst zoeken?
- Een wachtrij (eerst erin, eerst eruit)
- Een stapel (laatst erin, eerst eruit)
- Een gesorteerde lijst
- Helemaal geen structuur
- Welke zoekmethode vindt gegarandeerd een pad met de minste kanten?
- BFS
- DFS
- Beide, altijd
- Geen van beide
- Zet de knopen op de bezoekvolgorde van BFS vanaf A (buren alfabetisch genomen).
- Zet de knopen op de bezoekvolgorde van recursieve DFS vanaf A (buren alfabetisch).
- Hoeveel zetten telt het kortste pad van S (linksboven) naar G (rechtsonder) op het rooster van 15 bij 9 in het lab, met alle muren weggehaald?
- Waarom moeten BFS en DFS onthouden welke knopen ze hebben bezocht?
- Anders zou een kring in de graaf het zoeken eindeloos laten rondgaan
- Om ze aan het einde af te drukken
- Om ze te sorteren
- Het is alleen nodig voor DFS
- In welk jaar loste Euler het bruggenprobleem van Koningsbergen op?
- 1683
- 1736
- 1843
- 1945
- Wat is de looptijd van BFS op een graaf met V knopen en E kanten?
- O(V + E)
- O(V × E)
- O(log V)
- O(2ⱽ)
- Je wilt het kleinste aantal vriend-van-een-vriendverbindingen tussen twee mensen in een sociaal netwerk weten. Welke zoekmethode?
- BFS
- DFS
- Binair zoeken
- Mergesort
- Een graaf bestaat uit knopen verbonden door kanten; burenlijsten slaan die op.
- BFS gebruikt een wachtrij, gaat ring voor ring en vindt paden met de minste kanten.
- DFS gebruikt een stapel (of recursie), gaat eerst diep en dan terug.
- Beide werken in O(V + E) en moeten bezochte knopen onthouden.
- Met de oplossing van Euler uit 1736 voor de bruggen van Koningsbergen begon de grafentheorie.
Bronnen van deze cursus (11)
- MIT OpenCourseWare 6.006 Introduction to Algorithms, Spring 2020 (Demaine, Ku, Solomon)
- lecture notes 1 Introduction, 3 Sorting, 9 Breadth-First Search, 10 Depth-First Search (ocw.mit.edu/courses/6-006-introduction-to-algorithms-spring-2020/pages/lecture-notes) and Fall 2011 lecture 13 BFS notes (ocw.mit.edu/courses/6-006-introduction-to-algorithms-fall-2011)
- Cormen, Leiserson, Rivest, Stein, Introduction to Algorithms (MIT Press)
- with the CLRS-based Khan Academy algorithms unit by Thomas Cormen and Devin Balkcom of Dartmouth (khanacademy.org/computing/computer-science/algorithms: binary search, selection sort analysis, merge sort, quicksort overview and analysis)
- MacTutor History of Mathematics, Al-Khwarizmi (mathshistory.st-andrews.ac.uk/Biographies/Al-Khwarizmi: word algorithm from the Latin title Algoritmi de numero Indorum)
- D. E. Joyce, Euclid's Elements Book VII Propositions 1 and 2 (mathcs.clarku.edu/~djoyce/elements/bookVII)
- Wolfram MathWorld, Merge Sort (mathworld.wolfram.com/MergeSort.html: proposed by John von Neumann in 1945, after Knuth 1998 p. 159)
- Stanford CS "Tony Hoare: Quicksort" (cs.stanford.edu/people/eroberts/courses/soco/projects/2008-09/tony-hoare/quicksort.html: devised while a student at Moscow State University for Russian-English machine translation with a tape dictionary, programmed in Algol 60 and published 1961; the year 1959 from the Wikipedia Quicksort article and its cited sources)
- Bodleian Libraries, Ada Lovelace (blogs.bodleian.ox.ac.uk/adalovelace: 1843 paper, Note G Bernoulli numbers)
- Fourmilab text of Menabrea's Sketch with Lovelace's notes (fourmilab.ch/babbage/sketch.html)
- Seven Bridges of Konigsberg (Euler 1736) and Tower of Hanoi (Edouard Lucas, 1883) checked against the Wikipedia articles and their cited sources.
Geraadpleegd: 2026-10-17