Salta al contenuto
Note per Studenti Visite di grafi - BFS e DFS

Visite di grafi - BFS e DFS

In questa pagina 6

Una visita (traversal) è un'esplorazione sistematica di G=(V,E)G = (V, E) a partire da un vertice ss. 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. Su un albero preorder e postorder sono DFS; la BFS corrisponde alla visita per livelli, quella della numerazione a livelli degli heap (HeapAlbero binario completo e sua altezza floor(log2 n); heap = albero completo con heap-order property; proprietà (radice minima, cammini non decrescenti); rappresentazione su array con level numbering; insert con up-heap bubbling, removeMin con down-heap bubbling, rimozione di una entry qualsiasi; invarianti e costi Theta(log n); esempio svolto.Heap →). La differenza con gli alberi è che un grafo può avere cicli, quindi bisogna ricordare i vertici già visti (campo ID) per non girare all'infinito. Una scansione semplice di LVL_V e LEL_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 →).

Convenzioni: ogni vertice vv ha un campo v.ID (00 = non visitato, ≥1\ge 1 = visitato); ogni arco ee ha e.label (null = non etichettato). incidentEdges(v) restituisce un iteratore agli archi incidenti su vv (la lista di adiacenza) e opposite(v, e) il vertice di ee diverso da vv, entrambi in Θ(1)\Theta(1) per elemento. Prima di una visita tutti i vertici hanno ID =0= 0 e tutti gli archi sono non etichettati.

BFS

Definizione (visita in ampiezza, BFS). La BFS da ss visita la componente connessa CsC_s di ss, etichetta gli archi di CsC_s come DISCOVERY EDGE o CROSS EDGE e divide i vertici in livelli LiL_i secondo la distanza ii da ss (distanza d(x,y)d(x, y) = lunghezza minima di un cammino; +∞+\infty se in componenti diverse).

Esempio. Sul grafo con liste 1:2,41:2,4; 2:1,3,42:1,3,4; 3:2,5,63:2,5,6; 4:1,2,64:1,2,6; 5:3,65:3,6; 6:3,4,5,76:3,4,5,7; 7:67:6, la BFS da 11 ha livelli L0={1}L_0=\{1\}, L1={2,4}L_1=\{2,4\}, L2={3,6}L_2=\{3,6\}, L3={5,7}L_3=\{5,7\}.

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 + 1

Proprietà (BFS e spanning tree). Dopo BFS(G, s): tutti i vertici di CsC_s sono visitati e tutti i suoi archi etichettati; i discovery edge formano uno spanning tree di CsC_s radicato in ss (il BFS tree); per ogni v∈Liv \in L_i il cammino nel BFS tree da ss a vv ha i=d(s,v)i = d(s, v) archi; se (u,v)(u, v) è un cross edge i livelli di uu e vv differiscono al più di 11.

Esempio. Nello stesso grafo d(1,6)=2d(1,6)=2 (cammino 1,4,61,4,6) e il BFS tree ha n−1=6n-1=6 discovery edge.

Perché i livelli sono le distanze (per induzione sul livello: Principio di induzionePer dimostrare una proprietà per ogni n basta il passo base e il passo induttivo da n a n+1.Principio di induzione →). L0={s}L_0=\{s\} è a distanza 00. Se i vertici di LiL_i sono esattamente quelli a distanza ii, i vertici scoperti scorrendo LiL_i sono vicini di vertici a distanza ii non ancora visti: non possono essere a distanza ≤i\le i (sarebbero già in un livello precedente), quindi sono a distanza i+1i+1. E ogni vertice a distanza i+1i+1 ha un vicino a distanza ii, perciò viene scoperto. Per un cross edge (u,v)(u,v) con u∈Liu\in L_i: alla fine dell'esame di LiL_i il vertice vv è già visitato, quindi sta in un livello ≤i+1\le i+1; per simmetria uu sta in un livello ≤liv(v)+1\le \text{liv}(v)+1, e i livelli differiscono al più di 11.

Traccia sul grafo dell'esempio (liste 1:2,41:2,4; 2:1,3,42:1,3,4; 3:2,5,63:2,5,6; 4:1,2,64:1,2,6; 5:3,65:3,6; 6:3,4,5,76:3,4,5,7; 7:67:6), con ii il livello in esame:

esame di archi non etichettati incontrati azione lista del livello successivo
L0={1}L_0=\{1\}, vertice 11 (1,2)(1,2), (1,4)(1,4) scoperti 22 e 44 (discovery) L1={2,4}L_1=\{2,4\}
L1L_1, vertice 22 (2,3)(2,3), (2,4)(2,4) 33 scoperto; 44 già visto: cross L2={3}L_2=\{3\}
L1L_1, vertice 44 (4,6)(4,6) 66 scoperto L2={3,6}L_2=\{3,6\}
L2L_2, vertice 33 (3,5)(3,5), (3,6)(3,6) 55 scoperto; 66 già visto: cross L3={5}L_3=\{5\}
L2L_2, vertice 66 (6,5)(6,5), (6,7)(6,7) 55 già visto: cross; 77 scoperto L3={5,7}L_3=\{5,7\}
L3L_3 nessuno nuovo tutti gli archi sono già etichettati L4=∅L_4=\emptyset, fine

Formula (complessità della BFS). Ogni vertice di CsC_s entra in una lista una sola volta (poi ID ≠0\ne 0) e ogni arco di CsC_s viene etichettato una volta; dalla lista di adiacenza di ciascun vertice ogni arco è esaminato due volte (una per estremo). Con nsn_s vertici e msm_s archi in CsC_s il costo è Θ(ns+ms)=Θ(ms)\Theta(n_s + m_s) = \Theta(m_s) (se ss non è isolato, ms≥ns−1m_s \ge n_s - 1). Se GG è connesso, Θ(n+m)\Theta(n + m). Il conto: ogni vertice di CsC_s è estratto da una lista una volta e per ciascuno si percorre la sua lista di adiacenza, di lunghezza degree(v)\text{degree}(v) (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 →); il costo totale è ∑v(1+degree(v))=ns+2ms\sum_{v}\big(1+\text{degree}(v)\big)=n_s+2m_s perché la somma dei gradi è 2ms2m_s (Grafi - definizioni e proprietàGrafo G=(V,E) diretto e non diretto, grafo semplice e pesato; incidenza, adiacenza, grado; cammini, cicli, sottografi, grafi connessi e componenti connesse; alberi liberi, foreste, spanning tree e spanning forest; proprietà con dimostrazioni (somma dei gradi = 2m, m <= n(n-1)/2, alberi m = n-1, connessi m >= n-1, foreste m <= n-1).Grafi - definizioni e proprietà →), e ns+2ms∈Θ(ns+ms)n_s+2m_s\in\Theta(n_s+m_s). Con la matrice di adiacenza lo scorrimento dei vicini di ogni vertice costerebbe Θ(n)\Theta(n) e il totale Θ(n2)\Theta(n^2).

Esempio. Con ns=7n_s=7 vertici e ms=9m_s=9 archi il costo è Θ(ns+ms)=Θ(16)\Theta(n_s+m_s)=\Theta(16).

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 EDGE

Proprietà (DFS). Dopo DFS(G, s): tutti i vertici di CsC_s sono visitati, tutti gli archi di CsC_s sono etichettati DISCOVERY o BACK, e i discovery edge formano uno spanning tree di CsC_s radicato in ss. Complessità Θ(ms)\Theta(m_s) con liste di adiacenza (Θ(n+m)\Theta(n + m) se connesso), con lo stesso argomento della BFS. Un vertice uu si dice discoverable da vv se esiste un cammino da vv a uu fatto di vertici non ancora visitati.

Esempio. La DFS da 11 nello stesso grafo visita 1,2,3,5,6,4,71,2,3,5,6,4,7 con 66 discovery edge e 33 back edge, (3,6)(3,6), (1,4)(1,4) e (2,4)(2,4).

Traccia (la pila delle chiamate cresce a ogni discovery edge e si accorcia ai ritorni): da 11 si scende a 22 (primo vicino non visitato), da 22 a 33, da 33 a 55, da 55 a 66. In 66, con lista 3,4,5,73,4,5,7: l'arco (6,3)(6,3) non è etichettato ma 33 è già visitato, quindi è un back edge; 44 non è visitato, discovery edge (6,4)(6,4) e si scende in 44, dove (4,1)(4,1) e (4,2)(4,2) portano a vertici già visitati: back edge (1,4)(1,4) e (2,4)(2,4). Si torna in 66: l'arco (6,5)(6,5) è già etichettato (è il discovery edge (5,6)(5,6) visto da 55); 77 non è visitato, discovery edge (6,7)(6,7). Nessun vertice è visitato due volte, perché l'ID lo impedisce.

Esempio svolto

Grafo con vertici 1..71..7 e liste di adiacenza in ordine crescente: 1:2,41: 2, 4; 2:1,3,42: 1, 3, 4; 3:2,5,63: 2, 5, 6; 4:1,2,64: 1, 2, 6; 5:3,65: 3, 6; 6:3,4,5,76: 3, 4, 5, 7; 7:67: 6.

  • DFS da 1: ordine di visita 1,2,3,5,6,4,71, 2, 3, 5, 6, 4, 7. Discovery edge: (1,2),(2,3),(3,5),(5,6),(6,4),(6,7)(1,2), (2,3), (3,5), (5,6), (6,4), (6,7) (da 66 si va in 44, poi si torna e si scopre 77). Back edge: (3,6)(3,6), (1,4)(1,4), (2,4)(2,4).
  • BFS da 1: ordine 1,2,4,3,6,5,71, 2, 4, 3, 6, 5, 7; livelli L0={1}L_0 = \{1\}, L1={2,4}L_1 = \{2, 4\}, L2={3,6}L_2 = \{3, 6\}, L3={5,7}L_3 = \{5, 7\}. Discovery edge: (1,2),(1,4),(2,3),(4,6),(3,5),(6,7)(1,2), (1,4), (2,3), (4,6), (3,5), (6,7). Cross edge: (2,4)(2,4), (3,6)(3,6), (5,6)(5,6). In effetti d(1,6)=2d(1, 6) = 2 (tramite 44) e non 44 come nel cammino dell'albero DFS 1,2,3,5,61, 2, 3, 5, 6 (quattro archi): la BFS dà le distanze minime, la DFS no.

(Tracce verificate eseguendo le implementazioni.) Un secondo esempio d'esame: liste A:B,C,DA: B,C,D; B:A,E,FB: A,E,F; C:A,GC: A,G; D:A,HD: A,H; E:BE: B; F:B,IF: B,I; G:CG: C; H:D,IH: D,I; I:F,HI: F,H. La BFS da AA visita A,B,C,D,E,F,G,H,IA, B, C, D, E, F, G, H, I (livello 1: B,C,DB, C, D; livello 2: E,F,G,HE, F, G, H; livello 3: II).

Applicazioni (tutte in O(n+m)O(n + m))

Problema Come si risolve
visitare tutto il grafo si ripete la visita da ogni vertice non visitato
connettività numero di componenti =1= 1
componenti connesse contatore kk; per ogni vv con ID =0= 0, k←k+1k \leftarrow k + 1 e BFS(G, v, k) con w.ID <- k; restituisce kk 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 ss e tt (BFS) campo u.parent: quando (v,u)(v, u) è discovery, u.parent <- v; dopo BFS(G, s), se tt non è visitato non c'è cammino, altrimenti si risale da tt a ss con parent
raggiungibilità s→ts \to t, un cammino qualsiasi DFS o BFS da ss
ciclo si trova un cross edge (u,v)(u, v) (BFS) o back edge (DFS) e si risale da uu e vv lungo parent fino a un antenato comune: il cammino più l'arco è un ciclo; se non ci sono cross/back edge il grafo è aciclico (è una foresta, per il confronto con m≤n−1m\le n-1 si veda Grafi - definizioni e proprietàGrafo G=(V,E) diretto e non diretto, grafo semplice e pesato; incidenza, adiacenza, grado; cammini, cicli, sottografi, grafi connessi e componenti connesse; alberi liberi, foreste, spanning tree e spanning forest; proprietà con dimostrazioni (somma dei gradi = 2m, m <= n(n-1)/2, alberi m = n-1, connessi m >= n-1, foreste m <= n-1).Grafi - definizioni e proprietà →); esempio: cross edge (2,4)(2,4) con parent di 22 e 44 uguale a 11 dà il ciclo 1,2,4,11,2,4,1

Adattare la BFS (domande d'esame ricorrenti):

  • vertici a distanza ≤d\le d da ss: si esegue la BFS fermandosi quando i=di = d (non si costruisce Ld+1L_{d+1}) e si contano i vertici dei livelli L0,…,LdL_0, \dots, L_d; costo O(n+m)O(n + m);
  • numero di vertici raggiungibili da ss e distanza massima: si conta il numero di vertici inseriti in lista e si restituisce ii 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

Proprietà (confronto tra BFS e 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) Θ(n+m)\Theta(n + m) Θ(n+m)\Theta(n + m)

Esempio. La BFS trova d(1,6)=2d(1,6)=2, mentre il cammino da 11 a 66 nell'albero DFS (1,2,3,5,61,2,3,5,6) ha lunghezza 44.

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 →). Nelle reti di calcolatori la BFS è l'idea dell'instradamento con il minimo numero di salti (Algoritmi di instradamento - link state e distance vectorL'instradamento (routing) trova il percorso di costo minimo in un grafo pesato in cui i router sono nodi e le reti tra due router sono archi. Link state: ogni router diffonde con un flooding i pacchetti LSP sui propri collegamenti, ricostruisce tutto il grafo e applica Dijkstra (nodi con stato (distanza, permanente o temporaneo)). Distance vector: ogni router conosce solo i vicini e scambia con loro il proprio vettore delle distanze, aggiornato con Bellman-Ford $D_{A,w}=\min{D_{A,w},,D_{A,Y}+d_{Y,w}}$; converge in al più $n-1$ giri ma può soffrire del conteggio all'infinito (limite a 16, hold down, aggiornamenti immediati, split horizon con poison reverse). Path vector: ogni annuncio porta l'intero cammino, si sceglie per politica e non per costo, e i cicli si scoprono trovando sé stessi nel cammino. Dijkstra è corretto se i costi sono non negativi (invariante: un nodo permanente ha già la distanza vera); Bellman-Ford è corretto perché dopo $k$ giri conosce i cammini minimi con al più $k+1$ archi.Algoritmi di instradamento - link state e distance vector →); nella DFS la stessa struttura "vai avanti finché puoi, poi torna indietro" è il backtracking della ricorsione (RicorsioneFunzioni che chiamano se stesse: caso base e passo ricorsivo, stack delle chiamate, esempi su numeri, stringhe e liste, ricorsione multipla e suo costo, divide et impera, confronto con l'iterazione.Ricorsione →).

Errori comuni

  • Controllare ID ma non label: 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 ss.
  • Dire che la BFS costa Θ(n)\Theta(n) o la DFS Θ(m)\Theta(m): il costo è Θ(n+m)\Theta(n + m) (liste di adiacenza); con la matrice sarebbe Θ(n2)\Theta(n^2).
  • Usare la DFS per cammini minimi in numero di archi.

Versione ripasso

Esercizi su questo argomento

Teoria collegata