Note per la Lezione 4 Ugo Vaccaro

Documenti analoghi
Esercizi su alberi binari

Alberi e alberi binari I Un albero è un caso particolare di grafo

Algoritmi e Strutture Dati

Alberi ed Alberi Binari

Note per la Lezione 6 Ugo Vaccaro

Esercizi di Algoritmi e Strutture Dati

Problemi di ordinamento

LE STRUTTURE DATI DINAMICHE: GLI ALBERI. Cosimo Laneve

Espressioni aritmetiche

Dati e Algoritmi I (Pietracaprina) Esercizi sugli Alberi

Esercizio 1. E vero che in un AVL il minimo si trova in una foglia o nel penultimo livello? FB = -1. livello 0 FB = -1. livello 1 FB = -1.

Esercizi su BST. IASD a.a A. De Bonis

Alberi. Gli alberi sono una generalizzazione delle liste che consente di modellare delle strutture gerarchiche come questa: Largo. Fosco.

In questa lezione Alberi binari di ricerca: la cancellazione

GLI ALBERI BINARI DI RICERCA. Cosimo Laneve

Alberi. Alberi: definizioni. Alberi Binari. Esercizi su alberi binari: metodi ricorsivi. Struttura dati per alberi generici. ASD-L - Luca Tesei

Algoritmi e Strutture Dati. HeapSort

PROVETTE D ESAME. Algoritmi e Strutture Dati

Lezione 7 Alberi binari: visite e alberi di ricerca

Alberi. Strutture dati: Alberi. Alberi: Alcuni concetti. Alberi: definizione ricorsiva. Alberi: Una prima realizzazione. Alberi: prima Realizzazione

Un albero completamente bilanciato o pieno (full) alberi completamente sbilanciati. Un albero binario completo

Lezione 12 Tabelle Hash

Laboratorio di Python

Esercizi di Algoritmi e Strutture Dati

Dati e Algoritmi I (Pietracaprina) Esercizi su Alberi Binari di Ricerca e (2,4)-Tree

Alberi binari. Esercizi su alberi binari

Laboratorio di Python

Algoritmi Greedy. Tecniche Algoritmiche: tecnica greedy (o golosa) Un esempio

Alberi n-ari: specifiche sintattiche e semantiche. Realizzazioni. Visita di alberi n-ari.

Heap e code di priorità

Problemi, istanze, soluzioni

Alberi Binari di Ricerca

Esercitazione 6. Alberi binari di ricerca

Algoritmi e Strutture Dati. Alberi

Alberi binari e alberi binari di ricerca

Alberi binari di ricerca

Albero binario. Alberi binari (introduzione) Terminologia. Alberi di ricerca binaria (BST)

Alberi Binari di Ricerca

ADT Coda con priorità

Esercizi Capitolo 6 - Alberi binari di ricerca

Capitolo 3: Gli alberi. Alberi n-ari

Per semplicità eliminiamo le ripetizioni nell'albero.

Algoritmi e Strutture Dati

Esercizi vari. Alberto Montresor. 19 Agosto, 2014

Algoritmi e Strutture Dati

Progettazione di algoritmi

In questa lezione Strutture dati elementari: Pila Coda

Note per la Lezione 7 Ugo Vaccaro

Fondamenti di Informatica T-1 Modulo 2

TRIE (albero digitale di ricerca)

Algoritmi e Strutture di Dati I 1. Algoritmi e Strutture di Dati I Massimo Franceschet francesc

Grafi: visite. Una breve presentazione. F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)

Lezione 9 Alberi binari di ricerca

Progettazione di algoritmi

Il tipo astratto coda con priorità: specifiche sintattiche e semantiche. Realizzazioni.

alberi binari e ricorsione

Algoritmo basato su cancellazione di cicli

Introduzione. Heap-binomiali: un heap binomiale è un insieme di alberi binomiali.

Esercitazioni di Algoritmi e Strutture Dati

Strutture dati per insiemi disgiunti

5. DIVIDE AND CONQUER I

Algoritmi di Ricerca

In questa lezione. Heapsort. ordinamento con complessità, nel caso peggiore, O(nlogn) [CLRS01] cap. 6 da pag. 106 a pag. 114

Alberto Montresor Università di Trento

Implementazione ADT: Alberi

Transcript:

Progettazione di Algoritmi Anno Accademico 2016 2017 Note per la Lezione 4 Ugo Vaccaro Ripasso di nozioni su Alberi Ricordiamo che gli alberi rappresentano una generalizzazione delle liste, nel senso che mentre ogni elemento di una lista ha al più un successore, ogni elemento di un albero, detto anche nodo, può avere più di un successore. Ogni nodo dell albero ha pertanto associata la lista (eventualmente vuota) dei figli ad esso collegati da un arco. Chiameremo foglie i nodi dell albero senza figli e nodi interni i rimanenti nodi. Analogamente, l albero associa ad ogni nodo (eccetto che alla radice, che è il nodo di partenza dell albero) un unico genitore, detto padre. I nodi figli dello stesso padre sono detti fratelli. Ad ogni nodo interno è associato il sottoalbero di cui tale nodo è radice. Se un nodo u è la radice di un sottoalbero contenente il nodo v, diremo che u è un antenato di v e che v è un discendente di u. Ricordiamo inoltre che la distanza di un nodo u dalla radice r è pari al numero di archi che sono presenti nel percorso dalla radice r al nodo u. Tale distanza sarà talvolta chiamata profondità (o livello) del nodo u nell albero con radice r. L altezza di un albero è pari al massimo, calcolato su tutte le foglie, della profondità delle sue foglie. Un particolare tipo di albero è costituito dagli alberi binari. In essi, ogno nodo ha al più due figli, che chiamaremo figlio sinistro e figlio destro. Per ogno nodo u di un albero, denoteremo con u.dato il contenuto del nodo, con u.sx il riferimento (puntatore) al suo figlio sinistro e con u.dx il riferimento (puntatore) al suo figlio destro. Talvolta, ipotizzeremo che sia anche presente un puntatore u.padre al padre di u. É bene tener presente che tali puntatori possono essere vuoti, ad esempio il nodo u è una foglia se e solo se u.sx e d.sx sono entrambi vuoti, ovvero sia u.sx che d.sx hanno valore null. Vediamo qualche applicazione della tecnica Divide-et-Impera alla risoluzione di problemi algoritmici su alberi binari. Input: Albero binario con puntatore r alla radice. Output: Per ogni nodo x dell albero, vogliamo il numero c(x) di nodi nel sottoalbero radicato in x. Per esempio, per l albero rappresentato a sinistra indichiamo a destra lo stesso albero con il valore c(x) all interno di ogni nodo x. 6 3 2 1 1 1 Il seguente semplice algoritmo risolve il problema in questione. La struttura dell algoritmo è la tipica struttura di algoritmi ricorsivi per alberi binari: si controlla se l albero è vuoto, altrimenti si ricorre nell eventuale albero radicato nel figlio sinistro e nell eventuale albero radicato nel figlio destro della radice. 1

CalcolaNumero(r) IF(r==null) { c(r)=0 RETURN c(r) ELSE { c(r)=1+c(r.sx)+c(r.dx) RETURN c(r) Poichè ogni nodo dell albero viene esaminato una ed una sola volta, si ha che la complessità dell algoritmo CalcolaNumero(r) è Θ(n), dove n è il numero di nodi dell alberto radicato in r. Consideriamo ora il seguente problema. Input: Albero binario con puntatore r alla radice. Output: La somma delle distanze dalla radice r ad ogni altro nodo x. Ad esempio, per l albero disegnato di sotto a sinistra, la distanza di ciascun nodo dalla radice è indicato dall intero contenuto nel nodo stesso e la somma delle distanze sarebbe 0+1+1+2+2+2 = 8. 0 1 1 2 2 2 Denotiamo con d T (x) la distanza del nodo x dalla radice dell albero T. Dalla figura, comprendiamo che vale la seguente relazione: d T (x) = d Tl (x)+1 r a b x dove d Tl (x) è la distanza di x dalla radice a del sottoalbero sinistro T l di r. Pertanto, per la quantità L(T) = 2

x T d T(x) che vogliamo calcolare, vale L(T) = x T d T (x) = x T l d T (x)+ x T r d T (x) = x T l (d Tl (x)+1)+ x T r (d Tr (x)+1) = x T l (d Tl (x))+ x T l 1+ x T r (d Tr (x))+ x T r 1 = L(T l )+(numero nodi in T l )+L(T r )+(numero nodi in T r ) Ricordiamo che il numero di nodi in un generico albero binario radicato in un arbitrario nodo r lo sappiamo calcolare mediante l algoritmo CalcolaNumero(r). Mettendo tutto insieme, possiamo scrivere il seguente algoritmo per il calcolo della somma delle distanze dalla radice r ad ogni altro nodo x nell albero. La lgoritmo prende in input, oltre al puntatore alla radice r dell albero, anche il vettore c contenente i valori c(x) =numero di nodi nel sottoalbero radicato in x, per ogni nodo x, calcolati dall algoritmo precedente. CalcolaSommaDistanze(r,c) 1. IF (r==null) { 2. RETURN 0 3. ELSE { 4. RETURN CalcolaSommaDistanze(r.sx)+CalcolaNumero(r.sx) +CalcolaSommaDistanze(r.dx)+CalcolaNumero(r.dx) Poichè ogni nodo dell albero viene esaminato un numero costante di volte, abbiamo che la complessità dell algoritmo CalcolaNumero(r) è Θ(n), dove n è il numero di nodi dell alberto radicato in r. Consideriamo ora il seguente problema. Input: Puntatori r1 ed r2 a due alberi binari Output: True se e solo se i due alberi binari hanno la stessa struttura. Ricordiamo che due alberi binari hanno la stessa struttura se sono entrambi vuoti, oppure se sono entrambi non vuoti e i sottoalberi destri e sinistri delle radici hanno rispettivamente la stessa struttura. Ad esempio, questi due alberi hanno chiaramente la stessa struttura: 3

mentre i seguenti due non hanno la stessa struttura Una possibile soluzione è la seguente. Di nuovo, l algoritmo adotta la solita tecnica di verificare innanzitutto se gli alberi in questione sono entrambi vuoti. Nel caso in cui ciò non sia, il problema viene ricorsivamente risolto nei due eventuali sottoalberi delle due radici. StessaStruttura(r1, r2) 1. IF((r1==null)&&(r2==null) { 2. RETURN True 3. ELSE { % se siamo qui, allora uno solo tra i due alberi è vuoto, oppure nessuno dei due è vuoto 4. IF (r1==null r2== null) { 5. RETURN False % infatti uno dei due è vuoto e l altro no 6. ELSE { 7. RETURN StessaStruttura(r1.sx, r2.sx)&&stessastruttura(r1.dx, r2.dx) Se entrambi gli alberi sono entrambi vuoti, allora hanno la stessa struttura e l algoritmo ritorna true. Se questo non è vero, significa che i due alberi non sono entrambi vuoti, ossia uno tra essi e vuoto, oppure nessuno dei due lo è. Se uno dei due e vuoto e l altro no, allora i due alberi non hanno la stessa struttura. Se entrambi non sono vuoti, allora i due alberi hanno la stessa struttura se e solo se i sottoalberi sinistri e quelli destri hanno rispettivamente la stessa struttura. Per l analisi della complessità dell algoritmo, è evidente che nel caso peggiore l algoritmo esegue due visite complete di ciascun albero, per cui la complessità è pari a Θ(n), per alberi con n nodi. 4

Consideriamo un albero binario la cui radice è r e due suoi nodi u e v. Si definisce Lowest Common Ancestor (LCA) dei nodi u e v, indicato con LCA(u, v), il nodo dell albero di profondità massima che ha sia u che v come discendenti. Ad esempio, nel caso seguente il LCA di u e v è il nodo x: x v u Il problema in questione è quindi: Input: Puntatore r ad un albero binario, puntatori u e v a due nodi nell albero. Output: Nodo LCA(u, v). Proponiamo un algoritmo che opera utilizzando due stack Su e Sv. In ciascuno stack verranno inseriti i nodi presenti sul cammino da u e v, rispettivamente, fino alla radice r. Al termine di questa operazione, in cima a entrambi gli stack si troverà necessariamente un riferimento alla radice r dell albero. Adesso, estraiamo contemporaneamente un elemento da entrambi gli stack; l ultimo elemento uguale estratto da entrambi sarà il LCA che cerchiamo. Occorre prestare attenzione al fatto che, durante questo processo, uno dei due stack potrebbe svuotarsi (in particolare ciò accade se uno dei due nodi è antenato dell altro, e quindi è esso stesso il LCA di entrambi). LowestCommonAncestor(u,v,r) 1. Stack Su 2. Stack Sv 3. WHILE(u!==null) { 4. Su.PUSH(u) 5. u=u.padre 6. WHILE(v!==null) { 7. Sv.PUSH(v) 8. v=v.padre 9. WHILE(!Su.ISEMPTY()&&!Sv.ISEMPTY()&&Su.TOP()==Sv.TOP()) { 10. LCA=Su.TOP() 11. Su.POP() 12. Sv.POP() 13. RETURN LCA Il costo dell algoritmo è dominato dal costo necessario per riempire gli stack, che nel caso peggiore corrisponde alla profondità massima dell albero. Poiché nel caso peggiore un albero di n nodi ha profondità n 1 (l albero degenera in una lista), si ha che il costo dell algoritmo nel caso peggiore è O(n). Il caso migliore si verifica quando uno dei due nodi è la radice e l altro è uno dei suoi figli, in questo caso il costo sarà O(1). 5

Si consideri un albero binario arbitrario in cui a ciascun nodo v sia associato un numero v.dato. Vogliamo progettare un algoritmo ricorsivo che, dato in input l albero con radice r e un intero k 0, restituisce la somma dei valori associati ai nodi che si trovano a profondità maggiore o uguale a k; se non esistono nodi a profondità maggiore o uguale a k, oppure nel caso in cui l albero vuoto, l algoritmo deve restituire 0. Ricordiamo che la radice si trova a profondità zero. Ad esempio, considerando l albero seguente, e posto k = 2: 2 3 7 6 1 9 5 4 8 5 2 l output dell algoritmo dovrebbe essere la somma degli interi contenuti nei nodi grigi, ovvero 36. Quindi il problema algoritmico è il seguente. Input: Puntatore r ad un albero binario, intero k 0 Output: la somma dei valori u.dato, per tutti i nodi u di profondità k. SommaProfondità(r,k) 1. IF(r==null) { 2. RETURN 0 3. ELSE { 4. risultato=0 5. IF(k 0){ 6. risultato=r.dato 7. risultato=risultato + SommaProfondità(r.sx,k-1)+SommaProfondità(r.dx,k-1) 8. RETURN risultato Ogni nodo viene visitato al più una volta, per cui la complessità dell algoritmo è pari a O(n), per alberi di n nodi. 6