LEZIONE 15: Implementazione di alberi binari

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