Algoritmi e Strutture Dati Capitolo 5 - Alberi Alberto Montresor Università di Trento This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/2.5/ or send a letter to Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA. © Alberto Montresor 1 Alberi radicati ✦ Albero: definizione informale ✦ ✦ E' un insieme dinamico i cui elementi hanno relazioni di tipo gerarchico Albero: definizione ricorsiva ✦ ✦ Insieme vuoto di nodi, oppure Una radice T e 0 o più sottoalberi, con la radice di ogni sottoalbero collegata a T da un arco (orientato) T es.: radice T con n sottoalberi T1 © Alberto Montresor T2 Tn 2 Alberi ordinati T Figlio (child) di T Nodi interni = Nodi - Foglie Radice (root) Padre (parent) dei nodi j e k Figlio di T Radice del proprio sottoalbero Sottoalbero a j k ... Foglie (leaf) © Alberto Montresor Nodi fratelli (figli di a) 3 Alberi: definizioni ✦ In un albero ✦ ✦ ✦ Profondità di un nodo: la lunghezza del percorso dalla radice al nodo (i.e., numero archi attraversati) Livello: l'insieme dei nodi alla stessa profondità Altezza dell'albero: massimo livello delle sue foglie p=0 p=1 p=2 p=3 Livello 3 Altezza albero: 3 © Alberto Montresor 4 Alberi? © Alberto Montresor 5 Alberi? DAG Radice Foresta © Alberto Montresor 5 possibile cheAlberi: permettonouna di leggere e scrivere ilspecifica contenuto dei nodi. T REE % Costruisce un nuovo albero, costituito da un solo nodo e contenente v Tree(I TEM v) % Legge il valore I TEM read() % Scrive v nel nodo write(I TEM v) % Restituisce il padre; nil se questo nodo è radice T REE parent() % Restituisce il primo figlio; nil se questo nodo è foglia T REE leftmostChild() % Restituisce il prossimo fratello del nodo a cui è applicato; nil se assente T REE rightSibling() % Inserisce il sottoalbero t come primo figlio di questo nodo insertChild(T REE t) precondition: t.parent() = nil % Inserisce il sottoalbero t come successivo fratello di questo nodo insertSibling(T REE t) precondition: t.parent() = nil % Distrugge il sottoalbero radicato nel primo figlio di questo nodo deleteChild() % Distrugge il sottoalbero radicato nel prossimo fratello di questo nodo deleteSibling() © Alberto Montresor 6 Algoritmi di visita degli alberi ✦ Visita (o attraversamento) di un albero: ✦ ✦ ✦ Algoritmo per “visitare” tutti i nodi di un albero In profondità (depth-first search, a scandaglio): DFS ✦ Vengono visitati i rami, uno dopo l’altro ✦ Tre varianti In ampiezza (breadth-first search, a ventaglio): BFS ✦ A livelli, partendo dalla radice © Alberto Montresor 7 dall’ultimo nodo visitato al primo). Il seguente schema di procedura serve per effettuare sia la visita anticipata quella posticipata. Visita alberi: che in profondità in ordine anticipato (previsita) visitaProfondità(T REE t) precondition: t �= nil (1) T esame “anticipato” del nodo radice di t a T REE u ← t.leftmostChild() while u �= nil do visitaProfondità(u) u ← u.rightSibling() (2) b c e d f d e g esame “posticipato” del nodo radice di t Sequenza: a © Alberto Montresor b c f g 8 dall’ultimo nodo visitato al primo). Il seguente schema di procedura serve per effettuare sia la visita anticipata quella posticipata. Visita alberi: che in profondità in ordine posticipato (postvisita) visitaProfondità(T REE t) precondition: t �= nil (1) T esame “anticipato” del nodo radice di t a T REE u ← t.leftmostChild() while u �= nil do visitaProfondità(u) u ← u.rightSibling() (2) b c e d f f g g esame “posticipato” del nodo radice di t Sequenza: c © Alberto Montresor d b e a 9 nodo t, la stessa procedura viene richiamata ricorsivamente sui primi i figli di t, se presenti; viene effettuata simmetrica di t, e simmetrico quindi invisita(invisita) () viene richiamata sui restanti figli. Visita alberi:lainvisita profondità in ordine invisita(T REE t) precondition: t �= nil T REE u ← t.leftmostChild() integer k ← 0 while u �= nil and k < i do k ←k+1 invisita(u) u ← u.rightSibling() esame “simmetrico” del nodo t while u �= nil do invisita(u) u ← u.rightSibling() visitaAmpiezza(T REE t) precondition: t �= nil T Q UEUE Q ← Queue() a Q.enqueue(t) b while not Q.isEmpty () do e T REE u ← Q.dequeue() d f nodogu esamec“per livelli” del u ← u.leftmostChild() while u �= nil do Q.enqueue(u) u ← u.rightSibling() La visita per livelli illustrata nell’algoritmo visitaAmpiezza() utilizza un approccio comSequenza (i=1): c ricorsiva b (che d implicitamente a f eutilizza g pletamente diverso. Invece di essere basata su una visita una pila), è una procedura† iterativa basata su una coda. All’inizio, si inserisce la radice nella coda. Quando un nodo viene estratto, vengono inseriti tutti i suoi figli nella coda, in ordine. È facile dimostrare per induzione che tutti i nodi del livello i-esimo vengono esaminati prima dei10 © Alberto Montresor icorsivamente sui primi i figli di t, se presenti; ndi invisita viene richiamata sui restanti figli. Visita()alberi: in ampiezza visitaAmpiezza(T REE t) precondition: t �= nil Q UEUE Q ← Queue() Q.enqueue(t) while not Q.isEmpty() do T REE u ← Q.dequeue() esame “per livelli” del nodo u u ← u.leftmostChild() while u �= nil do Q.enqueue(u) u ← u.rightSibling() T a b c Sequenza: a o visitaAmpiezza() utilizza un approccio comuna visita ricorsiva (che implicitamente utilizza una coda. All’inizio, si inserisce la radice nella inseriti tutti i suoi figli nella coda, in ordine. È del©livello i-esimo vengono esaminati prima dei Alberto Montresor b e d e g f c d f g 11 Realizzazione con vettore dei figli / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / // / / / / Padre Rischio di sprecare memoria se molti nodi hanno grado minore del grado massimo k. © Alberto Montresor Nodo Array di Figli 12 Realizzazione con puntatori padre/primo-figlio/fratello / / / / / / / / / / / / / / / / Padre Nodo Primo Figlio Fratello Soluzione: usare una lista di figli (fratelli). © Alberto Montresor 13 Realizzazione con puntatori padre/primo-figlio/fratello T REE N ODE parent N ODE child N ODE sibling I TEM value Tree(I TEM v) T REE t ← new T REE t.value ← v t.parent ← t.child ← t.sibling ← nil return t insertChild(T REE t) t.parent() ← this t.rightSibling() ← child % Puntatore al padre % Puntatore al primo figlio % Puntatore al successivo fratello % Valore del nodo % Crea un nuovo nodo % Inserisce t prima dell’attuale primo figlio child ← t insertSibling(T REE t) t.parent() ← this t.rightSibling() ← sibling % Inserisce t prima dell’attuale fratello sibling ← t deleteChild() © Alberto Montresor 14 t.rightSibling() ← sibling % Inserisce t prima dell’attuale fratello Realizzazione con puntatori padre/primo-figlio/fratello sibling ← t deleteChild() N ODE newChild ← child.rightSibling() delete(child) child ← newChild deleteSibling() N ODE newBrother ← sibling.rightSibling() delete(sibling) sibling ← newBrother delete(T REE t) N ODE u ← t.leftmostChild() while u �= nil do T REE next ← u.rightSibling() delete(u) u ← next delete t © Alberto Montresor 15 Realizzazione con vettore dei padri ✦ ✦ L'albero è rappresentato da un vettore i cui elementi contengono l'indice del padre Esempio: 0 a T 1 b a 1 e b 2 c 2 d 3 f c e d f g 3 g © Alberto Montresor 16 di seguito una semplice realizzazione, tralasciando per semplicità di memorizzare i valori dei Realizzazione con vettore dei padri nodi. T REE integer[ ] p % Vettore dei padri % Costruisce una “foresta” con n nodi isolati Tree(integer n) p ← new integer[1 . . . n] for i ← 1 to n do p[i] ← 0 % Restituisce il padre del nodo i; restituisce 0 se i è radice integer parent(integer i) return p[i] % Rende il nodo i un figlio del nodo j setParent(integer i, integer j) p[i] ← j 5.5 Alberi binari © Alberto Montresor 17 Alberi binari ✦ ✦ Definizione ✦ Un albero binario è un albero ordinato in cui ogni nodo ha al più due figli e ✦ si fa distinzione tra il figlio sinistro ed il figlio destro di un nodo. Nota: ✦ due alberi T e U aventi gli stessi nodi, gli stessi figli per ogni nodo e la stessa radice, sono CAPITOLO 5. distinti ALBERI qualora un nodo u sia designato come figlio sinistro di 91un nodo v in T e come figlio destro del medesimo nodo in U livello 1 1 2 T 1 4 3 T 0 2 5 5 © Alberto Montresor 2 4 3 1 1 4 3 5 2 2 3 T 3 18 Alberi binari Figlio sinistro Radice del sottoalbero sinistro Figlio destro Radice del sottoalbero destro Radice j.parent() Padre del nodo j (e k) Sottoalbero sinistro Sottoalbero destro a j k a.left() © Alberto Montresor a.right() 19 figli destro e sinistro, che sostituiscono le operazioni per leggere e scrivere il primo figlio e i Alberisuccessivi. binari: specifica fratelli T REE % Restituisce il figlio sinistro (destro) di questo nodo; restituisce nil se assente T REE left() T REE right() % Inserisce il sottoalbero t come figlio sinistro (destro) di questo nodo insertLeft(T REE t) precondition: t.parent = nil insertRight(T REE t) precondition: t.parent = nil % Distrugge il sottoalbero sinistro (destro) di questo nodo deleteLeft() deleteRight() Una realizzazione ragionevole fa uso di puntatori, come nel caso degli alberi ordinati, i cui campi child e sibling sono sostituiti da left e right. La complessità di tutte le operazioni è O(1), con esclusione di quella di cancellazione, che agendo ricorsivamente può avere complessità Per motivi di spazio, le operazioni parent(), left(), right(), read() e write() non sono20 ©O(n). Alberto Montresor Alberi binari: realizzazione / / Padre Figlio Figlio Sinistro Destro © Alberto Montresor / / / / / / / / Nodo 21 mostrate; semplicemente, restituiscono il valore della variabile corrispondente. Alberi binari: realizzazione T REE Tree(I TEM v) T REE t = new T REE t.parent ← nil t.left ← t.right ← nil t.value ← v return t insertLeft(T REE T ) T.parent ← this left ← T insertRight(T REE T ) T.parent ← this right ← T T REE deleteLeft() if left �= nil then left.deleteLeft() left.deleteRight() delete left left ← nil deleteRight() if right �= nil then right.deleteLeft() right.deleteRight() delete right right ← nil Per motivi di spazio, le operazioni parent(), left(), right(), read() e write() non sono mostrate; semplicemente, restituiscono il valore della variabile corrispondente. © Alberto Montresor 22 esaminando ilprofondità nodo t solo Alberi binari: visite in nella riga (2) o in (3), rispettivam visitaProfondità(T REE t) (1) (2) (3) if t �= nil then esame “anticipato” del nodo radice di t visitaProfondità(t.left()) esame “simmetrico” del nodo radice di t visitaProfondità(t.right()) esame “posticipato” del nodo radice di t 5.6 Altre realizzazioni © Alberto Montresor 23 Figura 5.7: Alberi ordinati e alberi binari. Limite inferiore complessità ordinamento ✦ ✦ Esempio 5.7 (Albero delle scelte). Le possibili sequenze di confronti (a due alt fettuatedi da ordinamento un algoritmo per risolvere un problema possono essere visualizzate co Albero delle scelte in algoritmi binario, detto albero delle scelte. In tale albero, i nodi con figli rappresentano ✦ Sequenze di confronti (a dati delalternative) problema, le coppie padri-figli i risultati confronti, e le foglie soluzi due rappresentabile comedeialbero binario del problema. Un percorso radice-foglia rappresenta la sequenza di confronti e ✦ Nodi interni → confronti, foglie → soluzioni del problema individuare una soluzione e il livello massimo di una foglia dà il numero di confro nel caso pessimo. Un albero delle scelte che minimizzi il livello massimo delle fo ✦ Percorso radice-foglia: insieme di confronti per individuare una soluzione con tale valore una limitazione inferiore al numero di decisioni che ogni algoritmo il problema deve effettuare nel caso peggiore. Limite inferiore ordinamento ✦ ✦ ✦ ✦ Sia n la dimensione del vettore Numero di possibili soluzioni: n! Altezza minima albero: log2 n! ≥ n/2 log2 n/2 © Alberto Montresor no b<c si c<b no si a<c a,b,c Da cui deriva che qualunque algoritmo di ordinamento richiede Ω(n log n) confronti a<b si si a,c,b no c,b,a no c,a,b a<c si b,a,c no b,c,a Figura 5.8: Albero delle scelte per l’ordinamento di tre numeri. La Fig. 5.8 illustra un albero delle scelte per il problema di ordinare tre nu Poiché un qualsiasi algoritmo di ordinamento di n numeri deve fare una sequen 24 Semplici esercizi basati su visite ✦ Es. 5.1 - Dato un albero radicato T, calcolare la sua altezza ✦ Dato un albero radicato T, calcolare il numero totale di nodi ✦ Dato un albero radicato T, stampare tutti i nodi a profondità h © Alberto Montresor 25