Algoritmi Avanzati a.a.2013/2014 Prof.ssa Rossella Petreschi TECNICA DEL TOUR DI EULERO Lezione n°7 Algoritmi Avanzati a.a.2013/2014 Prof.ssa Rossella Petreschi 1
Tecniche algoritmiche parallele di base Tecnica della prima metà: ad ogni passo si dimezza il numero di elementi su cui si esegue la computazione. Si lavora nella prima metà del vettore che da dimensione n si riduce progressivamente a dimensione n/2, n/4, … fino a raggiungere la soluzione in O(log2n) passi (es. somma di n elementi, ricerca del massimo). Tecnica del salto del puntatore: in O(log2n) passi si ottiene la soluzione raddoppiando ad ogni passo la distanza fra le coppie di elementi su cui si opera (es. somme prefisse). Tecnica del tour di eulero Tecnica dell’accelerated cascading 2
Tour di Eulero Dato un grafo G, un Tour di Eulero (TDE) su G è un ciclo (cammino chiuso) che passa su ogni arco una e una sola volta. Non tutti i grafi ammettono un tour di Eulero, quelli in cui ogni nodo ha grado pari si. Dato un albero T = (V, E) è possibile costruire un grafo G = (V, E') con E' = (v,u), (u,v) : (u,v) E }. Per ogni nodo di G, il numero di archi entranti è uguale al numero di archi uscenti, quindi G contiene un circuito euleriano. c h g e f b i d a c h g e f b i d a 3
La funzione TDE Il TDE è una sequenza ciclica di tutti gli archi del grafo costruita a partire da un qualunque nodo. Ad esempio: (c,i)(i,a)(a,d)(d,a)(a,i)(i,g)(g,i)(i,c)(c,f)(f,c)(c,h)(h,e)(e,h)(h,c)(c,b)(b,c) Per semplicità si può scrivere il TDE come: c i a d a i g i c f c h e h c bc Si può vedere il TDE come una funzione che, per ogni arco, identifica il successore nel tour: TDE(c,i) = (i,a) TDE(i,a) = (a,d) TDE(a,d) = (d,a) TDE(d,a) = (a,i) TDE(a,i) = (i,g) ecc… c h g e f b i d a 4
Costruzione della funzione TDE Affinché tutti gli archi siano visitati, è fondamentale che, per ogni nodo v, fra l’apparizione nel tour dell’arco entrante (u,v) e quella dell’arco uscente (v,u) siano presenti tutti gli archi relativi alla visita di tutti gli altri nodi adiacenti a v. Per garantire questa condizione è sufficiente considerare un ordinamento ciclico degli adiacenti di ogni nodo. Se si entra in v con l’arco (u,v) se ne esce seguendo il successore di u in tale ordinamento: TDE(u,v) = (v, nextv(u)) dove nextv(u) identifica il nodo che segue u tra gli adiacenti di v. 5
Come costruire il TDE Vogliamo ora calcolare su una PRAM-EREW il TDE: a partire da un albero (rappresentato come elenco di archi), si vuole una struttura che permetta di identificare efficientemente il successore di ogni arco nel tour. Utilizziamo delle liste di adiacenza cicliche con un informazione addizionale: per ogni arco (u,v) manteniamo un puntatore all’arco (v,u). In questo modo, dato l’arco (u,v) (nella lista di adiacenza di u), si potrà facilmente accedere all’arco (v,u) e quindi al suo successore nella lista di adiacenza di v. 6
Struttura dati per il TDE c b a h i e f g (e,h) (h,c) (c,f) (c,b) (c,i) (i,g) (a,i) (a,d) 7
Passo 1 Il processore i-esimo costruisce il reciproco dell’arco i-esimo e imposta opportunamente i puntatori. (e,h) (h,e) (h,c) (c,h) (c,f) (f,c) (c,b) (b,c) (c,i) (i,c) (i,g) (g,i) (a,i) (i,a) (a,d) (d,a) (e,h) (h,c) (c,f) (c,b) (c,i) (i,g) (a,i) (a,d) 8
Passo 2 Si ordina lessicograficamente il vettore degli archi. Nota: se nell’eseguire l’ordinamento si spostassero realmente i dati in memoria tutti i puntatori agli archi reciproci verrebbero perduti. Per ovviare a questo problema la soluzione più semplice è quella di calcolare la sequenza ordinata degli indici che si userà poi per accedere al vettore come se fosse ordinato. (a,d) (a,i) (b,c) (c,b) (c,f) (c,h) (c,i) (d,a) (e,h) (f,c) (g,i) (h,c) (h,e) (i,a) (i,c) (i,g) 9
Passo 3 Si creano le liste di adiacenza circolari per ogni nodo nella seguente maniera: si divida la lista in blocchi dallo stesso primo nodo dell’arco; per ogni arco si imposti il puntatore al successivo nel blocco; l’ultimo arco di ogni blocco punti al primo. Tempo parallelo per la costruzione della struttura dati: Passo 1: costante Passo 2: tempo per l’ordinamento Passo 3: costante (a,d) (a,i) (b,c) (c,b) (c,f) (c,h) (c,i) (d,a) (e,h) (f,c) (g,i) (h,c) (h,e) (i,a) (i,c) (i,g) 10
Radicare l’albero Input: Tour di Eulero (TDE) di un albero non radicato T, dato per liste di adiacenza. Output: Cammino di Eulero (CDE) di T radicato in r e T rappresentato tramite vettore di padri. Algoritmo: Sia v un qualunque adiacente di r, si spezza il TDE ponendo TDE (v,r) = 0. Ora, per distinguere, in ogni arco, un nodo padre e un nodo figlio, si assegna valore 1 ad ogni arco del cammino di Eulero ottenuto e si calcolano le somme prefisse S su tali valori. Si avrà p(w) = v sse S(v,w) < S(w,v) Nota: se analizziamo l'orientamento dato, vediamo che esso segue una visita di tipo DFS, ma ciò non vuol dire che abbiamo realizzato una DFS in parallelo che è anzi uno di quei problemi che restano inerentemente sequenziali. 11
Calcolo di funzioni elementari Per calcolare le funzioni elementari su alberi radicati (dati in input con il loro CDE) adoperiamo il seguente schema. Considerando, per ogni vertice v, l’arco discendente (p(v),v) e l’arco ascendente (v,p(v)): si assegni, a seconda del problema in considerazione, un valore agli archi ascendenti e un valore agli archi discendenti; si eseguano le somme prefisse sulla sequenza di valori che si ottiene seguendo il Cammino di Eulero; a seconda del problema, si dia una funzione di lettura della soluzione. 12
Visita in postorder CDE h e c b i a d g f 1 S 2 3 4 5 6 7 8 v a b c d Fatto: nella numerazione in postorder (FS,…,FD,R), ogni nodo v viene numerato quando la sua visita è completata, ovvero quando, con la tecnica del backtrack, si torna al padre p(v). Dato il CDE di un albero T radicato in r, per ottenere la numerazione dei nodi in postorder sfruttando il Fatto, assegniamo valore +1 ad ogni arco (v,p(v)) che risale dal figlio al padre e valore 0 ad ogni arco (p(v),v) che scende dal padre al figlio. Sulla sequenza così ottenuta eseguiamo poi le somme prefisse ottenendo S. La numerazione in postorder è data da: v r Post(v) = S(v,p(v)) v = r Post(v) = n Riprendendo l’albero precedentemente visto, consideriamolo radicato in h (per chiarezza riportiamo solo il primo nodo di ogni arco nel CDE). CDE h e c b i a d g f 1 S 2 3 4 5 6 7 8 v a b c d e f g h i Post 4 2 8 3 1 7 5 9 6 13
Discendenti di un nodo CDE h e c b i a d g f 1 S 2 3 4 5 6 7 8 v a b c Fatto: Sia dato un albero T con i nodi numerati in postorder. Il numero dei nodi nel sottoalbero radicato in un nodo v (incluso) è dato dalla differenza tra il massimo ed il minimo valore che i nodi in Tv hanno nella numerazione, ovvero è dato dalla differenza del numero di nodi visitati prima di ritornare a p(v) e il numero di nodi visitati prima di raggiungere v. Con la numerazione in postorder il massimo valore in Tv è esattamente quello di Post(v) mentre il minimo può essere trovato in corrispondenza dell’arco (p(v),v). Il numero di discendenti della radice è |Tr| = n per ogni altro nodo il valore è |Tv| = S(v,p(v)) - S(p(v),v) CDE h e c b i a d g f 1 S 2 3 4 5 6 7 8 v a b c d e f g h i |Tv| 2 1 7 9 4
Visita in preorder CDE h e c b i a d g f 1 S 2 3 4 5 6 7 8 v a b c d e Fatto: nella numerazione in preorder (R,FS….FD), ogni nodo v viene numerato la prima volta che viene incontrato durante la visita, ovvero quando si arriva a v dal padre p(v). Dato il CDE di un albero T radicato in r, per ottenere la numerazione dei nodi in preorder sfruttando il Fatto, assegniamo valore 0 ad ogni arco (v,p(v)) che risale dal figlio al padre e valore +1 ad ogni arco (p(v), v) che scende dal padre al figlio. Sulla sequenza così ottenuta eseguiamo poi le somme prefisse. La numerazione in preorder è data da: v = r Pre(v) = 1 v r Pre(v) = S(p(v),v) +1 Riprendendo l’esempio abbiamo: CDE h e c b i a d g f 1 S 2 3 4 5 6 7 8 v a b c d e f g h i Pre 6 4 3 7 2 9 8 1 5 15
Livello di un vertice CDE h e c b i a d g f 1 -1 S 2 3 4 v a b c d e f Fatto: Il livello di un nodo è quello del padre aumentato di 1, ovvero quello di un figlio diminuito di 1. Per calcolare il livello di ciascun nodo nell’albero assegniamo valore +1 ad ogni arco discendente e valore -1 ad ogni arco ascendente. Eseguite le somme prefisse, si ha: v = r l(v) = 0 v r l(v) = S(p(v),v) Riprendendo l’esempio abbiamo: CDE h e c b i a d g f 1 -1 S 2 3 4 v a b c d e f g h i l(v) 3 2 1 4
Occorrenza sinistra e destra Anche per individuare la prima e l’ultima occorrenza di un nodo nel CDE di un albero radicato, sfruttiamo il vettore delle somme prefisse S calcolato nello stesso modo del calcolo dei livelli dei nodi. Per ogni arco (u,v) nel CDE calcoleremo: left(e) = 1 sse S(e.pred) < S(e) right(e) = 1 sse S(e.next) < S(e) Per ogni nodo v nel CDE calcoleremo: left(v) = indice dell’unico arco con left(u,v) = 1 right(v) = indice dell’unico arco con right(u,v) = 1 Notare: left(u,v) = right(u,v) = 1 sse v è una foglia. left(r)= 0
Esempio left e right Consideriamo l’albero in figura e il suo CDE,radicato nel nodo h h e h c b c i a d a i g c f c h I valori left e right associati all’arco (u,v) identificano rispettivamente la prima e l’ultima visita del nodo v nel Cammino di Eulero. c h g e f b i d a CDE h,e e,h h,c c,b b,c c,i i,a a,d d,a a,i i,g g,i i,c c,f f,c c,h 1 -1 S 2 3 4 left right
Visita inorder La visita inorder (FS,R,FD) è definita soltanto su alberi binari, perché è necessario poter distinguere tra figlio sinistro e figlio destro di ogni nodo. Nelle nostre strutture dati non abbiamo tale informazione quindi assumeremo che l’albero abbia la proprietà 0/2 (ciascun nodo ha esattamente 0 o 2 figli). Per ottenere la numerazione inorder ciascuna foglia viene conteggiata in corrispondenza della sua unica occorrenza (left = right = 1) nel CDE, mentre i nodi interni vengono conteggiati nell’occorrenza intermedia tra il primo ed il secondo figlio (left = right = 0). Si da valore 1 a tutti gli archi tali che left = right e si eseguono le somme prefisse S. v inorder(v) = S(u,v) dove (u,v) è l’arco in cui v è stato conteggiato: left(u,v) = right(u,v).
Esempio di visita inorder c g a b e d f CDE g,a a,g g,c c,b b,f f,b b,d d,b b,c c,e e,c c,g left 1 right S 2 3 4 5 6 7 v a b c d e f g inorder 1 4 6 5 7 3 2
LCA CDE Sliv 1 2 3 4 left right c h g e f b i d a lca(a,f)=c La computazione del minimo antecedente comune (lca) di ogni nodo si basa sulle seguenti osservazioni: u è antecedente di v sse left(u) < left(v) < right(u) u e v sono confrontabili (nessuno è antecedente dell’altro) sse right(v) < left(u) oppure right(u) < left(v) se u e v sono confrontabili, lca(u,v) è il vertice di livello più basso compreso tra right(u) e left(v) Esempio: c h g e f b i d a lca(a,f)=c right(a) left(f) CDE h,e e,h h,c c,b b,c c,i i,a a,d d,a a,i i,g g,i i,c c,f f,c c,h Sliv 1 2 3 4 left right