! ! 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? 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 t.value t.parent return t % Puntatore al padre % Puntatore al primo figlio % Puntatore al successivo fratello % Valore del nodo new T REE v t.child t.sibling % Crea un nuovo nodo nil insertChild(T REE t) t.parent this t.sibling child child t % Inserisce t prima dell’attuale primo figlio insertSibling(T REE t) t.parent this t.sibling sibling sibling t deleteChild() © Alberto Montresor % Inserisce t prima dell’attuale fratello 14 t.parent this t.sibling 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 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 left T this insertRight(T REE T ) T.parent this right T 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 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 www.xkcd.com © Alberto Montresor 24