Dipartimento di
Elettronica,
Informazione e
Bioingegneria
API 2013/4
Alberi
@ G. Gini 2013
Alberi
• Alberi binari
ƒ Rappresentazioni
ƒ Proprietà
• Alberi n-ari
• attraversamento
• Alberi binari di ricerca
ƒ Bilanciamento
G. Gini 2013/4
Famiglia - 1700 circa
G. Gini 2013/4
The art of computer programming
• Donald Knuth, 1968-1973
• Enciclopedia dell’informatica in 7 volumi
• TeX
"Beware of bugs in the above code; I have only proved it correct,
not tried it."
G. Gini 2013/4
albero
L’albero e’ un tipo astratto di dati usato
per rappresentare relazioni
gerarchiche.
- struttura del file system
- -albero genealogico
- organigramma
- albero di decisione
- ….
G. Gini 2013/4
Albero sintattico
Input: s=6*8+((2+42)*(5+12)+987*7*123+15*54)
Output:
exp
term
fact
term
fact
fact
exp
term
6
*
8
+
(
(
term
fact
fact
exp
exp
term
term
term
term
fact
fact
fact
fact
5
+ 12
2
+ 42
)
*
(
)
fact
fact
+ 978 *
7
Osservazione: vale anche per il linguaggio naturale
G. Gini 2013/4
term
fact
fact
* 123 + 15
fact
*
54
)
parsing di espressioni
notazione algebrica normale (INORDER)
(9+7)*(3-(4+1))
notazione polacca inversa - postfissa(POSTORDER)
97+341+-*
G. Gini 2013/4
Albero binario – def ricorsiva
Un albero è vuoto oppure è un nodo con due figli, sinistro e destro,
che sono a loro volta alberi binari.
<albero-binario> ::= NIL |
<nodo> <albero-binario><albero-binario>
Rappresentazione grafica: ogni nodo dell'albero contiene un
elemento dell'insieme e due frecce, a figlio sinistro e a figlio
destro.
B
A
D
C
G. Gini 2013/4
primitive sugli alberi binari
•
•
•
•
•
•
1. vuoto? (cioè NULL)
2. radice
3. sinistro
4. destro
5. aggiungi-nodo
6. rimuovi-nodo
Notazioni
Nodo, arco
Figlio, discendente, genitore,..
Radice, foglia, ramo
G. Gini 2013/4
Es: contare le foglie
contafoglie(x) - per x vuoto è zero, altrimenti è la somma delle
foglie del sottoalbero sinistro e del sottoalbero destro.
-------------------------------------int contafoglie ( tree-item x)
--------------------------------------if vuoto? (x) then return 0
if foglia(x) then return 1
else return (contafoglie (sinistro(x)) +
(contafoglie (destro(x))
Funzione ausiliaria
-------------------------------------boolean foglia ( tree-item x)
--------------------------------------if vuoto? (x) then return false
if vuoto?(sinistro(x)) AND vuoto?(destro(x))
then return true
G. Gini 2013/4
Es: conta foglie (in C)
int conta-f ( tree t ) {
if ( t == NULL ) /*se vuoto*/
return 0;
if ( t->left == NULL && t->right == NULL); /*se foglia*/
return 1;
return conta-f( t->right ) + conta-f(t->left);
}
G. Gini 2013/4
Profondità e altezza
Profondità = il numero di livelli
dell’albero
ƒ A ha profondità 0.
ƒ B e C sono a livello 1.
Altezza =
massima profondità + 1
ƒ L’albero ha altezza 4.
0
1
2
3
4
altezza
G. Gini 2013/4
Alcune definizioni
• Albero binario pieno (Full binary tree) - ogni nodo è una
foglia oppure un nodo interno con 2 figli non vuoti
• Albero binario completo (Complete binary tree) - se
l’altezza dell’albero è d, allora tutti i nodi tranne
eventualmente quelli al livello d sono full.
G. Gini 2013/4
Full Binary Tree
TEOREMA: il numero di foglie in un full binary tree non vuoto è il
numero dei nodi interni +1
•
Dimostrazione (per induzione matematica):
•
Caso base: un full binary tree con 1 nodo interno ha due foglie
•
Ipotesi induttiva: assumiamo che un full binary tree T con n-1
nodi interni ha n foglie
Passo induttivo: dato l’albero full binary tree T con n nodi interni,
considera il nodo interno I con due figli foglia.
ƒ Togli i figli di I e ottieni l’albero T’.
•
ƒ Per ipotesi induttiva, T’ è un full binary tree con n foglie.
ƒ Riaggiungi i figli di I. Il numero dei nodi interni ora aumenta
di 1 e diventa n. Il numero di foglie pure esso aumenta di 1.
G. Gini 2013/4
Rappresentazioni per alberi
Anche per gli alberi come per le liste si possono avere 2
rappresentazioni:
1. mediante array
2. a puntatori
Per rappresentare un albero binario di profondità n
basta un array in cui riservare memoria per ogni
nodo; nel caso di alberi sbilanciati i nodi mancanti
avranno un valore di default (-1).
7
/\
3 9
/\ \
1 5 11
7
G. Gini 2013/4
3
9
1
5
-1
11
Dimensionare l’array 1D
profondità = il numero di livelli dell’albero, ovvero il numero di
archi da attraversare per andare dalla radice alla foglia più
lontana (nell’esempio 2)
7
/ \
3
9
/ \
\
1
5
11
Un albero binario pieno (full) di profondità n ha:
1 nodo radice + 2 nodi primo livello + 4 nodi di secondo livello +...
Quindi il numero dei nodi è: 20+21+…+2n =
n
Σ2
i
i=0
G. Gini 2013/4
Implementazione in array 2D
posizione
0
1
2
3
4
5
6
7
8
9
10
11
genitore
--
0
0
1
1
2
2
3
3
4
4
5
Figlio sinistro
1
3
5
7
9
11
--
--
--
--
--
--
Figlio destro
2
4
6
8
10
--
--
--
--
--
--
--
Fratello sinistro
--
--
1
--
3
--
5
--
7
--
9
--
Fratello destro
--
2
--
4
--
6
--
8
--
10
--
--
G. Gini 2013/4
Rappresentazione a puntatori
Vediamo l’albero binario come una lista di scatole di tre elementi:
l’informazione, il puntatore al sottoalbero sinistro, il puntatore al
sottoalbero destro.
typedef struct Nodo {
Tipo data;
struct Nodo *left;
struct Nodo *right;
} nodo;
B
A
D
typedef nodo * tree;
C
G. Gini 2013/4
nota
• Le foglie sono implementate come i nodi interni,
quindi si spreca spazio per i puntatori nulli.
G. Gini 2013/4
Ricerca elemento
Restituisce NULL se non trova nell’albero t il dato d, altrimenti
restituisce il puntatore al primo nodo che lo contiene
visita t in preordine sinistro
--------------------------------tree trova ( tree t, d)
-------------------------------if null(t) then return NULL
if ( t->dato = d ) then return t; /*trovato*/
return (trova( t->left, d) || trova( t->right, d ))
G. Gini 2013/4
ricerca con assegnamenti (in C)
tree trova ( tree t, int d) {
tree temp;
if ( t == NULL) return NULL;
if ( t->dato == d ) return t;
temp = trova( t->left, d );
if ( temp == NULL )
return trova( t->right, d );
else
return temp;
}
G. Gini 2013/4
Es: calcolo della altezza
int height ( tree t ) {
if (t == NULL) return 0;
return max ( heigth( t->left), heigth( t>right ))
+ 1;
}
int max ( int a, int b ) {
if (a >b) return a;
else return b; }
G. Gini 2013/4
Es: conteggio dei nodi - in C
int contaNodi ( tree t )
{
if ( t == NULL )
return 0;
else
return (contaNodi(t->left)
+ contaNodi(t->right)
+ 1); /* c’è anche il nodo corrente */
}
G. Gini 2013/4
Es: conta i nodi non-foglia
int interni ( tree t ) {
if ( t == NULL ) return 0;
if ( t->left == NULL && t->right == NULL)) return 0;
return 1 + interni( t->right ) + interni(t->left);
}
oppure…
int interni2 ( tree t ) {
return contaNodi( t ) – conta-f( t );
}
Si noti che la seconda versione scandisce due volte l'intero albero,
impiegando circa il doppio del tempo (e lo stesso spazio in
memoria)
G. Gini 2013/4
Es: numero di nodi su livelli pari
Consideriamo che il livello della radice, cioè 0, sia pari .
Un albero col solo nodo radice ha 1 livello.
int evenCount ( tree t ) {
if ( t == NULL ) return 0;
if ( t->left == NULL ) && (t->right==NULL) return 1;
return evenCount( t->left->left) +
evenCount( t->left->right ) +
evenCount( t->right->left ) +
evenCount( t->right->right ) +
1;
}
G. Gini 2013/4
… oppure con assegnamenti
int evenCount ( tree t ) {
int n = 1; /* Il nodo corrente è sempre su livello pari, per
ipotesi induttiva*/
if ( t == NULL ) return 0;
if ( t->left != NULL ) /* ci sono nodi a sinistra*/
n += evenCount( t->left->left ) + evenCount( t->left>right );
if ( t->right != NULL ) */ ci sono nodi a destra*/
n += evenCount( t->right->left ) + evenCount( t->right>right );
return n;
}
G. Gini 2013/4
Es: numero di nodi su livelli dispari
int oddCount ( tree t, int level ) {
int n;
if ( t == NULL ) return 0;
n = oddCount( t->left, level+1 ) + oddCount( t->right,
level+1 );
if ( level%2 == 1 )
/* == 0 Æ per evenCount */
n++;
return n;
}
oppure
oddCount (t) = contanodi (t) – evenCount(t)
G. Gini 2013/4
… numero di nodi su livelli dispari
Si può vedere come il calcolo del numero dei nodi di
livello pari partendo dal livello 1 invece che 0
int oddCount ( tree t) {
if ( t == NULL ) return 0; /* vuoto*/
return evenCount ( t->left ) + evenCount (t->right)
}
G. Gini 2013/4
Albero n-ario
ogni nodo ha un numero arbitrario di figli
• Si usa ad esempio per rappresentare tassonomie e
organizzazioni gerarchiche
definito ricorsivamente:
<albero> =ε | nodo seguito da
un numero finito di alberi
G. Gini 2013/4
Conversione in albero binario
•
•
Il figlio di sinistra diventa sottoalbero sinistro;
il sottoalbero destro contiene il primo fratello verso
destra
G. Gini 2013/4
Implementazioni sequenziali ad hoc
Lista semplice con i nodi nell’ordine di visita (ad
esempio pre-order)
•
•
•
•
Risparmio di spazio ma solo accesso sequenziale
Occorre conservare la struttura dell’albero per poterlo
ricostruire
esempi:
ƒ se l’albero è full, marca i nodi come foglie o come
interni
A’B’/DC’E’G/F’HI
usa un simbolo per marcare i NULL
AB/D//CEG///FH//I//
G. Gini 2013/4
Rappresentare solo foglie e struttura
•
•
le liste in LISP sono alberi con l’informazione (i simboli atomici)
memorizzata nelle foglie.
la lista (a (b c) d (e f g)) corrisponde all’albero seguente
• In LISP è memorizzato
come albero binario
G. Gini 2013/4
con diverso numero di puntatori
Ingestibile
G. Gini 2013/4
Rappresentazione con 2 puntatori
A
B
D
E
F
G
Il nodo è una struttura con 3
campi:
1. informazione,
2. puntatore al primo figlio,
C
3. puntatore al fratello a
destra
H
I
A
null
Sottoalbero B
B
G. Gini 2013/4
Sottoalbero C
C
null
Realizzazione con 3 puntatori
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
Padre
Nodo
G. Gini 2013/4
Primo
Figlio Fratello
Attraversamento di albero
• Un processo per visitare i nodi di un albero in un
dato ordine è detto attraversamento (tree
traversal)
• Ogni attraversamento è una enumerazione dei
nodi dell’albero
G. Gini 2013/4
Algoritmi di visita degli alberi
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, pre, post-ordine
In ampiezza (breadth-first search, a ventaglio,
BFS)
✦A livelli, partendo dalla radice
✦
G. Gini 2013/4
Modi di visita – albero binario
• Pre-ordine, o pre-visita, o anticipato: visita ogni
nodo prima di visitare i suoi figli
• Post-ordine, o post-visita, o posticipato: visita
ogni nodo dopo aver visitato i suoi figli
• In-ordine, o simmetrico: visita il sottoalbero di
sinistra, quindi il nodo, poi il sottoalbero di destra
Si estende per albero n-ario
G. Gini 2013/4
Esempi visita df
Previsita
Postvisita
Simmetrica
G. Gini 2013/4
df - ordine anticipato - preorder
T
a
b
c
e
d
f
g
a b c d e f g
void preorder (tree rt)
{
if (rt == NULL)
return; // Empty
visit(rt);
preorder(rt->left);
preorder(rt->right);
}
G. Gini 2013/4
df - ordine posticipato - postorder
T
a
b
c
e
d
f
g
c d b f g e a
void postorder(tree rt)
{ if (rt == NULL) return; // Empty
postorder(rt->left);
postorder(rt->right);
visit(rt);
}
G. Gini 2013/4
df - ordine simmetrico - inorder
T
a
b
c
void inorder(tree rt)
{
if (rt == NULL)
return; // Empty
inorder(rt->left);
visit(rt);
inorder(rt->right);
}
G. Gini 2013/4
e
d
f
g
c b d a f e g
in ampiezza
T
a
b
c
e
d
f
g
Sequenza: a b e c d f g
G. Gini 2013/4
Albero binario – programma in C
typedef struct Tree {
int data;
struct Tree *left;
struct Tree *right;
} TREE;
void insertNum(int i);
void orderedInsert(TREE *, int);
void deepFirstVisit(TREE *t);
void breadthFirstVisit(TREE *t);
int search(TREE *t, int i) ;
TREE *root;
G. Gini 2013/4
void main() {
int i;
do {
printf("Inserire un intero (-1 = fine):");
scanf("%d",&i);
if(i==-1) break;
insertNum(i);
} while(i!=-1);
deepFirstVisit(root);
printf("\n");
breadthFirstVisit(root);
printf("\nInserire il valore da cercare: ");
scanf("%d",&i);
if(search(root,i))
printf("Trovato\n");
else printf("Niente da fare!\n");
}
G. Gini 2013/4
void insertNum(int i) {
if(root==NULL) {
root=(TREE *) malloc(sizeof(TREE));
root->data=i;
root->left=NULL;
root->right=NULL;
} else orderedInsert(root,i);
}
int search(TREE *t, int numero) {
if(t==NULL)
return 0;
if(numero==t->data)
return 1;
if(numero > t->data)
return search(t->right,numero);
else
return search(t->left,numero);
}
G. Gini 2013/4
void orderedInsert(TREE *nodo, int numero) {
TREE *nuovo;
if(numero == nodo->data) return;
if(numero > nodo->data) {
if(nodo->right == NULL) {
nuovo=(TREE *) malloc(sizeof(TREE));
nuovo->data=numero;
nuovo->left=NULL;
nuovo->right=NULL;
nodo->right=nuovo;
} else orderedInsert(nodo->right, numero);
} else {
if(nodo->left == NULL) {
nuovo=(TREE *) malloc(sizeof(TREE));
nuovo->data=numero;
nuovo->left=NULL;
nuovo->right=NULL;
nodo->left=nuovo;
} else orderedInsert(nodo->left, numero);
}}
G. Gini 2013/4
void deepFirstVisit(TREE *t) {
if(t==NULL) return;
deepFirstVisit(t->left);
deepFirstVisit(t->right);
printf("%d - ",t->data);
}
void breadthFirstVisit(TREE *t) {
if(t==NULL) return;
printf("%d - ",t->data);
breadthFirstVisit(t->left);
breadthFirstVisit(t->right);
}
G. Gini 2013/4
= postorder
= preorder
Alberi adatti per applicazioni di
data base, di dizionari, etc
G. Gini 2013/4
Alberi binari per rappresentare un insieme
ordinato
I valori nel sottoalbero di sinistra sono minori di quello
del padre
ƒ I valori nel sottoalbero di destra sono maggiori di
quello del padre
ƒ
47
25
11
7
17
77
43
31 44
G. Gini 2013/4
65
68
93
Esempi
rappresentare con alberi binari diversi l’insieme ordinato
dei numeri dispari da 1 a 11.
7
/ \
3
9
/ \
\
1
5
11
3
/ \
1
7
/ \
5
9
\
5
/ \
3
9
/
/ \
1
7
11
11
G. Gini 2013/4
Ricerca in albero binario ordinato
Ricerca
Ricerca del
del valore
valore 44
La
La radice
radice contiene
contiene 66
44 << 66
44 sta
sta nel
nel sottoalbero
sottoalbero
sinistro
sinistro di
di 66
T
66
22
11
88
44
33
G. Gini 2013/4
Ricerca
Ricerca del
del valore
valore 44
44 >> 22
44 sta
sta nel
nel sottoalbero
sottoalbero
destro
destro di
di 22
T
66
22
11
88
44
33
G. Gini 2013/4
Ricerca
Ricerca del
del valore
valore 44
44 == 44
trovato!
trovato!
T
66
22
11
88
44
33
G. Gini 2013/4
Pseudocodice per ricerca
--------------------------------------------------------boolean element-of?(item x , tree-item tree)
--------------------------------------------------------if vuoto?(tree) then return false
if x = radice(tree) then return true
if x < radice(tree) then return
element-of(x, sinistro(tree))
if x > radice(tree) then return
element-of(x, destro(tree))
G. Gini 2013/4
Ricerca in C – come predicato
typedef struct Nodo {
Tipo data;
struct Nodo *left;
struct Nodo *right;
} nodo;
typedef nodo * tree;
int searchItem ( Tipo item , tree node) {
if ( node == NULL) return 0;
If (item == node -> data ) return 1;
if ( item < node -> data )
return searchItem ( item , node -> left );
else return searchItem ( item , node -> right );
}
G. Gini 2013/4
Vantaggi alberi binari ordinati
Per vedere se un elemento x è contenuto basta
confrontare x con radice: se x è minore basta cercare
nel sottoalbero sinistro, se maggiore nel destro.
Quanto è vantaggioso?
Se l'albero è bilanciato i sottoalberi conterranno la metà
degli elementi, quindi ad ogni passo dimezziamo il
numero dei nodi da esaminare.
Il numero dei passi crescerà quindi come log2 n.
G. Gini 2013/4
Dizionari
Dizionario
✦Insieme dinamico che implementa le seguenti
funzionalità
✦
•
•
•
Cerca un item
Inserisci un item
Cancella un item
Come implementarlo?
✦Array (ordinato)
✦Lista (non ordinata)
✦Albero binario di ricerca
✦
G. Gini 2013/4
Alberi binari di ricerca (ABR)
✦
✦
Definizione
✦
Ogni nodo u contiene una chiave
u.key associata ad un valore
u.value
✦
Le chiavi appartengono ad un
insieme totalmente ordinato
Proprietà
1.
Le chiavi dei nodi del sottoalbero
sinistro di u sono minori di u.key
2.
Le chiavi dei nodi del sottoalbero
destro di u sono maggiori di u.key
3.
Tutti nodi i del sottoalbero sinistro
di u sono tali che per tutti nodi r del
sottoalbero destro di u, l.key <=
r.key
G. Gini 2013/4
Alberi binari di ricerca (ABR)
Proprietà di ricerca
✦Le proprietà 1. e 2. permettono di realizzare un
algoritmo di ricerca dicotomica
✦
Proprietà di ordine
✦Come visitare l'albero per ottenere la lista ordinata
dei valori? Depth first inorder
✦
implementazione
✦Ogni nodo deve mantenere
✦
Figlio sinistro, destro
✦Padre
✦Chiave
✦Valore
✦
G. Gini 2013/4
Padre
Key, Value
Figlio
Figlio
sinistro destro
Ricerca - chiave
---------------------------------------------------------tree ricerca (item x , tree-item tree)
--------------------------------------------------------if vuoto?(tree) then return NULL
if x= tree.key then return tree
if x < tree.key then return
ricerca (x, left(tree))
if x > tree.key then return
ricerca (x, right (tree))
G. Gini 2013/4
Ricerca del minimo e del massimo
Sottoalbero con
radice 2:
Sottoalbero con
radice 8:
T
- minimo 1
- massimo 4
- minimo 8
- massimo 15
66
22
88
44
11
12
12
4
99
33
G. Gini 2013/4
15
15
Ricerca minimo e massimo
L’elemento minimo è quello più a sinistra
---------------------------------------item MINIMUM (tree-item x)
--------------------------------------if x.left = NULL then return x
return MINIMUM (x.left)
L’elemento massimo quello più a destra
----------------------------------------item MAXIMUM (tree-item x)
----------------------------------------if x.right = NULL then return x
return MAXIMUM (x.right)
G. Gini 2013/4
… in forma iterativa
Montresor usa notazione con .
G. Gini 2013/4
Ricerca successore/predecessore
Definizione
✦Il successore di un nodo u è il
più piccolo nodo maggiore di u
✦
T
6
u
PRIMO CASO
✦u ha un figlio destro
✦Il successore u' è il minimo
del sottoalbero destro di u
✦
2
1
12
4
Esempio:
successore di 2 è 3
✦
3
u'
G. Gini 2013/4
8
9
15
Ricerca successore/predecessore
T
u'
SECONDO CASO
✦u non ha un figlio destro
✦Il successore è il primo avo
u' tale per cui u sta nel
sottoalbero sinistro di u'
✦
66
22
11
Esempio:
successore di 4 è 6
✦
88
12
12
33
44
u
G. Gini 2013/4
99
15
15
Ricerca successore/predecessore
G. Gini 2013/4
Ricerca: costo computazionale
Le operazioni di ricerca
sono
confinate ai nodi
posizionati
lungo un singolo percorso
dalla radice ad una foglia
✦Tempo di ricerca:
dipende dall’altezza
✦caso pessimo?
✦
• L'albero è una sequenza
lineare – n passi
•caso ottimo?
• albero è completo, o
bilanciato. L'altezza
dell'albero con n nodi è
log n
G. Gini 2013/4
h = altezza dell’albero
Inserimento e rimozione
• Quando si inserisce o si rimuove un elemento la
struttura dell’albero cambia.
• L’albero modificato deve mantenere le proprietà di
un albero binario di ricerca.
• L’inserimento risulta essere un’operazione immediata.
• La rimozione di un elemento è più complicata, proprio
perché bisogna essere certi che l’albero rimanga un
albero binario di ricerca.
• Dopo un certo numero di operazioni un albero si può
sbilanciare
G. Gini 2013/4
Inserimento
5 < 6
Inserimento chiave 5
Dovrà essere nel
sottoalbero sinistro
Si inserisce sempre
come foglia
5 > 2
T
5 > 4
66
22
88
4
4
11
12
12
4
33
G. Gini 2013/4
55
99
15
15
Inserimento
Inserire z con key = 14
9
5
15
4
2
1
7
6
16
12
8
11
3
13 y
z 14
G. Gini 2013/4
19
18
21
Inserimento - esempio
Inserire la sequenza <5, 4, 7, 2, 6, 8>
5
5
5
4
5
4
2
4
5
7
5
4
2
7
7
6
G. Gini 2013/4
4
2
7
6
8
Inserimento - esempio
Inserire la sequenza <5, 4, 7, 2, 6, 8>
5
4
2
7
6
G. Gini 2013/4
8
Inserimento - esempio
Inserire la sequenza <8, 7, 6, 4, 5, 2>
8
7
6
L’inserimento può portare
ad avere alberi sbilanciati
4
2
La struttura dell’albero
risulta diversa a seconda
della sequenza di
inserimento!
5
G. Gini 2013/4
Inserimento in C
void orderedInsert(TREE *nodo, int numero) {
TREE *nuovo;
if(numero == nodo->data) return;
if(numero > nodo->data) {
if(nodo->right == NULL) {
nuovo=(TREE *) malloc(sizeof(TREE));
nuovo->data=numero;
nuovo->left=NULL;
nuovo->right=NULL;
nodo->right=nuovo;
} else orderedInsert(nodo->right, numero);
} else {
if(nodo->left == NULL) {
nuovo=(TREE *) malloc(sizeof(TREE));
nuovo->data=numero;
nuovo->left=NULL;
nuovo->right=NULL;
nodo->left=nuovo;
} else orderedInsert(nodo->left, numero);
}}
G. Gini 2013/4
inserimento in C
tree orderedInsert(tree nodo, int numero) {
tree *nuovo;/* ritorna il puntatore all’albero*/
if(numero == nodo->data) return nodo;
if(numero > nodo->data) { /* va inserito a destra
if(nodo->right == NULL) {/* non c’è figlio destro*/
nuovo=malloc(sizeof(tree));
nuovo->data =numero;
nuovo->left =NULL;
nuovo->right =NULL;
nodo->right =nuovo;
return nodo;
} else return orderedInsert(nodo->right, numero);
} else { /* va inserito a sinistra*/
if(nodo->left == NULL) {
nuovo=malloc(sizeof(tree));
nuovo->data=numero;
nuovo->left=NULL;
nuovo->right=NULL;
nodo->left=nuovo;
return nodo;
} else return orderedInsert(nodo->left, numero);
}}
G. Gini 2013/4
Inserimento (Cormen) con notazione funzionale
scendere nell'albero fino a che non si raggiunge il posto in cui il nuovo
elemento deve essere inserito, ed aggiungerlo come foglia
TREE-INSERT(T, z)
1 y := NIL
//padre
2 x := root(T)
//figlio
3 while x ≠ NIL
//cerca dove inserire
4
do y := x
//memorizza il padre
5
if key (z)< key(x)
6
then x := left (x)
//scende a sinistra
7
else x := right(x)
//scende a destra secondo il confronto
8 z.p := y
// inserisci z come figlio di y
9 if y = NIL
10
then root(T) := z
//l'albero T e' vuoto e z diventa la radice
11
else if key(z) < key(y)
12
then left(y) := z
13
else right(y) := z
G. Gini 2013/4
Complessità inserimento
Per inserire z si usano due puntatori y e x. Il
puntatore a x scende l’albero , y punta al padre di x.
Nel ciclo while i due puntatori (x e y) scendono
l’albero. x scende al figlio sinistro o destro a seconda
dell’esito del confronto di key[z] con key[x]. Ci si ferma
quando x = NIL e x occupa la posizione in cui z verrà
inserito.
I passi necessari per l’inserimento sono non più del
cammino massimo tra la radice e una foglia (cioè
l’altezza).
Nel caso ricorsivo, il numero delle chiamate ricorsive
dipende da h.
G. Gini 2013/4
cancellazione
Cancellare z, che ha chiave 14
Caso 1: z senza figli (foglia) – si rimuove
9
5
15
4
2
1
7
6
16
12
8
11
3
z 14
G. Gini 2013/4
19
13
18
21
cancellazione
Caso 2: cancellare z (che ha chiave 16) con 1 figlio – si
riattacca il figlio al padre di z
9
15
5
4
2
1
7
6
z 16
12
8
11
3
19
13
14
G. Gini 2013/4
18
21
cancellazione
Caso 3: cancellare z (che ha chiave 12) con 2 figli.
9
15
5
4
2
1
7
6
3
16
z 12
8
11
y
13
Successore di z:
Viene rimosso e
va al posto di z.
G. Gini 2013/4
19
14
18
21
cancellazione
Caso 3: z con 2 figli.
9
5
15
4
2
1
7
6
16
y 13
8
11
19
14
3
18
z 12
G. Gini 2013/4
21
cancellazione
tre casi:
1. Se z non ha figli, allora si modifica puntatore a z, che
punta non più a z ma a NIL.
2. Se z ha un unico figlio, allora si taglia fuori z dall’albero,
facendo puntare il puntatore a z all’unico figlio di z.
3. Se z ha due figli, allora si individua il successore, ossia il
minimo del suo sottoalbero destro.
1. Il successore y ha nessun figlio o 1 figlio. Quindi y
prende il posto di z, riconducendosi al caso 1 e 2.
Alla fine i dati in y vengono copiati in z.
G. Gini 2013/4
Passi necessari
• L’operazione di rimozione può richiede che
SUCCESSOR(z) venga eseguito.
• SUCCESSOR(z) richiede tempo che dipende da h
anche TREE-INSERT() e TREE-DELETE() richiedono
tempo che dipende dall’altezza
G. Gini 2013/4
Cancellazione pseudocodice
TREE-DELETE(T, z)
1 if left (z)= NIL or right(z) = NIL
2
then y := z
// z ha 0 o 1 figlio, copialo in y
3
else y := TREE-SUCCESSOR(z) // z ha 2 figli, trova
succ(z)
4 if left (y)≠ NIL
// y nodo da eliminare
5
then x := left(y)
// x figlio di y
6
else x := right (y)
G. Gini 2013/4
Cancellazione -continua
7 if x ≠ NIL then
// se y ha il figlio
8
then p(x) := p(y)
// taglia fuori y e aggiorna il padre
9 if p (y)= NIL
10
then root (T):= x
// se y è la radice, x diventa radice
11
else if y = left(p(y)) // collega x al padre di y
12
then left(p(y)) := x
13
else right(p(y)) := x
14 if y ≠ z then
// se y è il successore
15
key (z):= key(y)
// ricopia i dati di y in z
16 return y
G. Gini 2013/4
Cancellazione –correttezza
Caso 1 - nessun figlio
✦Eliminare foglie non cambia la proprietà di ordine dei nodi
rimanenti
✦
Caso 2 - solo un figlio (destro o sinistro)
✦Se u è il figlio destro (sinistro) di p, tutti i i valori nel
sottoalbero destro sono maggiori (minori) di p
✦Quindi f può essere attaccato come figlio destro (sinistro)
di p al posto di u
✦
Caso 3 - due figli
✦Il successore s
✦
è sicuramente maggiore di tutti i nodi del sottoalbero sinistro di u
✦è sicuramente minore di tutti i nodi del sottoalbero destro di u
✦
Quindi può essere sostituito a u
✦
G. Gini 2013/4
Modifica: costo computazionale
Le operazioni di modifica
sono confinate ai nodi
posizionati
lungo un singolo percorso
dalla radice ad una foglia
✦In tempo di ricerca
dipende dall’altezza
dell’albero
•Caso pessimo: l'albero è
organizzato come una
sequenza lineare - n
•Caso ottimo: l'albero è
completo, o bilanciato. In
questo caso, l'altezza
dell'albero è log n
✦
G. Gini 2013/4
h = altezza dell’albero
bilanciamento
Fattore di bilanciamento
✦Il fattore di bilanciamento β(v) di un nodo v è la massima
differenza di altezza fra i sottoalberi di v
✦
Esempio: albero perfetto:
✦β(v)=0 per ogni nodo v
✦
In generale:
✦Sono necessarie tecniche per mantenere bilanciato
l'albero
✦
Vedremo alberi rosso-neri
✦
G. Gini 2013/4
Per approfondire
• Stanford Encyclopedia of philosophy
• Temi generali (Recursion, computer science, ..)
• Autori
G. Gini 2013/4
Esercizio 1
•
1.
2.
Sia data la seguente struttura per la memorizzazione di alberi binari
etichettati con numeri interi:
typedef struct nodo {
int info;
struct nodo *left, *right;
} NODO;
typedef NODO *tree;
scrivere due funzioni ricorsive
int sommaNodi(tree t), che somma i valori delle etichette
nell'albero
int cercaMax(tree t), che cerca il valore dell'etichetta massima
dell'albero.
G. Gini 2013/4
Soluzione 1
int sommaNodi(tree t) {
if (t==NULL) return 0;
return t->info+sommaNodi(t->left)+sommaNodi(t->right);}
int max(int a,int b) {
if(a>b) return a;
else return b;}
int max3(int a,int b,int c) {
return max(a,max(b,c));}
int cercaMax(tree t) {
if (t==NULL) return 0;
if (t->left==NULL && t->right==NULL) return t->info;
if(t->left==NULL)
return max(t->info, cercaMax(t->right));
if(t->right==NULL)
return max(t->info, cercaMax(t->left));
return max3(cercaMax(t->right), cercaMax(t->left), t->info);
}
G. Gini 2013/4
Esercizio 2
Un albero binario si dice isobato se tutti i cammini dalla radice alle
foglie hanno la stessa lunghezza
• Data la seguente definizione di albero
typedef struct EL { int dato;
struct EL *left;
struct EL *right; } Node;
typedef Node *tree;
codificare una funzione che riceve in input un albero e restituisce 1
se l’albero è isobato, 0 altrimenti.
G. Gini 2013/4
Soluzione 2
Uso funzione ausiliaria
int contaProfonditaSeUguale(tree t) {
int s, d;
if(t==null) return 0;
s=contaProfonditaSeUguale(t->left);
d=contaProfonditaSeUguale(t->right);
if(d==-1||s==-1)
return -1
if(d==s)
return d+1;
if(d==0)
return s+1;
if(s==0)
return d+1;
}
return -1;//d!=s
int isobato(tree t) {
if(contaProfonditaSeUguale(t)==-1)
return 0;
else
return 1;
}
G. Gini 2013/4
Esercizio 3
•
•
•
Si consideri la seguente definizione di un albero binario
typedef struct EL { int dato;
struct EL * left, right; } node;
typedef node * tree;
Definiamo un albero come "artussiano" se è composto solo da
ƒ nodi foglia
ƒ nodi con un solo figlio
ƒ nodi con due figli aventi lo stesso numero di discendenti
Codificare una funzione che riceve in input un albero e restituisce 1
se l’albero è "artussiano", 0 altrimenti. Nel risolvere il problema è
consigliato servirsi di opportune funzioni ausiliarie.
G. Gini 2013/4
Soluzione 3
int contaNodi ( tree t ) {
if ( t == NULL ) return 0;
else return (contaNodi(t->left) + contaNodi(t->right) + 1); }
int artussiano(tree t) {
if(t==NULL) return 1;
if(t->left==NULL && t->right==NULL) return 1;
if(t->left==NULL) return artussiano(t->right)
if(t->right==NULL) return artussiano(t->left)
if(contaNodi(t->left)==contaNodi(t->right) &&
artussiano(t->left) && artussiano(t->right))
return 1;
return 0;
}
G. Gini 2013/4
Esercizio 4
•
Si consideri un albero binario in cui il valore dei nodi è sempre positivo.
•
Supponiamo che percorrendo un cammino dalla radice alle foglie si totalizzi
un punteggio pari alla somma dei valori contenuti nei nodi percorsi.
•
- Scrivere una funzione maxPunti che calcola il punteggio massimo che
possiamo totalizzare percorrendo un cammino dalla radice alle foglie.
•
-Vogliamo percorrere l’albero dalla radice ad una foglia totalizzando
esattamente un certo punteggio. Scrivere una funzione esisteCammino che
dati un albero ed un intero k, dice se esiste un cammino dalla radice ad una
foglia che permette di totalizzare esattamente k punti.
G. Gini 2013/4
Soluzione 4
int maxPunti ( tree t ) {
int D, S;
if (t == NULL) return 0;
S = maxPunti( t->left );
D = maxPunti( t->right );
if ( S > D ) return S + t->dato;
else return D + t->dato;
}
int esisteCammino ( tree t, int k ) {
int D, S;
if (t == NULL && k==0) return 1;
if (t == NULL) return 0;
if (k – t->dato <0) return 0;
return (esisteCammino(t->left, k - t->dato) ||
esisteCammino(t->right, k - t->dato));
}
G. Gini 2013/4
Esercizio 5
•
•
•
Si consideri la seguente definizione di un albero binario
typedef struct EL { int dato;
struct EL * left, right; } node;
typedef node * tree;
Scrivere una funzione che riceve il puntatore alla radice di un albero e lo
scandisce interamente costruendo una lista tale che abbia tanti nodi quante
sono le foglie dell’albero e che ogni nodo della lista punti ad una diversa
foglia dell’albero. La funzione deve restituire al chiamante il puntatore
all’inizio della lista creata.
typedef struct ELLista { tree foglia;
struct ELLista * next; } nodeLista;
typedef nodeLista * Lista;
Si noti che esistono diversi modi di visitare l’albero che originano diverse
liste come risultato.
G. Gini 2013/4
Soluzione 5
void creaLista(tree t, Lista *lis) {
Lista temp;
if(t==NULL) return;
if(t->left==NULL && t->right==NULL) {
temp=*lis;
*lis=(Lista)malloc(sizeof(NodoLista));
*lis->foglia=t;
*lis->next=temp;
}
if(t->left!=NULL)
creaLista(t->left, lis);
if(t->right!=NULL)
creaLista(t->right, lis);
}
G. Gini 2013/4
Esercizio 6
•
Si definisca una struttura dati adatta a rappresentare un albero Nario. Per semplicità si consideri il caso in cui i nodi contengono,
come dati utili, dei semplici numeri interi.
•
Ogni nodo contiene, invece di una coppia di puntatori a nodi, come
nel caso degli alberi binari, una lista di puntatori a nodo. Tale lista è
una lista concatenata semplice, realizzata tramite la struttura Ramo.
typedef Nodo * Albero;
typedef struct Branch { Albero child; struct Branch * next; }
Ramo;
typedef struct Knot { int dato; struct Branch * rami; } Nodo;
•
Si progetti la funzione int conta( … ) che conta il numero di nodi di
un albero N-ario.
G. Gini 2013/4
soluzione 6
int contaNodi ( Albero t ) {
if ( t == NULL ) return 0;
else
return 1 + contaRami( t->rami );
}
int contaRami ( Ramo * b ) {
if ( b == NULL )
return 0;
else
return contaNodi( b->child ) + contaRami( b->next );
}
G. Gini 2013/4