Breath-first search Visita in ampiezza di un grafo Algoritmo Esempio Complessità dell’algoritmo Proprietà Albero BFS Grafo Un grafo G = (V,E) consiste in: - un insieme V di vertici (o nodi) - un insieme E di coppie di vertici, detti archi: ogni arco connette due vertici Esempio 1: V = {persone che vivono in Italia}, E = {coppie di persone che si sono strette la mano} [NON ORIENTATO] Esempio 2: V = {persone che vivono in Italia},E = { (x,y) tale che x ha inviato una mail a y} [ORIENTATO] 1 V= {1,2,3,4,5,6,7} 2 6 3 4 5 7 Scopo e tipi di visita di un grafo Scopo: una visita (o attraversamento) di un grafo G permette di esaminare i nodi e gli archi di G in modo sistematico. Problema di base in molte applicazioni. Esistono varie tipologie di visite con diverse proprietà. In particolare: – visita in ampiezza (BFS = Breadth First Search) – visita in profondità (DFS = Depth First Search) Breath-first search Dato un grafo G=(V,E) e un specifico vertice s chiamato sorgente, la visita in ampiezza esplora gli archi di G per scoprire ogni vertice che sia raggiungibile a partire da s. Essa calcola la distanza (ossia il numero minimo di archi) da s ad ogni vertice raggiungibile. Inoltre, produce un “albero BFS” che ha come radice s e comprende tutti vertici raggiungibili. Nell’albero BFS il cammino da s a v corrisponde ad un “cammino minimo”. L’algoritmo funziona per grafi orientati e non orientati. Breath-first search La visita in ampiezza esplora i nodi del grafo a partire da quelli a distanza 1 da s. Dopo aver visitato i nodi a distanza 1, visita quelli a distanza 2. E così via. distanza=3 distanza=2 u 2 r distanza=1 s t 1 0 2 3 Cresce la distanza dalla sorgente v 2 w 1 2 3 x y Archi dell’albero BFS Via via visita nodi più distanti dalla sorgente. Struttura dati Strutture dati utilizzate: • Liste di adiacenza Adj: per conoscere i vertici adiacenti a un vertice. • color[u]: per colora il vertice u di bianco (vertice non scoperto), di grigio (vertice appena scoperto) e di nero (vertice non più sulla frontiera dei vertici appena scoperti). • d[u]: la distanza di u. All’inizio è ∞. • p[u]: il predecessore di u nell’albero BFS. • Q: coda Algoritmo BFS(G,s) 1. for ogni vertice u in V[G] – {s} 2. do color[u] ← WHITE 3. d[u] ← ∞ 4. p[u] ← NIL 5. colors[s] ← GRAY 6. d[s] ← 0 7. p[s] ← NIL 8. Q ← {s} // inizializzazione di ogni vertice // si comincia dal vertice s // Q= coda dei vertici grigi sulla frontiera All’inizio tutti i vertici sono bianchi e, successivamente, possono diventare grigi e poi neri. La visita in ampiezza costruisce un albero BFS che all’inizio contiene solo la radice: il vertice sorgente s. Algortimo 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. while Q ≠Ø do u ← head[Q] // termina quando la coda è vuota // prendi prossimo elemento dalla coda // scopri vertici bianchi adiacenti for ogni vertice v in Adj[u] do if color[v] = WHITE then color[v] ← GRAY // diventano grigi d[v] ← d[u] + 1 // la distanza è d[u] +1 p[v] ← u // il predecessore è u ENQUEUE(Q,v) // e vanno in Q DEQUEUE(Q) color[u] ← BLACK // u nero: non più sulla frontiera Un vertice viene scoperto, la prima volta che viene incontrato durante la visita: in tale istante esso cessa di essere bianco. Esempio Visita della sorgente. Stato iniziale: la sorgente è in Q. r s t u ∞ 0 ∞ ∞ (a) Q = {s} d[s] = 0 r s t 1 0 ∞ Q = {w, r} ∞ d[w] = 1 d[r] = 1 (b) u ∞ ∞ ∞ ∞ ∞ 1 ∞ ∞ v w x y v w x y r s t u r s t u 1 0 2 ∞ 1 0 2 ∞ 2 1 2 ∞ v w x y (c) ∞ 1 2 ∞ v w x y Q = {r, t, x} d[r] = 1 d[t] = 2 d[x] = 2 (d) Terminata la visita dei nodi a distanza 1. Q = {t, x, v} d[t] = 2 d[x] = 2 d[v] = 2 Terminata la visita dei nodi a distanza 1. Esempio r s t u 1 0 2 3 (d) 2 1 2 ∞ v w x y r s t u 1 0 2 3 (f) 2 1 2 3 v w x y Q = {x, v, u} d[x] = 2 d[v] = 2 d[u] = 3 s t u 1 0 2 3 2 1 2 3 v w x y r s t u 1 0 2 3 (e) Q = {u, y} d[u] = 3 d[y] = 3 r (g) Terminata la visita dei nodi a distanza 2. Terminata la visita del grafo G. d[v] = 2 d[u] = 3 d[y] = 3 Q = {y} d[y] = 3 2 1 2 3 v w x y r s t u 1 0 2 3 2 1 2 3 v w x y (h) Terminata la visita dei nodi a distanza 3. Q = {v, u, y} Q=Ø Complessità Analisi del tempo di esecuzione su un grafo G=(V,E): • Il tempo necessario per l’inizializzazione è O(|V|), tempo O(1) per ogni vertice. • Ogni nodo raggiungibile viene visitato 1 volta: Le operazioni di inserimento e rimozione dalla coda è O(1), quindi il tempo totale dedicato alle operazioni sulla coda è O(|V|). Di ogni vertice scoperto viene visitata tutta la lista di adiacenza, ossia quando il vertice è estratto dalla coda. Si sommano le lunghezze delle liste di adiacenza dei nodi visitati. Le somma delle liste di adiacenza di tutti i nodi è pari a Θ(|E|). • Sommando il tempo di inizializzazione e il tempo per visitare i vertici, si ha che l’algoritmo di BFS() viene eseguito in tempo O(|V| + |E|). Cammino minimo Si definisce la distanza minima δ(s,v) ≥ 0 da s a v come il numero minimo di archi per passare dal vertice s al vertice v. Quando δ(s,v) = ∞, v risulta irraggiungibile partendo da s, ossia non esiste alcun cammino da s a v. Un cammino di lunghezza δ(s,v) da s a v è chiamato cammino minimo da s a v. (Possono esserci più cammini minimi!) Si ha la seguente proprietà: Per ogni arco (u,v) in E, δ(s,v) ≤ δ(s,u) +1. Nota: la visita BSF calcola la distanza minima di ogni nodo raggiungibile dalla sorgente s. Nodi in coda Proposizione I nodi che entrano in Q sono tutti e soli i nodi u con δ(s,u) < ∞, ossia tutti i nodi raggiungibili da s. Dimostrazione “Se il nodo v entra in Q allora δ(s,v) < ∞.” Si procede per induzione sull’i-esima iterazione dell’operazione di ENQUEUE(). Per i = 0: nella coda si trova s δ(s,s) = 0 < ∞. Per i > 0: Supponiamo vera la proposizione per ogni iterazione k<i. All’i-esima iterazione viene visitata la lista di adiacenza di un Nodi in coda I nodi bianchi v che vengono messi in coda appartengono ad Adj[u]. Si ha che per ipotesi induttiva δ(s,u) < ∞ (u è stato inserito alla k’-esima iterazione, k’ < i ) e, inoltre, esiste l’arco (u,v). Quindi δ(s,v) ≤ δ(s,u) +1 < ∞. “Se δ(s,v) < ∞ allora il nodo v entra in Q.” Si procede per induzione su δ(s,v) = i. Per δ(s,v) = 0: è vero! v = s. Per δ(s,v) = i>0: per ipotesi induttiva si ha che per ogni u tale che δ(s,u) < i u è entrato in coda. Nodi in coda Sorgente v0 vi-1 vi Nodo v δ(s,v) < ∞ δ(s,v) < ∞, quindi esiste un cammino minimo da s a v: <v0, …, vi-1, vi>, con v0= s e vi = v. Si ha che δ(s, vi-1) = i-1<i. vi-1 entra in coda per ipotesi induttiva. Quindi, quando verrà visitata la lista di adiacenza di vi-1, nella quale ci sarà anche vi = v, potrà verificarsi: a) v è bianco e verrà messo in coda b) v è grigio o nero e quindi è stato messo in coda Corollario Se il grafo è connesso (o fortemente connesso nel caso di grafo orientato) allora tutti i nodi vengono messi in coda. Albero BFS Proposizione Il vettore p definisce un sottografo di G, Tp=(Vp,Ep), dove: Vp = {s e tutti v in V: p[v] ≠ NIL} Ep = {(p[v], v) in E: v in Vp - {s} } Tp è un albero (albero BFS). Inoltre la lunghezza del cammino tra s e u in Tp è δ(s,u). Corollario Se G è connesso (fortemente connesso nel caso di grafo orientato) allora Tp è un albero di copertura minima (minimum spanning tree), ossia vengono selezionati il numero minimo di archi per cui il sottografo risulta ancora connesso. Albero BFS Ecco l’albero BFS del grafo visto nell’esempio: s p(s)=NIL 0 Radice p(r)=s r s 1 0 p(t)=w p(u)=t t u 2 r 1 w 3 v 2 1 2 3 v w x y p(v)=r 1 p(w)=s p(x)=w p(y)=x 2 x y 2 2 3 3 t u