Ricerca nei grafi: BFS e DFS
Nel 1736 Leonhard Euler dimostrò che nessuna passeggiata attraversa ciascuno dei sette ponti di Königsberg una volta sola, e con quella risposta nacque la teoria dei grafi.
Punti e linee
Un grafo è un insieme di punti, detti vertici o nodi, uniti da linee, dette archi. L'idea è semplice e si adatta a moltissime cose: città unite da strade, pagine web unite da link, persone unite da amicizie, caselle di un labirinto unite dove non c'è un muro. Una volta disegnato un problema come grafo, la stessa manciata di algoritmi può rispondere a domande come «posso arrivare da qui a lì?» e «qual è la strada più breve?».
Memorizzare un grafo nel codice
Un modo comune per memorizzare un grafo è la lista di adiacenza: per ogni vertice, la lista dei suoi vicini. Gli appunti del corso 6.006 del MIT usano proprio questo, un array o dizionario Adj in cui Adj[u] elenca i vertici uniti a u. Per il grafo qui sopra si presenta così:
const graph = {
A: ["B", "C"],
B: ["A", "D"],
C: ["A", "D", "E"],
D: ["B", "C", "F"],
E: ["C", "F"],
F: ["D", "E"],
};Visita in ampiezza: anello per anello
La visita in ampiezza (BFS) esplora un grafo ad anelli attorno alla partenza. Prima la partenza stessa, a distanza 0. Poi tutti i suoi vicini, a distanza 1. Poi ogni nuovo vertice accanto a questi, a distanza 2, e così via. Per farlo usa una coda, una fila in cui il primo a entrare è il primo a uscire. Prendi il vertice in testa alla coda, visitalo e aggiungi in fondo i suoi vicini non ancora visti. Partendo da A nell'esempio, la BFS visita A, poi B e C, poi D ed E, poi F. Poiché si allarga un anello alla volta, la prima volta che la BFS raggiunge un vertice ha trovato un percorso con il minor numero di archi. F è a 3 archi da A, per esempio 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(); // il primo entrato è il primo a uscire
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
}Visita in profondità: più avanti possibile, poi indietro
La visita in profondità (DFS) si comporta come una persona in un labirinto che tiene sempre una mano sul muro. Segue un percorso il più in profondità possibile. Quando arriva a un vicolo cieco o in un punto già visto, torna indietro all'ultimo punto di scelta e prova l'opzione successiva. Usa una pila invece di una coda (l'ultimo entrato è il primo a uscire) ed è molto naturale da scrivere con la ricorsione, dove è la pila delle chiamate a ricordare. Partendo da A, una DFS ricorsiva visita A, B, D, C, E, F. La DFS raggiunge tutti i vertici che raggiunge la BFS, ma il percorso che segue non è per forza il più breve.
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
}Trascina per ruotare. Scorri o avvicina le dita per lo zoom.
Quale usare
Nel primo labirinto del laboratorio, la BFS visita 105 caselle e trova un percorso di 22 mosse, il più breve possibile. La DFS per caso si tuffa verso l'arrivo e visita solo 31 caselle, ma il suo percorso richiede 28 mosse. Nessuna delle due costa sempre meno. Entrambe trattano ogni vertice e ogni arco un numero fisso di volte, quindi entrambe girano in tempo O(V + E) per V vertici ed E archi. Scegli la BFS quando ti servono meno passi possibile: il minor numero di mosse in un rompicapo, il minor numero di legami tra due persone. Scegli la DFS quando devi esplorare tutto o seguire le scelte fino in fondo: controllare se tutte le stanze di un labirinto sono collegate, oppure provare le mosse di un rompicapo una linea alla volta e tornare indietro quando sei bloccato. Quando gli archi hanno lunghezze diverse, come le strade, nessuna delle due basta da sola; metodi come l'algoritmo di Dijkstra estendono l'idea della BFS con le distanze.
| grafo | Vertici uniti da archi |
| vertice (nodo) | Un punto di un grafo |
| arco | Una linea che unisce due vertici |
| lista di adiacenza | Per ogni vertice, la lista dei suoi vicini |
| coda | Il primo entrato è il primo a uscire; la usa la BFS |
| BFS | Visita in ampiezza: anello per anello; trova i percorsi con meno archi |
| DFS | Visita in profondità: prima in fondo, poi indietro; usa una pila |
| O(V + E) | Il tempo di esecuzione di BFS e DFS |
- Nel grafo di esempio, quanti archi ha il percorso più breve da A a F?
- Quanti archi ha il grafo di esempio?
- Quale struttura dati usa la visita in ampiezza?
- Una coda (il primo entrato è il primo a uscire)
- Una pila (l'ultimo entrato è il primo a uscire)
- Una lista ordinata
- Nessuna struttura
- Quale ricerca trova di sicuro un percorso con il minor numero di archi?
- BFS
- DFS
- Entrambe, sempre
- Nessuna delle due
- Metti i vertici nell'ordine in cui li visita la BFS partendo da A (vicini presi in ordine alfabetico).
- Metti i vertici nell'ordine in cui li visita la DFS ricorsiva partendo da A (vicini in ordine alfabetico).
- Nella griglia 15 per 9 del laboratorio, senza nessun muro, di quante mosse è il percorso più breve da S (in alto a sinistra) a G (in basso a destra)?
- Perché BFS e DFS devono ricordare quali vertici hanno visto?
- Altrimenti un ciclo nel grafo farebbe girare la ricerca in tondo per sempre
- Per stamparli alla fine
- Per ordinarli
- Serve solo per la DFS
- In che anno Euler risolse il problema dei ponti di Königsberg?
- 1683
- 1736
- 1843
- 1945
- Qual è il tempo di esecuzione della BFS su un grafo con V vertici ed E archi?
- O(V + E)
- O(V × E)
- O(log V)
- O(2ⱽ)
- Vuoi sapere il minor numero di legami «amico di un amico» tra due persone in un social network. Quale ricerca?
- BFS
- DFS
- Ricerca binaria
- Merge sort
- Un grafo è fatto di vertici uniti da archi; lo si memorizza con liste di adiacenza.
- La BFS usa una coda, procede anello per anello e trova i percorsi con meno archi.
- La DFS usa una pila (o la ricorsione), va prima in profondità e poi torna indietro.
- Entrambe girano in O(V + E) e devono ricordare i vertici visti.
- La soluzione di Euler del 1736 sui ponti di Königsberg diede inizio alla teoria dei grafi.
Fonti di questo corso (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.
Consultato il: 2026-10-17