Informatica 3 Informatica 3 LEZIONE 15: Implementazione di alberi binari - BST Lezione 15 - Modulo 1 • Modulo 1: Implementazione degli alberi binari • Modulo 2: BST Politecnico di Milano - Prof. Sara Comai Implementazione degli alberi binari 1 Introduzione Politecnico di Milano - Prof. Sara Comai 2 Implementazione (1) • Implementazione: – alberi binari basati su puntatori • discussione sullo spazio necessario – alberi binary basati su array Scelta: stessa definizione per i nodi interni e le foglie? Politecnico di Milano - Prof. Sara Comai 3 Politecnico di Milano - Prof. Sara Comai 4 1 Implementazione (2) Implementazione tramite union (1) Esempi: albero delle espressioni, albero di Huffmann, PR quadtree, ... enum Nodetype {leaf, internal}; class VarBinNode { // Generic node class public: Nodetype mytype; // Store type for node union { struct { VarBinNode* left; VarBinNode* right; Operator opx; } intl; 4x(2x+a)-c Operand var; // // // // Internal node Left child Right child Value // Leaf: Value only }; Politecnico di Milano - Prof. Sara Comai 5 Politecnico di Milano - Prof. Sara Comai Implementazione tramite union (2) 6 Implementazione tramite union (3) // Leaf constructor VarBinNode(const Operand& val) { mytype = leaf; var = val; } // Preorder traversal void traverse(VarBinNode* subroot) { if (subroot == NULL) return; // Internal node constructor VarBinNode(const Operator& op, VarBinNode* l, VarBinNode* r) { mytype = internal; intl.opx = op; intl.left = l; intl.right = r; } if (subroot->isLeaf()) cout << "Leaf: “ << subroot->var << "\n"; bool isLeaf() { return mytype == leaf; } } VarBinNode* leftchild() { return intl.left; } else { cout << "Internal: “ << subroot->intl.opx << "\n"; traverse(subroot->leftchild()); traverse(subroot->rightchild()); } VarBinNode* rightchild() { return intl.right; } }; Politecnico di Milano - Prof. Sara Comai 7 Politecnico di Milano - Prof. Sara Comai 8 2 Implementazione tramite ereditarietà (1) Implementazione tramite ereditarietà (2) class VarBinNode { // Abstract base class public: virtual bool isLeaf() = 0; }; // Internal node class IntlNode : public VarBinNode { private: class LeafNode : public VarBinNode { // Leaf private: Operand var; // Operand value public: LeafNode(const Operand& val) { var = val; } // Constructor public: VarBinNode* left; VarBinNode* right; Operator opx; // Left child // Right child // Operator value IntlNode(const Operator& op, VarBinNode* l, VarBinNode* r) { opx = op; left = l; right = r; } bool isLeaf() { return true; } bool isLeaf() { return false; } Operand value() { return var; } VarBinNode* leftchild() { return left; } }; VarBinNode* rightchild() { return right; } }; Politecnico di Milano - Prof. Sara Comai 9 Implementazione tramite ereditarietà (3) Operator value() { return opx; } Politecnico di Milano - Prof. Sara Comai 10 Ereditarietà - 2^ versione (1) class VarBinNode { // Abstract base class public: virtual bool isLeaf() = 0; virtual void trav() = 0; }; // Preorder traversal void traverse(VarBinNode *subroot) { if (subroot == NULL) return; // Empty class LeafNode : public VarBinNode { // Leaf private: Operand var; // Operand value if (subroot->isLeaf()) // Do leaf node cout << "Leaf: " << ((LeafNode *)subroot)->value() << endl; public: LeafNode(const Operand& val) { var = val; } // Constructor bool isLeaf() { return true; } Operand value() { return var; } void trav() { cout << "Leaf: " << value() << endl; } }; else { // Do internal node cout << "Internal: " << ((IntlNode *)subroot)->value() << endl; traverse((IntlNode *)subroot)->leftchild()); traverse(((IntlNode *)subroot)->rightchild()); } } Politecnico di Milano - Prof. Sara Comai 11 Politecnico di Milano - Prof. Sara Comai 12 3 Ereditarietà - 2^ versione (2) Ereditarietà - 2^ versione (3) class IntlNode : public VarBinNode { private: VarBinNode* lc; // Left child VarBinNode* rc; // Right child Operator opx; // Operator value public: IntlNode(const Operator& op, VarBinNode* l, VarBinNode* r) { opx = op; lc = l; rc = r; } bool isLeaf() { return false; } VarBinNode* left() { return lc; } VarBinNode* right() { return rc; } Operator value() { return opx; } void trav() { cout << "Internal: " << value() << endl; if (left() != NULL) left()->trav(); if (right() != NULL) right()->trav(); } }; Politecnico di Milano - Prof. Sara Comai // Preorder traversal void traverse(VarBinNode *root) { if (root != NULL) root->trav(); } 13 Differenze Politecnico di Milano - Prof. Sara Comai 14 Spazio necessario (1) • Prima versione: semplice aggiungere nuovi metodi per l’attraversamento dell’albero • Seconda versione: l’aggiunta di nuovi metodi per l’albero richiede di aggiungere dei metodi a livello di nodo • Overhead = spazio necessario per mantenere la struttura dati (spazio non utilizzato per memorizzare i dati) – dipende da diversi fattori: • nodi con stessa definizione oppure definizioni diverse per nodi interni e nodi foglia • albero esteso con puntatori al padre • albero pieno o non pieno • Prima versione: occorre considerare sempre il tipo di nodo • Seconda versione: il tipo di nodo viene determinato a tempo di esecuzione (late binding) • Prima versione: preferibile se le sottoclassi dei tipi di nodi vengono tenute nascoste alla classe albero • Seconda versione: preferibile se i nodi hanno anche vita indipendente Politecnico di Milano - Prof. Sara Comai 15 Politecnico di Milano - Prof. Sara Comai 16 4 Spazio necessario (2) Implementazione tramite array (1) Esempio: – n numero dei nodi – p spazio occupato dal puntatore – d spazio occupato dai dati Alberi completi: Tutti i nodi sono uguali e hanno i puntatori ai figli: – Spazio totale: n(2p + d) – Overhead: 2pn – Se p = d l’overhead è pari a 2p/(2p + d) = 2/3 I nodi foglia non hanno i puntatori e l’albero è pieno: n/2 (2p) n/2 (2p) + dn p p+d – Se p = d l’overhead è pari a 1/2 – Se i dati vengono memorizzati solo nelle foglie: 2p / (2p + d) per p = d dà un overhead di 2/3 Politecnico di Milano - Prof. Sara Comai 17 Implementazione tramite array (2) Padre (i) = (i-1) / 2 se i != 0 Figlio di sinistra (i) = 2i + 1 se 2i + 1 < n Figlio di destra (i) = 2i + 2 se 2i + 2 < n Fratello di sinistra (i) = i - 1 se i è pari Fratello di destra (i) = i + 1 se i è dispari ei+1<n Politecnico di Milano - Prof. Sara Comai Posizione 0 1 2 3 4 5 6 7 8 9 10 Padre -- 0 0 1 1 2 2 3 3 4 4 11 5 Figlio di sinistra 1 3 5 7 9 11 -- -- -- -- -- --- Figlio di destra 2 4 6 8 10 -- -- -- -- -- -- Fratello di sinistra -- -- 1 -- 3 -- 5 -- 7 -- 9 -- Fratello di destra -- 2 -- 4 -- 6 -- 8 -- 10 -- -- Politecnico di Milano - Prof. Sara Comai 18 Implementazione tramite cursori • Alcuni linguaggi di programmazione non dispongono di puntatori Indice Dati Sinistra Destra 1 A 2 3 2 B 0 4 3 C 5 6 4 D 0 0 5 E 0 0 6 F 0 0 ... ... ... ... MAX ... ... ... 19 Politecnico di Milano - Prof. Sara Comai 20 5 Introduzione Informatica 3 • Definizione di Binary Search Tree (BST) – albero binario che soddisfa le seguenti proprietà: • ad ogni nodo è associata una chiave • le chiavi di tutti i nodi che si trovano nel sotto-albero di sinistra di un nodo con chiave K hanno valore inferiore a K • le chiavi di tutti i nodi che si trovano nel sotto-albero di destra di un nodo con chiave K hanno valore maggiore o uguale a K Lezione 15 - Modulo 2 Binary Search Tree (BST) Politecnico di Milano - Prof. Sara Comai 21 Politecnico di Milano - Prof. Sara Comai Introduzione (2) 22 Ricerca di un elemento Input: Radice di un sotto-albero R Chiave dell’elemento da cercare K Algoritmo: – Se R ha valore di chiave pari a K la ricerca è terminata (search hit) – Se la chiave K è inferiore alla chiave del nodo radice R si prosegue la ricerca nel sotto-albero di sinistra – Se la chiave K è maggiore della chiave del nodo radice R si prosegue la ricerca nel sotto-albero di destra – Il processo continua finchè si trova la chiave oppure si arriva ad un nodo foglia – Se si raggiunge un nodo foglia senza incontrare K l’elemento non esiste nel BST (search miss) – La funzione di ordinamento è “gratuita”: l’attraversamento in ordine simmetrico (“in order”) produce come risultato l’enumerazione di tutti i nodi dal più piccolo al più grande Politecnico di Milano - Prof. Sara Comai 23 Politecnico di Milano - Prof. Sara Comai 24 6 Ricerca di un elemento (2) Ricerca di un elemento (3) Esempio di ricerca di un elemento presente nel BST: ricerca dell’elemento 7 Esempio di ricerca di un elemento non presente nel BST: ricerca dell’elemento 34 – 34 è minore di 37 --> visita del sotto-albero di sinistra – 34 è maggiore di 24 --> visita del sotto-albero di destra – 34 è maggiore di 32 - 32 è un nodo foglia --> ricerca terminata: l’elemento non è presente – 7 è minore di 37 --> visita del sotto-albero di sinistra – 7 è minore di 24 --> visita del sotto-albero di sinistra – elemento trovato Politecnico di Milano - Prof. Sara Comai 25 Politecnico di Milano - Prof. Sara Comai 26 Inserimento di un elemento già presente Inserimento di un elemento • Osservazione: • L’inserimento è una ricerca con esito negativo seguita dalla sostituzione del link NULL (di sinistra o di destra di un nodo esterno) con il puntatore al nodo da inserire • Esempio: inserimento del nodo 34: – se occorre inserire un nodo la cui chiave è già presente nel BST il nuovo elemento viene sistemato nel sotto-albero di destra del nodo già presente – Un effetto collaterale di questo modo di procedere è che i nodi con chiavi replicate non sono necessariamente contigui all’interno del BST • Esempio: inserimento di un elemento con chiave 24 – 34 è minore di 37 --> visita del sotto-albero di sinistra – 34 è maggiore di 24 --> visita del sotto-albero di destra – 32 è un nodo foglia - 34 è maggiore di 32 --> inserimento a destra 24 • Per cercare tutti gli elementi con una determinata chiave si parte dal primo nodo trovato e si procede con la ricerca nel sotto-albero di destra 34 Politecnico di Milano - Prof. Sara Comai 27 Politecnico di Milano - Prof. Sara Comai 28 7 Cancellazione di un nodo Cancellazione di un nodo (2) • Caso più semplice: eliminazione del nodo con chiave minima (nell’esempio rimozione del nodo 5) • Eliminazione di un nodo R con chiave non minima sotto-radice • Si visita l’albero partendo dalla radice fino a trovare l’elemento da cancellare R • Si visita l’albero partendo dalla radice e continuando a scendere nel sotto-albero di sinistra fino a quando non si arriva al nodo il cui puntatore di sinistra è NULL (nodo di chiave minima) • Chiamiamo questo nodo S • Per rimuovere S è sufficiente fare in modo che il puntatore a sinistra del padre di S punti al figlio destro di S Politecnico di Milano - Prof. Sara Comai – Se R non ha figli allora il padre di R dovrà puntare a NULL – Se R ha un figlio allora il padre di R dovrà puntare al figlio di R – Se R ha due figli si può adottare il seguente approccio: • si fa puntare R ad uno dei due sotto-alberi di R • si applica la funzione di inserimento per tutti i nodi dell’altro sotto-albero 29 Cancellazione di un nodo (3) Politecnico di Milano - Prof. Sara Comai 30 Cancellazione di un nodo (4) – Approccio alternativo: • si cerca un valore in uno dei sotto-alberi che possa sostituire il valore di R (preservando le proprietà del BST) • R può essere sostituito dal nodo con: – Esempio di cancellazione del nodo 37 • può essere sostituito dal nodo con: – la più piccola chiave maggiore del nodo rimosso: 40 – la più grande chiave minore del nodo rimosso: 32 – la più piccola chiave maggiore del nodo rimosso – la più grande chiave minore del nodo rimosso Politecnico di Milano - Prof. Sara Comai 31 Politecnico di Milano - Prof. Sara Comai 32 8 Cancellazione di un nodo Prestazioni dei BST • Osservazioni generali: – Per semplificare gli algoritmi di ricerca le chiavi di ricerca sono integrate nella struttura dati – Ciò richiede tipicamente un’implementazione più complicata per le operazioni di cancellazione dei nodi • Metodi di cancellazione alternativi: – cancellazione “lazy” (pigra): i nodi vengono marcati con un flag come “cancellati” ma non vengono eliminati dalla struttura dati » gli algoritmi di ricerca devono tenere in considerazione questi flag » svantaggi: eccessive cancellazioni portano a strutture dati che sprecano spazio di memoria e tempo nella ricerca » per attenuare questi svantaggi si possono effettuare ricostruzioni periodiche della struttura oppure utilizzare i nodi cancellati per futuri inserimenti Politecnico di Milano - Prof. Sara Comai 33 Prestazioni dei BST (2) • I tempi di esecuzione degli algoritmi definiti sui BST dipendono dalla forma dell’albero • Quando l’albero è perfettamente bilanciato ci sono circa log N nodi tra la radice e ciascun nodo esterno • La forma dell’albero dipende dall’ordine con il quale gli elementi vengono inseriti – Un BST di N nodi può essere costituito da una catena di nodi di altezza N – Ciò accade quando gli elementi vengono inseriti ordinati 24 32 35 – Nel caso peggiore la ricerca di un elemento in un BST con N chiavi richiede N confronti Politecnico di Milano - Prof. Sara Comai 34 BST Search • RICERCA di un elemento: template <class Key, class Elem> bool BST<Key, Elem>:: findhelp(BinNode<Elem>* subroot, const Key& K, Elem& e) const { if (subroot == NULL) return false; else if (K < subroot->val()) return findhelp(subroot->left(), K, e); else if (K > subroot->val())) return findhelp(subroot->right(), K, e); else { e = subroot->val(); return true; } } – Un albero bilanciato ha un costo pari a Θ(log n) nel caso medio – Se l’albero non è bilanciato il costo nel caso peggiore è Θ(n) • INSERIMENTO di N nodi: – Se l’albero è bilanciato ogni inserimento richiede un costo pari a Θ(log n) --> per n nodi: Θ(n log n) – Se i nodi vengono inseriti in ordine (albero sbilanciato) il costo dell’inserimento diventa Θ(n2) • VISITA DELL’ALBERO: – Costa Θ(n) indipendentemente dal bilanciamento dell’albero Politecnico di Milano - Prof. Sara Comai 35 Politecnico di Milano - Prof. Sara Comai 36 9 BST Insert Remove Minimum Value template <class Key, class Elem> BinNode<Elem>* BST<Key,Elem>:: inserthelp(BinNode<Elem>* subroot, const Elem& val) { if (subroot == NULL) // Empty: create node return new BinNodePtr<Elem>(val,NULL,NULL); if (val < subroot->val()) subroot->setLeft(inserthelp(subroot->left(), val)); else subroot->setRight( inserthelp(subroot->right(), val)); // Return subtree with node inserted return subroot; } Politecnico di Milano - Prof. Sara Comai template <class Key, class Elem> BinNode<Elem>* BST<Key, Elem>:: deletemin(BinNode<Elem>* subroot, BinNode<Elem>*& min) { if (subroot->left() == NULL) { min = subroot; return subroot->right(); } else { // Continue left subroot->setLeft( deletemin(subroot->left(), min)); return subroot; } } 37 Politecnico di Milano - Prof. Sara Comai BST Remove BST Remove (2) template <class Key, class Elem> BinNode<Elem>* BST<Key,Elem>:: removehelp(BinNode<Elem>* subroot, const Key& K, BinNode<Elem>*& t) { if (subroot == NULL) return NULL; else if (K < subroot->val()) subroot->setLeft( removehelp(subroot->left(), K, t)); else if (K > subroot->val()) subroot->setRight( removehelp(subroot->right(), K, t)); } Politecnico di Milano - Prof. Sara Comai 38 39 else { // Found it: remove it BinNode<Elem>* temp; t = subroot; if (subroot->left() == NULL) subroot = subroot->right(); else if (subroot->right() == NULL) subroot = subroot->left(); else { // Both children are non-empty subroot->setRight( deletemin(subroot->right(), temp)); Elem te = subroot->val(); subroot->setVal(temp->val()); temp->setVal(te); t = temp; } } return subroot; Politecnico di Milano - Prof. Sara Comai 40 10 Conclusioni – I BST sono semplici da implementare e sono efficienti quando l’albero è bilanciato – Se non controllata questa struttura dati tende a non essere perfettamente bilanciata, portando a peggioramenti delle prestazioni – Esistono strutture dati alternative (AVL tree, Splay tree, ecc.) che al prezzo di un maggiore costo per gli inserimenti e le cancellazioni cercano di mantenere bilanciato l’albero Politecnico di Milano - Prof. Sara Comai 41 11