Appunti di Algoritmi e Strutture Dati Grafi Gianfranco Gallizia 12 Dicembre 2004
2
Indice 1 Grafi 5 1.1 Definizione.............................. 5 1.2 Implementazione........................... 5 1.2.1 Liste di adiacenza...................... 5 1.2.2 Matrici di adiacenza..................... 6 1.2.3 Matrici di incidenza..................... 6 1.3 Visita dei grafi............................ 7 1.3.1 Breadth-First Search (BFS)................. 7 1.3.2 Depth-First Search (DFS).................. 9 1.3.3 Applicazioni della DFS................... 11 1.3.4 Algoritmo di Dijkstra.................... 12 1.4 Cicli Euleriani e Hamiltoniani.................... 13 1.4.1 Cicli Euleriani........................ 13 1.4.2 Cicli Hamiltoniani...................... 13 2 Alberi 15 2.1 Definizione.............................. 15 2.2 Implementazione........................... 16 2.3 Visita degli alberi........................... 17 2.4 Heaps (cataste)............................ 18 3
4 INDICE
Capitolo 1 Grafi 1.1 Definizione Nella matematica moderna, si dice grafo (da non confondere con grafico) una figura costituita da punti, detti vertici o nodi, e da linee che li uniscono, dette lati o spigoli o archi. Più formalmente, dato un insieme A di nodi, un grafo è un sottoinsieme del prodotto cartesiano G A A. 1.2 Implementazione 1.2.1 Liste di adiacenza Tratto da http://it.wikipedia.org/wiki/grafo. Figura 1.1: Grafo e lista di adiacenza. La lista di adiacenza è un array di puntatori con tanti elementi quanti sono i nodi del grafo. La lista di adiacenza viene costruita concatenando in una lista i vicini del nodo che si sta considerando. 5
6 CAPITOLO 1. GRAFI 1.2.2 Matrici di adiacenza 1 2 3 4 1 0 1 1 0 2 1 0 1 1 3 1 1 0 1 4 0 1 1 0 Come si legge: Si guarda la prima colonna e confronta riga per riga. Uno 0 significa che tra il nodo che identifica la colonna e il nodo che identifica la riga non c è nessun arco. Un 1 significa che tra il nodo che identifica la colonna e il nodo che identifica la riga c è un arco. Nel caso in cui il grafo sia orientato si controlla che vi sia un arco entrante nel nodo che identifica la riga che parte dal nodo che identifica la colonna. 1.2.3 Matrici di incidenza Lega: M ij (1,2) (1,3) 1 1 1 2 1 0 3 0 1 0: se dal nodo i non parte o non arriva l arco j. 1: se dal nodo i parte o arriva l arco j.
1.3. VISITA DEI GRAFI 7 1.3 Visita dei grafi 1.3.1 Breadth-First Search (BFS) La BFS (visita in ampiezza) oltre a visitare il grafo a partire dal nodo S compila anche l albero 1 dei cammini minimi 2 che ha per radice il nodo S. Il principio su cui si basa la BFS è: visito prima tutti i nodi a distanza 1 da S, poi tutti tutti i nodi a distanza 2 ecc... Figura 1.2: Princio della BFS. 1 Vedi definizione di albero più avanti. 2 Albero dei cammini minimi: albero che illustra i percorsi più brevi a partire da un nodo.
8 CAPITOLO 1. GRAFI Lega: S: nodo di partenza. d(x): distanza di x da S. P(x): padre di x. color(x): colore 3 di x. Q: coda dei nodi grigi. Adj: lista di adiacenza del grafo. Pseudocodice. BFS(G[ ], s ) for each vertex in G[ ] except s do color (u):= white ; P(u):=NIL ; d(u):= i n f i n i t y ; color ( s ):= gray ; d( s ):=0; P( s ):=NIL ; Enqueue( s ) ; while Q<>EMPTY do u:=dequeue(q) ; for each v in Adj(u) do if color (v)=white then color (v):= gray ; d(v):=d(u)+1; P(v):=u ; Enqueue(v ) ; Dequeue(Q) ; color (u):= black ;. 3 Nella BFS ogni nodo viene colorato con il seguente criterio: W(bianco): non visitato. G(grigio): visita iniziata. B(nero): visita terminata.
1.3. VISITA DEI GRAFI 9 1.3.2 Depth-First Search (DFS) La DFS (visita in profondità) percorre il cammino più lungo possibile da un nodo e poi torna indietro e controlla i nodi che non sono stati visitati. Nella DFS si visitano tutti i nodi del grafo (anche quelli che non sono visitati con la BFS). Lega: S: nodo di partenza. P(x): padre di x. color(x): colore 4 di x. time:contatore temporale. i(x): istante in cui si scopre il nodo x. f(x): istante in cui si completa la visita del nodo x. Adj: lista di adiacenza del grafo. 4 Nella DFS ogni nodo viene colorato con il seguente criterio: W(bianco): non visitato. G(grigio): scoperto. B(nero): visitato.
10 CAPITOLO 1. GRAFI Pseudocodice. DFS(G[ ] ) for each u in G[ ] do color (u):= white ; P(u):=NIL ; time :=0; for each v in G[ ] do if color (u)=white then DFS v i s i t (u ) ;. DFS v i s i t (u) color (u):= gray ; time:=time+1; i (u):= time ; for each v in Adj(u) do if color (v)=white then P(v):=u ; DFS v i s i t (v ) ; color (u):= black ; time:=time+1; f (u):= time ;.
1.3. VISITA DEI GRAFI 11 1.3.3 Applicazioni della DFS Topological sort Applicato ad un grafo diretto e aciclico il Topological Sort ci consente di ordinare i nodi di un grafo G in modo che se G contiene un arco w v allora w appare prima di v. Il Topological Sort consiste nell applicare al grafo la DFS e poi nell ordinare in modo decrescente i nodi in base ai tempi finali. Strongly Connected Components (SCC) SCC 5 è il massimo insieme di nodi tali che coppia di nodi (u, v) un cammino da u a v e uno da v a u. La ricerca delle SCC si basa sul grafo trasposto 6. Si calcola la DFS del grafo e si tiene traccia dei tempi finali, quindi si calcola il grafo trasposto e si applica la DFS al grafo trasposto parto dai nodi che hanno avuto f(x) maggiore nella DFS del grafo diretto. L insieme degli alberi della DFS appena calcolata ci ritorna le componenti fortemente connesse (SCC). 5 Scc: in italiano componenti fortemente connesse. 6 Grafo trasposto di un grafo diretto: grafo diretto in cui gli archi sono invertiti rispetto al grafo originale.
12 CAPITOLO 1. GRAFI 1.3.4 Algoritmo di Dijkstra Formulato da Edsger W. Dijkstra nel 1959 questo algoritmo consente di trovare l albero dei cammini minimi in un grafo diretto pesato. Un grafo diretto pesato è un grafo orientato 7 in cui gli archi hanno un valore (chiamato peso) tale per cui w 5 v significa che per andare da w a v spo 5. Lega: s: nodo di partenza. d(x): distanza di x da S. P(x): padre di x. Q: coda con priorità basata sulle distanze. S: insieme dei nodi analizzati. Adj: lista di adiacenza del grafo. Dijkstra (G[ ], s ) for each vertex in G[ ] do d( vertex ):= i n f i n i t y ; P( vertex ):=NIL ; d( s ):=0; S:=VOID; Q:= Vertex of (G[ ] ) ; while Q<>EMPTY do u:=extractmin (Q) ; i n s e r t i n S (u ) ; for each v in Adj(u) do if d(v)>d(u)+weight (u, v) then d(v):=d(u)+weight (u, v ) ; P(v):=u ;. 7 Grafo orientato: grafo i cui archi hanno un verso.
1.4. CICLI EULERIANI E HAMILTONIANI 13 1.4 Cicli Euleriani e Hamiltoniani 1.4.1 Cicli Euleriani Ciclo Euleriano: percorso chiuso 8 in cui si passa una sola volta per ogni arco del grafo. Formulato per la prima volta da Eulero (Leonhard Euler 1707-1783) in relazione al problema dei ponti di Königsberg. Affinché ci sia un ciclo euleriano all interno di un grafo occorre che tutti i vertici abbiano un numero pari di archi entranti. Per determinare un ciclo euleriano in un grafo si procede così: 1. Si calcolano le valenze. Se le valenze 9 sono dispari non ci possono essere cicli Euleriani. 2. Parto da un nodo a caso si costruisce un ciclo segnando di volta in volta in maniera incrementale gli archi su cui si passa. 3. Se il ciclo ha toccato tutti gli archi allora si ha un ciclo euleriano altrimenti si cerca un altro ciclo parto da un altro nodo (ricordandosi su quali archi si è già stati). 4. Si assemblano i cicli parziali otteno così un ciclo euleriano. 1.4.2 Cicli Hamiltoniani Ciclo Hamiltoniano: percorso chiuso in cui si passa una sola volta in ogni vertice del grafo. Formulato per la prima volta da Sir William R. Hamilton (1805-1865) nel 1856. Sir Hamilton proponeva di trovare un cammino che toccasse tutti i vertici di un dodecaedro una volta sola. A differenza del problema dei cicli euleriani, il problema dei cicli hamiltoniani non ha una soluzione trattabile. Figura 1.3: Ciclo Hamiltoniano nel Dodecaedro. 8 Percorso chiuso: percorso in cui si parte da un nodo e si ritorna a quel nodo. 9 Valenza: numero di archi entranti in un nodo.
14 CAPITOLO 1. GRAFI
Capitolo 2 Alberi 2.1 Definizione Un albero è un grafo aciclico in cui ogni nodo ha un arco entrante ad eccezione della radice. Un albero binario è un albero in cui ogni genitore ha al massimo due figli. Corollario: un albero k-ario è un albero in cui ogni genitore ha al massimo k figli. Un albero si dice completo quando sono presenti il massimo numero di nodi possibili. Figura 2.1: Albero binario di livello 1 completo. 15
16 CAPITOLO 2. ALBERI 2.2 Implementazione P: puntatore al padre. k: chiave. l: puntatore al figlio sinistro. r: puntatore al figlio destro. P k l r Nota: negli alberi k-ari il puntatore r diventa il puntatore al fratello. Figura 2.2: Rappresentazione di un albero binario tramite strutture.
2.3. VISITA DEGLI ALBERI 17 2.3 Visita degli alberi Visita simmetrica simvis (ˆx) i f x<>nil then simvis ( l e f t (x ) ) ; write( key (x ) ) ; simvis ( right (x ) ) ;. Visita anticipata previs (ˆx) i f x<>nil then write( key (x ) ) ; previs ( l e f t (x ) ) ; previs ( right (x ) ) ;. Visita posticipata postvis (ˆx) i f x<>nil then postvis ( right (x ) ) ; write( key (x ) ) ; postvis ( l e f t (x ) ) ;.
18 CAPITOLO 2. ALBERI 2.4 Heaps (cataste). Una catasta è un albero binario in cui vige la regola dove: i è un nodo dell albero. l è il figlio sinistro del nodo. r è il figlio destro del nodo. i max(l, r) In una catasta il nodo radice è il nodo che ha il valore più alto (tale proprietà delle cataste è sfruttata nell heapsort). Figura 2.3: Heap (catasta).