.. Grafi: visita generica Una presentazione alternativa (con ulteriori dettagli) Algoritmi di visita Scopo: visitare tutti i vertici di un grafo (si osservi che per poter visitare un vertice occorre prima scoprirlo ) Realizzazione: Dividiamo l insieme dei vertici in tre insiemi colorati in modo diverso Bianco: Vertici non ancora scoperti (e quindi non visitati) Grigio: Vertici scoperti i cui adiacenti non sono ancora stati tutti scoperti, quindi vertici utili per continuare la visita Nero: Vertici visitati (e quindi scoperti) e non più utili per continuare a scoprire altri vertici (i loro adiacenti sono stati tutti già scoperti) Una versione astratta dell algoritmo di visita generica INIZIALIZZA (G) for ogni u V do color [u] white Proprietà 1: Il colore di un vertice può solo passare da white a gray a black.
Invarianti del while: INV1: Se (u,v) E[G] e u è nero, allora v è grigio o nero. INV2: Tutti i vertici grigi o neri sono raggiungibili da s. INV3: Qualunque cammino da s a un vertice bianco deve contenere almeno un vertice grigio. Dimostrazione. Se s è grigio: vero. Se s è nero: se non ci fosse nessun vertice grigio ci sarebbe un vertice bianco adiacente ad uno nero, che è impossibile per l'invariante INV1. Teorema. Al termine dell'algoritmo di visita, un vertice è nero se e solo se è raggiungibile da s. Dimostrazione. ( ) Per INV2 all'uscita del while tutti i vertici neri sono raggiungibili da s. ( ) Da INV3 e dalla condizione di uscita del ciclo (non ci sono vertici grigi) si ricava che non ci può essere nessun vertice bianco raggiungibile da s.
{G = <V, E> & v V color[v] = white} {Se (u,v) E[G] e u è nero, allora v è grigio o nero & & Tutti i vertici grigi o neri sono raggiungibili da s & & Qualunque cammino da s a un vertice bianco deve contenere almeno un vertice grigio} {v V è nero se e solo se v è raggiungibile da s} Versione astratta dell algoritmo di visita generica con costuzione del sottografo dei predecessori L algoritmo può essere modificato in modo da ricordare, per ogni vertice che viene scoperto, quale vertice grigio ha permesso di scoprirlo, ossia ricordare l arco percorso. Ad ogni vertice u si associa un attributo π[u] che rappresenta il vertice che ha permesso di scoprirlo. L algoritmo di inizializzazione deve essere esteso per inizializzare π: INIZIALIZZA (G) for ogni vertice u V[G] do color [u] white π[u] nil color [s] gray scegli un vertice grigio u if esiste un vertice bianco v Adj[u] then color [v] gray π[v] u else color [u] black Proprietà 2: Al termine dell'esecuzione di VISITA (G,s) tutti e soli i vertici neri diversi da s hanno un predecessore diverso da nil.
Sottografo dei predecessori G π =(V π, E π ): V π = {v V: π [v] nil} {s} E π = {(π[v], v) E: v V π -{s}} Come conseguenza immediata della Proprietà 2, al termine dell'esecuzione di VISITA (G,s) V π è l'insieme di tutti i vertici neri, cioè di tutti i vertici raggiungibili da s. Teorema. Il sottografo dei predecessori costruito dall'algoritmo di visita è un albero. Dimostrazione. Basta dimostrare che è invariante del while la seguente proprietà: <V π, E π > è connesso e E π = V π - 1. Osservazione: il sottografo dei predecessori costruito dalla versione dell algoritmo di visita appena descritto corrisponde all albero di scoperta generato dalla versione dell algoritmo di visita generica descritto nei lucidi illustati due lezioni prima (e in [Dem]). In certe applicazioni può essere necessario visitare tutti i vertici di un grafo. Ciò può essere fatto nel modo seguente: VISITA_TUTTI_I_VERTICI (G) INIZIALIZZA (G) for ogni u V do if color [u] =white then VISITA (G, u) Un algoritmo per stampare il cammino dal sorgente a un vertice u: Print-Path (G, s, u) if u = s then stampa u else if π[u] = nil then stampa non esiste cammino else Print-Path (G, s, π[u]) stampa u
Versione concreta dell algoritmo di visita generica con costruzione del sottografo dei predecessori Per gestire l'insieme dei vertici grigi in modo efficiente, si può utilizzare una struttura dati D i cui elementi siano ordinati, e su cui siano definite le operazioni: make_empty first (D) add (D, x) remove-first (D) not_empty (D) crea una struttura vuota restituisce il primo elemento di D (senza modificare D) aggiunge l'elemento x a D toglie da D il primo elemento restituisce true se D non è vuota e false altrimenti Se add (D,x) aggiunge x come primo elemento di D, D è una PILA (STACK); altrimenti, se add (D,x) aggiunge x come ultimo elemento di D, D è una CODA (QUEUE). L'algoritmo diventa: D make_empty color [s] gray add (D, s) while ci not sono empty(d) vertici grigi do u un first vertice (D) grigio if c è v bianco ADJ [u] then color [v] gray π[v] u add (D, v) else color [u] black remove_first (D) Analisi della complessità (nel caso di liste di adiacenza) Per analizzare la complessità occorre specificare come viene implementato nell'algoritmo di visita il test: if c è v bianco ADJ [u] Si può realizzare il test con un ciclo che percorre dall inizio la lista di adiacenza di u fino a trovare il primo vertice bianco, se c'è. le liste di adiacenza possono essere percorse più volte Sappiamo però dalla proprietà 1 che un vertice grigio o nero non può ridiventare bianco.
non è necessario scandire ogni volta la lista di adiacenza di u dall'inizio, ma è sufficiente ricordarsi ogni volta dove si è arrivati nella scansione e ripartire da quel punto la volta successiva. Questo può essere realizzato facilmente associando ad ogni vertice il valore corrente del puntatore alla lista degli adiacenti. Si ottiene una implementazione più efficiente perché le liste di adiacenza vengono percorse al massimo una volta. Ogni vertice viene inserito in D al massimo una volta, e quindi eliminato da D al massimo una volta. Se si assume che le operazioni di inserimento ed eliminazione richiedano tempo costante O(1), il tempo totale dedicato alle operazioni su D è O(V). Con la implementazione descritta sopra, le liste di adiacenza vengono scandite al massimo una volta. Quindi il tempo speso per la scansione delle liste di adiacenza è O(E). Poichè il tempo necessario per l'inizializzazione è O(V), il tempo totale di esecuzione è O(V+E). Esercizio Dimostrate che l algoritmo ottenuto dall algoritmo di visita generica (sia per la versione astratta che per quella concreta ) con costruzione del sottografo dei predecessori (ovvero dell albero di scoperta) rendendo D una coda, visita i nodi non appena sono stati scoperti, rendendo D una pila, visita i nodi non appena sono stati scoperti.