11.07.2015 Views

Esercizi di Algoritmi e Strutture Dati - Moreno Marzolla

Esercizi di Algoritmi e Strutture Dati - Moreno Marzolla

Esercizi di Algoritmi e Strutture Dati - Moreno Marzolla

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

<strong>Esercizi</strong> <strong>di</strong> <strong>Algoritmi</strong> e <strong>Strutture</strong> <strong>Dati</strong><strong>Moreno</strong> <strong>Marzolla</strong>marzolla@cs.unibo.itUltimo aggiornamento: 29 novembre 20101 Rotazioni semplici in ABRSi consideri l’operazione <strong>di</strong> rotazione semplice applicata ad un Albero Binario<strong>di</strong> Ricerca (ABR). Dimostrare che l’operazione <strong>di</strong> rotazione semplice, comedefinita a lezione, preserva la proprietà <strong>di</strong> or<strong>di</strong>namento degli ABR. In altre parole,<strong>di</strong>mostrare che un ABR, dopo una singola operazione <strong>di</strong> rotazione semplicerispetto ad un qualsiasi nodo x, è ancora un ABR.SoluzioneConsideriamo la figura seguente:X Y/ \ / \Y T3 T1 X/ \ / \T1 T2 T2 T3Supponiamo <strong>di</strong> effettuare una rotazione semplice verso destra, usando Xcome perno. Prima della rotazione, valgono le seguenti relazioni:Y ≤ XT 1 ≤ YT 2 ≥ YT 2 ≤ XT 3 ≥ X(dove per semplicità usiamo la notazione T 1 ≤ Y per in<strong>di</strong>care che per ogninodo Z <strong>di</strong> T 1 vale la relazione Z ≤ Y ). È possibile verificare che tutte questerelazioni valgono anche nell’albero <strong>di</strong> destra (quello ottenuto dopo la rotazione),che quin<strong>di</strong> è ancora un ABR.1


2 Costruzione <strong>di</strong> ABRDimostrare che qualsiasi algoritmo basato su confronti per la costruzione <strong>di</strong> unABR con n no<strong>di</strong> ha complessità asintotica Ω(n log n).Soluzione Supponiamo che sia possibile costruire un ABR con n no<strong>di</strong>, utilizzandoconfronti, in tempo strettamente inferiore a Ω(n log n). Ricor<strong>di</strong>amoche, dato un ABR con n no<strong>di</strong>, è possibile ottenere la lista or<strong>di</strong>nata delle chiaviin esso contenute me<strong>di</strong>ante una visita simmetrica (detta anche visita inor<strong>di</strong>ne)dell’albero. La visita simmetrica ha costo Θ(n). Quin<strong>di</strong>, se potessimocostruire un ABR in tempo inferiore a Ω(n log n), riusciremmo anche aor<strong>di</strong>nare un insieme <strong>di</strong> n elementi, usando solo confronti, in tempo inferiorea Ω(n log n), il che è impossibile dato il limite inferiore alla complessità delproblema dell’or<strong>di</strong>namento me<strong>di</strong>ante confronti3 Visita <strong>di</strong> un ABRL’operazione <strong>di</strong> visita <strong>di</strong> un ABR con n no<strong>di</strong> può essere implementata determinandol’elemento minimo dell’ABR, e poi invocando n − 1 volte l’operazionesuccessor(). Fornire una giustificazione intuitiva del fatto che questo algoritmo<strong>di</strong> visita abbia complessità asintotica Θ(n).SoluzioneRicor<strong>di</strong>amo l’algoritmo per determinare il successore <strong>di</strong> un nodoalgorithm successor(nodo v) -> nodoif (v == null) thenreturn null;en<strong>di</strong>fif (v.right != null) thenreturn min(v.right);elsep = v.parentwhile (p != null && v == p.right) dov = p;p = v.parent;endwhilereturn p;en<strong>di</strong>fdove la procedura min() determina il nodo con chiave minima in un alberora<strong>di</strong>cato nel nodo v, ed è definita comealgorithm min(nodo v) -> nodowhile (v != null && v.left != null) dov = v.left;endwhile2


eturn v;Se proviamo a “visualizzare” il comportamento dell’operazione <strong>di</strong> visita implementatame<strong>di</strong>ante determinazione dell’elemento minimo seguita da n − 1 invocazionidella procedura successor(), osserviamo che ciascun arco dell’alberoviene attraversato esattamente due volte: una volta “in <strong>di</strong>scesa”, ad opera dellaprocedura min(), e una volta “in salita” ad opera della procedura successor(),che nel ramo else risale <strong>di</strong> figlio in padre fino alla prima “svolta a destra”. Èanche importante osservare che una volta attraversato in <strong>di</strong>scesa e in salita, unarco non viene più attraversato. Osserviamo anche che vengono eseguite O(1)operazioni elementari per ogni attraversamento <strong>di</strong> arco.A questo punto, osserviamo che un albero on n no<strong>di</strong> ha esattamente n − 1archi (si <strong>di</strong>mostra per induzione, e vale per qualsiasi albero, non solo per alberibinari). Quin<strong>di</strong> il costo dell’operazione <strong>di</strong> visita implementata come sopra è2(n − 1) = Θ(n).4 Incremento <strong>di</strong> chiavi in un albero AVLSi consideri un albero AVL contenente n chiavi numeriche. Supponiamo chele chiavi siano tutte <strong>di</strong>stinte tra loro. Vogliamo implementare l’operazioneincrementaChiave(k, d) il cui scopo è quello <strong>di</strong> incrementare il valore dellachiave k <strong>di</strong> una quantità d (che potrebbe anche essere negativa), facendolo <strong>di</strong>ventarek + d. Al termine <strong>di</strong> questa operazione, la struttura dati risultantedeve ancora essere un albero AVL. Per l’ipotesi <strong>di</strong> unicità delle chiavi, il nodocontenente la chiave k, se esiste, è sempre unico; supponiamo anche che ilvalore k + d sia unico. Descrivere un algoritmo per realizzare l’operazioneincrementaChiave(k,d), stimandone poi il costo computazionale.Soluzione Esiste una soluzione banale, che consiste nel rimuovere il nodo conchiave k (che per ipotesi è unico) e inserire un nuovo nodo con chiave k + d. Ilcosto complessivo risulta essere O(log n).5 Implementazione <strong>di</strong> un albero AVLA lezione abbiamo visto che per una corretta implementazione degli alberi AVLè necessario conoscere l’altezza dei sottoalberi ra<strong>di</strong>cati in ciascun nodo. Infatti,questa informazione consente poi <strong>di</strong> capire quali sono i sottoalberi “pesanti”,e quin<strong>di</strong> procedere alle operazioni <strong>di</strong> ribilanciamento appropriate. Ricor<strong>di</strong>amoche l’altezza <strong>di</strong> un albero è la massima profon<strong>di</strong>tà cui si trova una sua foglia.L’albero composto da un singolo nodo (la ra<strong>di</strong>ce) ha altezza 0.1. Consideriamo innanzitutto un generico ABR (non bilanciato). Supponiamoche ciascun nodo v abbia un attributo intero v.h che corrispondeall’altezza del sottoalbero ra<strong>di</strong>cato in v. Mostrare come sia possibile estenderele operazioni <strong>di</strong> inserimento e rimozione <strong>di</strong> no<strong>di</strong> <strong>di</strong> un ABR per3


mantenere in modo efficiente il valore corretto <strong>di</strong> v.h per ciascun nodo.Tale mo<strong>di</strong>fica non deve alterare il costo computazionale delle operazioni<strong>di</strong> inserimento e rimozione, che devono mantenersi O(h) nel caso pessimo,essendo h l’altezza totale dell’albero.2. Consderiamo ancora un generico ABR. Dimostrare come l’operazione <strong>di</strong>rotazione semplice può essere estesa per mantenere il valore corretto <strong>di</strong> v.hper ciascun nodo. Dopo tale mo<strong>di</strong>fica, il costo dell’operazione <strong>di</strong> rotazionesemplice deve essere O(h) nel caso pessimo, essendo h l’altezza totaledell’albero.3. Usare i due punti precedenti per <strong>di</strong>mostrare come sia possibile mantenerel’informazione sull’altezza <strong>di</strong> ciascun sottoalbero in un albero AVL senzaalterare il costo computazionale delle operazioni <strong>di</strong> inserimento e rimozione<strong>di</strong> no<strong>di</strong>.SoluzioneAssumiamo che un oggetto nodo v abbia gli attributi seguenti:v.left riferimento al figlio sinistro (oppure null);v.right riferimento al figlio destro (oppure null);v.parent riferimento al padre (oppure null se v è la ra<strong>di</strong>ce);v.h altezza dell’albero ra<strong>di</strong>cato in v.Definiamo come prima cosa l’algoritmo aggiusta_h(v). L’algoritmo funzionacome segue: assume che i figli del nodo v (se esistono) abbiano il valorecorretto dell’attributo h (quin<strong>di</strong>, assume <strong>di</strong> conoscere in maniera esatta l’altezzadei sottoalberi ra<strong>di</strong>cati nei figli, sempre se non sono vuoti). In base a questainformazione, calcola il valore <strong>di</strong> v.h.algoritmo aggiusta_h(Nodo v)if ( v.left == null && v.right == null ) thenv.h = 0;elseif( v.left == null ) then // v.right != nullv.h = v.right.h + 1;elseif( v.right == null ) then // v.left != nullv.h = v.left.h + 1;elsev.h = max( v.left.h, v.right.h ) + 1;en<strong>di</strong>fA questo punto è facile definire un’altra procedura, che chiameremo aggiusta_h_ricche risale ricorsivamente da un nodo v fino alla ra<strong>di</strong>ce dell’albero, ricalcolandoil valore dell’attributo h <strong>di</strong> tutti i no<strong>di</strong> visitati:4


algoritmo aggiusta_h_ric(Nodo v)while ( v != null ) doaggiusta_h(v);v = v.parent;endwhileOra siamo in grado <strong>di</strong> ricalcolare il valore <strong>di</strong> h in caso <strong>di</strong> inserimento orimozione <strong>di</strong> no<strong>di</strong>.Nel caso <strong>di</strong> inserimento in un ABR, sappiamo che il nuovo nodo venga inseritoin una foglia v. Dopo l’inserimento, chiamiamo semplicemente l’operazioneaggiusta_h_ric(v) che ricalcola tutte le altezze da v fino alla ra<strong>di</strong>ce dell’albero.Il costo complessivo è O(h) nel caso pessimo, essendo h l’altezza dell’albero.Nel caso <strong>di</strong> rimozione, è necessario <strong>di</strong>stinguere i tre casi:• Il nodo rimosso v era una foglia. Sia p il padre <strong>di</strong> v (se esiste). Dopo averstaccato v, si invoca aggiusta_h_ric(p) e l’algoritmo termina.• Il nodo rimosso v ha un unico figlio w. Sia p il padre <strong>di</strong> v. Il nodo w vienereso figlio <strong>di</strong> p (al posto <strong>di</strong> v, e si invoca l’operazione aggiusta_h_ric(p).L’algoritmo termina.• Il nodo rimosso v ha due figli. Si in<strong>di</strong>vidua il nodo predecessore w, ilquale non ha figlio destro. Sia p il padre <strong>di</strong> w. È possibile rimuoverew attaccando il padre p all’unico figlio <strong>di</strong> w. Il nodo w si sostituiscea v. A questo punto si invoca aggiusta_h_ric(p), essendo sicuri chenel cammino da p alla ra<strong>di</strong>ce la procedura attraversa anche la posizioneprecedentemente occupata dal nodo v che è stato rimosso (posizione oraoccupata da w).Nel caso della rotazione semplice, consideriamo il caso <strong>di</strong> rotazione sempliceverso destra rispetto ad un nodo x. Si può eseguire la procedura seguente:x/ \y t3/ \t1 t2y = x.left;t1 = y.left;t2 = y.right;t3 = x.right;y.right = x;x.left = t2;x.right = t3;aggiusta_h_ric(x);5


6 Attraversamento in-or<strong>di</strong>ne iterativoScrivere un algoritmo iterativo per effettuare la visita in-or<strong>di</strong>ne <strong>di</strong> un alberobinario (non necessariamente <strong>di</strong> ricerca).Soluzione Per implementare l’algoritmo <strong>di</strong> visita in-or<strong>di</strong>ne facciamo uso <strong>di</strong>uno stack (pila). Gli elementi che inseriamo nello stack sono coppie < n, b >,essendo n un riferimento ad un nodo dell’albero, e b un valore booleano che puòessere true o false.algoritmo inor<strong>di</strong>ne-iter(Nodo t)Stack S;S.push( );while (!S.empty()) do := S.pop(); // estrai dallo stackif (f==true) thenvisita il nodo n;elseif (n.right != null) thenS.push( );en<strong>di</strong>fS.push( );if (n.left != null) thenS.push( );en<strong>di</strong>fen<strong>di</strong>fendwhileL’idea dell’algoritmo è la seguente: ogni nodo n viene inizialmente inseritonello stack con flag settato a false. Quando un nodo v con flag settato a falseviene estratto dallo stack, inseriamo prima il figlio destro, poi il nodo v con flagsettato a true, e quin<strong>di</strong> il figlio sinistro. Quando estraiamo dallo stack un nodocon flag settato a true, è giunto il momento <strong>di</strong> “visitare” il nodo, che non verràulteriormente reinserito.7 Albero inversoDato un albero binario, i cui no<strong>di</strong> contengono elementi interi, si scriva unaprocedura <strong>di</strong> complessità ottima per ottenere lalbero inverso, ovvero un alberoin cui il figlio destro (con relativo sottoalbero) è scambiato con il figlio sinistro(con relativo sottoalbero).Soluzionealgoritmo inverti(Nodo t)if (t == NULL) then6


eturn;else// scambia i figli sinistro e destroNodo tmp = t.left;t.left = t.right;t.right = tmp;inverti(t.left);inverti(t.right;en<strong>di</strong>fL’algoritmo viene inizialmente invocato passando come parametro un riferimentoalla ra<strong>di</strong>ce dell’albero da invertire.8 Cancellazione <strong>di</strong> no<strong>di</strong> da ABRL’operazione <strong>di</strong> cancellazione da un ABR commutativa? Nel senso che cancellareprima x e poi y, oppure cancellare prima y e poi x, produce lo stessoABR?SoluzioneConsideriamo il seguente ABR6/4/ \2 5/ \1 36/2/ \1 3Rimuovendo prima il valore 5, poi il valore 4 si ottiene il seguente ABRSe invece rimuoviamo prima il valore 4, poi il valore 5 si ottiene il seguenteABR6/3/2/17


9 Verifica ABR(Questo esercizio è stato assegnato nella prova scritta del 20/9/2010) Si consideriun albero binario B in cui a ciascun nodo v è associata una chiave numerica(reale) v.key. Non ci sono chiavi ripetute, e tutte le chiavi appartengonoall’intervallo [a, b].1. Scrivere un algoritmo efficiente che dato in input l’albero B e gli estremi ae b, restituisce true se e solo se B rappresenta un albero binario <strong>di</strong> ricerca.Non consentito usare variabili globali.2. Calcolare il costo computazionale nel caso ottimo e nel caso pessimodell’algoritmo <strong>di</strong> cui al punto 1. Disegnare un esempio <strong>di</strong> albero che produceun caso pessimo, e un esempio <strong>di</strong> albero che produce il caso ottimo.Soluzione Innanzitutto è utile ricordare che in un ABR i valori delle chiavicontenute nel sottoalbero sinistro <strong>di</strong> un nodo v sono tutti minori o uguali av.key, mentre i valori delle chiavi contenute nel sottoalbero destro <strong>di</strong> v sonotutti maggiori o uguali <strong>di</strong> v.key.Una possibile soluzione è la seguentealgoritmo checkABR(nodo v, float a, float b) -> boolif ( v == null ) thenreturn true;elsereturn (a

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!