Visite di grafi - BFS e DFS
In questa pagina 6
Una visita (traversal) è un'esplorazione sistematica di a partire da un vertice . Come per gli alberi (vedi Visite di alberiVisite in preorder e postorder come schemi generali (template) da adattare; complessità Theta(n + somma dei costi di visita) perché la somma dei figli è n-1; esempi (indice di un libro, spazio occupato in un file system); profondità con il preorder, altezza con il postorder; antenato comune più basso.Visite di alberi →) sono schemi generali (design pattern) in cui l'operazione di "visita" si adatta al problema. Una scansione semplice di e tocca tutto ma senza seguire la struttura del grafo, e serve a poco (vedi Rappresentazione dei grafiStrutture di base (lista dei vertici LV, lista degli archi LE), liste di adiacenza, matrice di adiacenza; operazioni incidentEdges, opposite, areAdjacent; spazio e tempi a confronto; esempio con un grafo di 5 vertici.Rappresentazione dei grafi →).
- BFS (breadth-first search, in ampiezza): visitato un vertice, si visitano tutti i suoi vicini prima dei vicini dei vicini.
- DFS (depth-first search, in profondità): visitato un vertice, si visita un vicino, poi un vicino del vicino, e così via, tornando indietro quando non ci sono nuovi vertici.
Convenzioni: ogni vertice ha un campo v.ID ( = non visitato, = visitato); ogni arco ha e.label (null = non etichettato). incidentEdges(v) restituisce un iteratore agli archi incidenti su (la lista di adiacenza) e opposite(v, e) il vertice di diverso da , entrambi in per elemento. Prima di una visita tutti i vertici hanno ID e tutti gli archi sono non etichettati.
BFS
La BFS da visita la componente connessa di , etichetta gli archi di come DISCOVERY EDGE o CROSS EDGE e divide i vertici in livelli secondo la distanza da (distanza = lunghezza minima di un cammino; se in componenti diverse).
Algoritmo BFS(G, s)
visita s; s.ID <- 1
L0 <- lista con s; i <- 0
while Li non è vuota do
Li+1 <- lista vuota
forall v in Li do
forall e in G.incidentEdges(v) do
if e.label = null then
w <- G.opposite(v, e)
if w.ID = 0 then
e.label <- DISCOVERY EDGE
visita w; w.ID <- 1; inserisci w in Li+1
else e.label <- CROSS EDGE
i <- i + 1Proprietà. Dopo BFS(G, s): tutti i vertici di sono visitati e tutti i suoi archi etichettati; i discovery edge formano uno spanning tree di radicato in (il BFS tree); per ogni il cammino nel BFS tree da a ha archi; se è un cross edge i livelli di e differiscono al più di .
Complessità. Ogni vertice di entra in una lista una sola volta (poi ID ) e ogni arco di viene etichettato una volta; dalla lista di adiacenza di ciascun vertice ogni arco è esaminato due volte (una per estremo). Con vertici e archi in il costo è (se non è isolato, ). Se è connesso, .
Per visitare tutto il grafo anche se non connesso:
forall v in V do v.ID <- 0
forall v in V do
if v.ID = 0 then BFS(G, v)DFS
Algoritmo DFS(G, v) (prima invocazione: v = s)
visita v; v.ID <- 1
forall e in G.incidentEdges(v) do
if e.label = null then
w <- G.opposite(v, e)
if w.ID = 0 then
e.label <- DISCOVERY EDGE
DFS(G, w)
else e.label <- BACK EDGEDopo DFS(G, s): tutti i vertici di sono visitati, tutti gli archi di sono etichettati DISCOVERY o BACK, e i discovery edge formano uno spanning tree di radicato in . Complessità con liste di adiacenza ( se connesso), con lo stesso argomento della BFS. Un vertice si dice discoverable da se esiste un cammino da a fatto di vertici non ancora visitati.
Esempio svolto
Grafo con vertici e liste di adiacenza in ordine crescente: ; ; ; ; ; ; .
- DFS da 1: ordine di visita . Discovery edge: (da si va in , poi si torna e si scopre ). Back edge: , , .
- BFS da 1: ordine ; livelli , , , . Discovery edge: . Cross edge: , , . In effetti (tramite ) e non come nel cammino dell'albero DFS : la BFS dà le distanze minime, la DFS no.
(Tracce verificate eseguendo le implementazioni.) Un secondo esempio d'esame: liste ; ; ; ; ; ; ; ; . La BFS da visita (livello 1: ; livello 2: ; livello 3: ).
Applicazioni (tutte in )
| Problema | Come si risolve |
|---|---|
| visitare tutto il grafo | si ripete la visita da ogni vertice non visitato |
| connettività | numero di componenti |
| componenti connesse | contatore ; per ogni con ID , e BFS(G, v, k) con w.ID <- k; restituisce e ogni vertice porta l'identificativo della sua componente |
| spanning tree (grafo connesso) | si restituiscono i discovery edge di una BFS/DFS da un vertice qualsiasi |
| cammino minimo in numero di archi tra e (BFS) | campo u.parent: quando è discovery, u.parent <- v; dopo BFS(G, s), se non è visitato non c'è cammino, altrimenti si risale da a con parent |
| raggiungibilità , un cammino qualsiasi | DFS o BFS da |
| ciclo | si trova un cross edge (BFS) o back edge (DFS) e si risale da e lungo parent fino a un antenato comune: il cammino più l'arco è un ciclo; se non ci sono cross/back edge il grafo è aciclico |
Adattare la BFS (domande d'esame ricorrenti):
- vertici a distanza da : si esegue la BFS fermandosi quando (non si costruisce ) e si contano i vertici dei livelli ; costo ;
- numero di vertici raggiungibili da e distanza massima: si conta il numero di vertici inseriti in lista e si restituisce dell'ultimo livello non vuoto;
- dimensione di ogni componente: una BFS che restituisce il contatore dei vertici scoperti (vedi l'Esercizio 23 · escursioni e componenti connesse).
BFS contro DFS
| BFS | DFS | |
|---|---|---|
| struttura di appoggio | liste di livelli (coda) | ricorsione (pila) |
| archi non-tree | cross edge, tra livelli adiacenti | back edge |
| cammini nell'albero | minimi in numero di archi | non necessariamente minimi |
| costo (liste di adiacenza) |
Per grafi pesati i cammini minimi richiedono l'algoritmo di Dijkstra, che generalizza la BFS (vedi Cammini minimi e algoritmo di DijkstraGrafi pesati, lunghezza di un cammino e distanza; sottocammini di un cammino minimo; problema SSSP; algoritmo di Dijkstra con cloud e priority queue, rilassamento degli archi, esempio svolto; correttezza (due lemmi) e complessità O(min(n^2, (n+m) log n)) con lista non ordinata o heap; pesi non negativi.Cammini minimi e algoritmo di Dijkstra →).
Errori comuni
- Controllare
IDma nonlabel: ogni arco verrebbe esaminato due volte e l'etichettatura non sarebbe consistente. - Dimenticare di rilanciare la visita dai vertici non visitati: si visita solo la componente di .
- Dire che la BFS costa o la DFS : il costo è (liste di adiacenza); con la matrice sarebbe .
- Usare la DFS per cammini minimi in numero di archi.
Versione ripasso
- Campi:
v.ID( = non visitato),e.label(null,DISCOVERY,CROSSper BFS,BACKper DFS). Visite = design pattern (vedi Visite di alberiVisite in preorder e postorder come schemi generali (template) da adattare; complessità Theta(n + somma dei costi di visita) perché la somma dei figli è n-1; esempi (indice di un libro, spazio occupato in un file system); profondità con il preorder, altezza con il postorder; antenato comune più basso.Visite di alberi →). - BFS(): livelli , = nuovi vicini dei vertici di ; arco con
label = null: se →DISCOVERYe in , altrimentiCROSS.- Proprietà: i discovery edge formano il BFS tree (spanning tree di ); ⇒ ; i cross edge collegano livelli che differiscono al più di .
- DFS(): visita , per ogni arco non etichettato: →
DISCOVERYeDFS(G,w), altrimentiBACK; discovery edge = spanning tree, cammini non minimi. - Costo: per la componente di , se connesso (liste di adiacenza, ogni arco visto due volte; vedi Rappresentazione dei grafiStrutture di base (lista dei vertici LV, lista degli archi LE), liste di adiacenza, matrice di adiacenza; operazioni incidentEdges, opposite, areAdjacent; spazio e tempi a confronto; esempio con un grafo di 5 vertici.Rappresentazione dei grafi →). Grafo non connesso: rilanciare da ogni vertice con
ID = 0. - Esempio (; ; ; ; ; ; ): DFS da 1 → ; BFS da 1 → , livelli .
- Applicazioni : connettività, componenti (contatore +
ID <- k), spanning tree, cammino minimo conparent(BFS), cammino/raggiungibilità, ciclo (cross/back edge +parent). - Adattamenti: vertici a distanza (fermarsi al livello ), contatore per la dimensione delle componenti.
- Pseudocodice BFS:
s.ID <- 1; L0 <- [s]; finché non è vuota: per ogni e ogni arco cone.label = null,opposite(v,e); sew.ID = 0:e.label <- DISCOVERY,w.ID <- 1, ; altrimentie.label <- CROSS. - Esempio d'esame: liste ; ; ; ; ; ; ; ; ⇒ BFS da : .
- Errori: non rilanciare la visita; DFS per cammini minimi; costo o invece di .