Sei sulla pagina 1di 8

GLI ALBERI

FONDAMENTI DI INFORMATICA C STRUTTURE NON LINEARI

Si dicono in generale GRAFI


Insieme di nodi (contengono gli atomi)
Connessi da relazioni binarie (archi orientati da un nodo a
un altro)

FRANCO ZAMBONELLI Grado di ingresso di un nodo


numero di archi che entrano nel nodo
Grado di uscita di un nodo
numero di archi che partono dal nodo

GLI ALBERI
 

 

La dimensione di un grafo praticamente data dal numero di


dimensioni che servono per poterlo disegnare senza incroci
Le molecole del DNA formano un grafo tridimensionale
Le liste sono casi monodimensionali di grafi (ogni nodo ha un
arco di ingresso e uno di uscita)

Le strutture di relazione tra elementi si rappresentano come grafi

Gli Alberi 3

ALBERO BINARIO Terminologia relativa agli alberi


livello di un nodo: distanza dalla radice, espressa come numero di
archi di cui composto il cammino dalla radice al nodo; la radice
La struttura di grafo ad albero, bi-dimensionale, presenta
a livello 0;
numerosissime applicazioni e riveste un ruolo fondamentale
nell'informatica. profondit (o altezza) dell'albero: massimo livello dei nodi
dell'albero;
Ci soffermiamo sul cosiddetto albero binario: alberi pi complessi foglia: nodo senza figli;
possono essere facilmente ricavati come generalizzazione degli alberi predecessore: relazione strutturale, ottenibile applicando la
binari. Sono possibili due definizioni teoriche di albero: propriet transitiva alla relazione padre-figlio.

Definizione 1 Predecessore una relazione d'ordine parziale, cio dati due nodi
dell'albero o uno predecessore dell'altro, in quanto esiste un
Un albero un insieme finito di nodi e archi orientati. Ogni arco percorso di archi dall'uno all'altro, oppure non c' relazione.
collega il nodo padre ad un nodo figlio.
Ogni nodo ha esattamente un padre (grado di ingresso + 1) Si pu anche definire una relazione successore, inversa di
Ogni nodo ha al pi due figli (grado di uscita <= 2). predecessore.
Il nodo radice non ha padre (grado di ingresso = 0)
Il massimo numero di nodi a livello i (i 0) 2i , come si pu
Definizione Alternativa (a carattere ricorsivo) verificare per induzione:
20=1,
Albero binario un insieme finito di nodi e pu essere:
se il massimo numero di nodi a livello i 2i, poich ogni nodo al
1. insieme vuoto oppure
livello i pu avere al pi due figli, al livello i+1 il massimo
2. nodo radice + due alberi binari disgiunti, detti rispettivamente
numero di nodi 2*2i=2i+1.
sottoalbero sinistro e sottoalbero destro
Il massimo numero complessivo di nodi in un albero di
profondit k 20 + 21 + ... + 2k = 2k+1-1.
radice

albero
binario
Gli Alberi 5

Alberi Binari di Ricerca (BST)


Visite di un albero
La struttura ad albero binario si presta alla gestione di insiemi di dati
Visitare un grafo, in genere significa: su cui definita una relazione d'ordine lineare.
percorrere tutti i suoi nodi una volta e una sola alberi binari con dati ordinati sono detti Alberi Binari di Ricerca
o pi, in generale, visitare un insieme specifico di nodi (BST = Binary Search Trees)
Uso albero per ricerche con tecnica dicotomica.
Si visita un albero per visualizzare, elaborare, modificare il
contenuto informativo dei nodi. Si suppone che siano disponibili due funzioni Minore e Uguale in
grado di verificare la relazione d'ordine e l'uguaglianza fra due atomi.
La visita di un albero pu seguire uno delle seguenti modalit base:
preordine: la visita della radice seguita dalla visita dei sottoalberi Si suppone inoltre di non voler memorizzare nel BST due atomi
postordine: la visita dei sottoalberi seguita dalla visita della uguali.
radice
ordine centrale (simmetrica): la visita della radice intermedia alle Nella pratica, lalbero binario di ricerca si presta alla realizzazione
visite dei sottoalberi; della struttura astratta dizionario,
informazione memorizzata partizionata in due componenti:
La definizione ricorsiva di albero suggerisce la realizzazione delle chiave e informazione associata.
varie operazioni con programmazione ricorsiva: le posizioni dei relazione dordine e operazioni di confronto soltanto sulla base
figli di un nodo possono essere trattate come posizioni di radici di della chiave.
alberi.
void PreOrdine(Posiz N, AlbBin T){
if <albero non vuoto>
{
Visita(N); /* A */
PreOrdine(<figlio sinistro>, T): /* B */
PreOrdine(<figlio destro>, T); /* C */
}
} /* end PreOrdine */
Le altre modalit di visita si ottengono semplicemente modificando
l'ordine delle operazioni nel corpo della procedura:
B,C,A per il post-ordine e
B,A,C per l'ordine centrale.

Gli Alberi 7

Operazioni in Alberi Binari di Ricerca /* OPERAZIONI DI BST */


/* Bst.h*/
Ordinamento a carattere ricorsivo #define BstChiaveEsistente 1
l'atomo nella radice di un BST partiziona gli atomi dei nodi in /*In inser. per duplicati */
due sottoinsiemi: #define BstChiaveAssente 2
quelli minori stanno nei nodi del sottoalbero sinistro, quelli /* In cancellaz. e recupero. */
#define BstOK 0
maggiori nei nodi del sottoalbero destro.
Lo stesso vale per tutti i sottoalberi typedef struct TNodo{
Atomo Dato;
Anche nei BST si limitano le operazioni possibili, poich struct TNodo *Sx,*Dx;
} Nodo;
necessario che ogni inserimento e cancellazione rispetti il criterio di
ordinamento detto. typedef Nodo *Posiz;
typedef Posiz Bst;
Descrizione intestazione delle funzioni in C extern void BstCrea(Bst *B);
Esegue una visualizzazione void BstVisitaOrd (Bst extern int BstInserisci(Bst *B, Atomo A);
completa degli atomi di B B) extern void VisitaOrdinata(Bst B);
secondo il criterio di extern int BstCancella(Bst *B, Atomo A);
ordinamento extern int BstRecupera(Bst B, Atomo *A);
recupera latomo contenuto in un int BstRecupera(Atomo
*A, extern int BstStato;
nodo che risulta uguale ad A
Bst B)

E possibile realizzare l'albero binario rappresentando con i puntatori


le relazioni d'ordine:
ogni nodo interessato dalle due relazioni dei pi grandi e pi
piccoli
in mancanza di successori da uno dei due lati, il rispettivo
puntatore avr valore nil.
Gli Alberi 9

/* IMPLEMENTAZIONE BST */
OPERAZIONE DI INSERIMENTO
/* albero binario di ricerca implem. con
puntatori */
#include "InfoBase.h"
#include "Bst.h"
#include <stdlib.h> j j

int BstStato; b l b l
Posiz BstTrova(Bst B, Atomo A);
void CancellaMin(Atomo *Min, Bst *B); f n f n

/* BstCrea */ d h d h m
void BstCrea(Bst *B){
(*B) = NULL; Inserimento della chiave m
} /* end BstCrea */
Si sfrutta il criterio di ordinamento per cercare il punto in cui
inserire un nuovo elemento con tecnica dicotomica

- se il l'albero considerato vuoto crea un nodo e


l'albero diventa a un nodo,
altrimenti se l'oggetto da inserire uguale alla
radice
- rifiuta l'inserimento
altrimenti se l'oggetto da inserire < della
radice
- inserisce nel sottoalbero sinistro
altrimenti
- inserisce nel sottoalbero destro

Il nuovo nodo sar sempre inserito come foglia, quindi con i due
sottoalberi vuoti.

Gli Alberi 11

/* impl. operazione inserimento */


OPERAZIONE DI AGGIORNAMENTO
/* BstInserisci */
int BstInserisci(Bst *B, Atomo A) {
BstStato=BstChiaveEsistente;
if (*B==NULL) L'operazione di aggiornamento richiede di trovare la posizione del
{ /* Creazione nodo */ nodo con chiave uguale a quella dell'atomo di ingresso.
(*B)=(Bst)malloc(sizeof(Nodo)); si programma una funzione interna che restituisce la posizione
(*B)->Dato=A; del nodo contenente un atomo assegnato
(*B)->Sx=NULL; la funzione ricorsiva BstTrova realizza la ricerca dicotomica,
(*B)->Dx=NULL;
BstStato=BstOK; come semplice variazione della visita in pre-ordine centrale
} la funzione BstRecupera restituisce quindi l'atomo A
else corrispondente alla chiave cercata.
if (Minore(A,(*B)->Dato)) in caso di non ritrovamento della chiave, la funzione restituisce
BstInserisci(&(*B)->Sx,A);
else lo stato di chiave assente.
if (Minore((*B)->Dato,A))
BstInserisci(&(*B)->Dx,A);
return BstStato;
} /* end BstInserisci*/
Gli Alberi 13

AGGIORNAMENTO CANCELLAZIONE DI UN NODO


/* BstTrova: usato solo internamente */
Posiz BstTrova(Bst B, Atomo A) {
BstStato=BstOK; /* verra' cambiato valore La cancellazione di un nodo qualunque N dell'albero pone due
solo se necessario */ problemi:
if (B==NULL){ a) non perdere i sottoalberi di N, reinserendoli nell'albero
BstStato=BstChiaveAssente; b) garantire il mantenimento del criterio di ordinamento
return NULL; dell'albero.
}
else
if (Uguale(B->Dato,A))
return (Posiz)B; Occorre distinguere diversi casi, a seconda che N sia foglia oppure
else abbia uno o due sottoalberi non vuoti:
if (Minore(A,B->Dato)) se N foglia non necessaria alcuna azione correttiva per
return (Posiz)BstTrova(B->Sx,A); mantenere la condizione di BST;
else
return (Posiz)BstTrova(B->Dx,A); se N non foglia necessario modificare la struttura dell'albero,
} /* end BstTrova */ tenendo conto che l'atomo minimo del sottoalbero destro di N, che
chiameremo MinDx(N), partiziona i valori dell'albero che ha
/* BstRecupera */ radice in N: i valori del sottoalbero sinistro di N sono tutti minori
int BstRecupera(Bst B, Atomo *A) {
Posiz P; di MinDx(N)
P=BstTrova(B,(*A)); se N ha due figli, pu essere sostituito dal minimo del suo
*A=(P->Dato); sottoalbero destro MinDx(N)
return BstStato; se N ha un solo figlio pu essere direttamente sostituito dal figlio
} /* end BstRecupera */

j j

b l b l

f n
h
g n
d h
d h
g

Gli Alberi 15

CANCELLAZIONE /* BstCancella */
ALGORITMO DI CANCELLAZIONE int BstCancella(Bst *B, Atomo A) {
Posiz Temp;
Richiede: Atomo Min;
BstStato=BstOK;
l'individuazione la cancellazione del nodo con chiave minima di if ((*B)==NULL)
un sottoalbero, per mantenere la propriet di ordinamento, BstStato = BstChiaveAssente;
secondo il seguente algoritmo else
si mostra solo la cancellazione della radice di un albero; la if (Minore(A,(*B)->Dato))
cancellazione di un nodo qualunque dellalbero sar preceduta BstCancella(&(*B)->Sx,A);
else
da una fase di ricerca): if (Minore((*B)->Dato,A))
BstCancella(&(*B)->Dx,A);
- se la radice ha due sottoalberi non vuoti else{ /*B punta al nodo che contiene A */
- cancella il nodo con chiave minima e if (((*B)->Sx!=NULL) && ((*B)-
memorizzane il >Dx!=NULL))
contenuto Min { /* Ci sono entrambi i sottoalberi
- sostituisci al contenuto della radice il */
dato Min CancellaMin(&Min,&(*B)->Dx);
altrimenti (*B)->Dato=Min;
- se c soltanto il sottoalbero sinistro }
- sostituisci alla radice il suo figlio
sinistro
altrimenti
- se c soltanto il sottoalbero destro
- sostituisci alla radice il suo figlio
destro
altrimenti
- cancella la radice, poich una
foglia
Gli Alberi 17

/* CONTINUA BstCancella */ CANCELLAZIONE (continua..)


else
{ /* c'e' al piu' un figlio, Sx o Dx L'operazione CancellaMin per cancellare il nodo con chiave
*/ minima dell'albero non ha rilevanza di per se, pertanto non stata
Temp=(*B); inclusa, fra le operazioni di base.
if ((*B)->Sx!=NULL)
(*B)=(*B)->Sx; /* solo sottoalb. Sx
*/ L'elemento minimo Min di un BST nel nodo pi a sinistra e
else valgono le seguenti propriet:
if ((*B)->Dx!=NULL) Min figlio sinistro del genitore;
(*B)=(*B)->Dx; /* solo sottoalb. Dx
*/ Min pu essere:
else /* il nodo e' una foglia: */ foglia,
(*B)=NULL; /* eliminazione non foglia: in tal caso avr soltanto un figlio destro (radice di un
diretta */ sottoalbero);
free(Temp); tutti i nodi del sottoalbero destro di Min hanno chiavi minori della
}
} chiave del genitore di Min;
return BstStato; all'eliminazione del nodo contenente Min il suo figlio destro pu
} /* end CancellaBst */ divenire figlio sinistro del genitore del nodo eliminato.

j
j
b l
f l
f n
d h n
d h

Gli Alberi 19

/* CancellaMin: uso interno */ VISUALIZZAZIONE ORDINATA BST


void CancellaMin(Atomo *Min, Bst *B){
Posiz Temp;
Procedura di visualizzazione completa e ordinata di un BST:
if ((*B)!=NULL)
if ((*B)->Sx!=NULL) semplice visita in ordine simmetrico.
CancellaMin(Min,&(*B)->Sx);
else
{ /* B punta al nodo con il minimo /* VisitaOrdinata */
*/ void VisitaOrdinata(Bst B) {
Temp=*B; if (B!=NULL)
*Min=(*B)->Dato; {
*B=(*B)->Dx; VisitaOrdinata(B->Sx);
free(Temp); Visualizza(B->Dato);
} VisitaOrdinata(B->Dx);
} /* end CancellaMin */ }
} /* end VisitaOrdinata */
Quando sono presenti entrambi i sottoalberi, l'eliminazione fisica
(con free) di un nodo eseguita dalla CancellaMin
La Cancella sostituisce nel nodo da cancellare effettivamente
l'atomo restituito come parametro di uscita da CancellaMin. Negli
altri casi la Cancella esegue leliminazione diretta dal nodo.
Gli Alberi 21

Libreria per il trattamento del dato:


Esempio di utilizzo di alberi binari di ricerca /* InfoBase.c */

#include <stdio.h>
Programma per la creazione, cancellazione e visualizzazione di un #include "InfoBase.h"
dizionario
informazione costituita da una chiave, in base alla quale si
eseguono ricerche e ordinamenti #define Terminatore "."
e da una stringa associata.

Il programma esegue tre cicli: void Acquisisci(Atomo *A){


inserimento di dati nellalbero, printf("inserire la chiave (max 15
ricerca di chiavi nellalbero caratteri, ");
printf(Terminatore);
cancellazione di chiavi dallalbero; printf(" per terminare) ");
scanf("%s", &(A->chiave));
Per il confronto fra stringhe si fa ricorso alla libreria standard if (!AtomoNullo(*A)){
string.h. printf("inserire l'informazione (max 47
car.) ");
Header per il trattamento del dato: scanf("%s", &(A->stringa));
/* InfoBase.h */ }
#include <string.h> }
typedef int boolean;
typedef struct {
char chiave[16]; void AcquisisciChiave(Atomo *A){
char stringa[48]; printf("inserire la chiave (max 15
} Atomo; caratteri) ");
scanf("%s", &(A->chiave));
extern void Acquisisci(Atomo *A); }
extern void AcquisisciChiave(Atomo *A);
extern void Visualizza(Atomo A); void Visualizza(Atomo A){
extern boolean Minore(Atomo A,Atomo B); printf("chiave: %s\tinfo:
extern boolean AtomoNullo(Atomo A); %s\n",A.chiave,A.stringa);
extern boolean Uguale(Atomo A,Atomo B); }
/* end Visualizza */

Gli Alberi 23

ESEMPIO DI UTILIZZO
/* infobase.c -- Continua */ Infine, il seguente programma un esempio di utilizzo delle
boolean Minore(Atomo A,Atomo B){ librerie sopra illustrate:
return (strcmp(A.chiave,B.chiave)<0);
/* strcmp presa da #include <stdio.h>
string.h */ #include <stdlib.h>
}
#include "InfoBase.h"
#include "Bst.h"
boolean AtomoNullo(Atomo A){
return !strcmp(A.chiave,Terminatore); int main(void){
} Atomo A;
Bst B;
boolean Uguale(Atomo A,Atomo B){ BstCrea(&B);
return !strcmp(A.chiave,B.chiave); printf("Ciclo di inserimento\n");
} do { /* ripeti finche atomo non nullo */
Acquisisci(&A);
if (!AtomoNullo(A)){
if(BstInserisci(&B,A)) /*lunico err.
possibile
e quello di chiave gia
esistente */
printf("Chiave esistente\n");
} } while (!AtomoNullo(A));
Gli Alberi 25

VisitaOrdinata(B);
printf("Ciclo di ricerca\n"); Complessit delle operazioni su alberi
do { /* ripeti finche atomo non nullo */ Tutte le operazioni su alberi, ad eccezione della creazione, vengono
AcquisisciChiave(&A);
if (!AtomoNullo(A)){ eseguite in tempo O(h), dove h la profondit dell'albero.
if(BstRecupera(B,&A)){/* lunico err.
possibile e quello di chiave assente */
printf("Chiave inesistente\n"); Prova intuitiva: le procedure viste
} non contengono cicli,
else{
Visualizza(A); sono ricorsive ed eseguono sempre una singola navigazione verso
} il basso,
} terminano quando viene incontrata una foglia.
} while (!AtomoNullo(A));
Per effetto del secondo e del terzo punto
printf("Ciclo di cancellazione\n");
do { /* ripeti finche atomo non nullo */ il numero di attivazioni di procedura sempre uguale alla
AcquisisciChiave(&A); profondit di una foglia
if (!AtomoNullo(A)){ lassenza di cicli interni alle procedure rende la complessit di
if(BstCancella(&B,A)){/* lunico err. caso peggiore delle singole chiamate costante rispetto alle
possibile dimensioni del problema. N
e quello di chiave assente */
printf("Chiave inesistente\n");
}
else VisitaOrdinata(B); /* in caso di
successo mostra lalbero dopo la cancell. */
}
} while (!AtomoNullo(A));
printf("Fine Lavoro");
return 0;
}

Gli Alberi 27

Albero bilanciato
Complessit delle operazioni su alberi: Visioni Alternative
Per garantire che le operazioni abbiano una complessit logaritmica
esistono due possibilit:
Sarebbe pi interessante esprimere la complessit delle operazioni fidarsi di un comportamento casuale che genera un albero di
in funzione di una dimensione pi significativa, quale il numero di profondit quasi minima,
nodi n dellalbero, ma il legame fra n e h non noto a priori, poich: intraprendere azioni correttive durante la costruzione dell'albero in
caso di sbilanciamento.
la forma dell'albero dipende dall'ordine degli inserimenti, come
mostrato in figura
Un albero bilanciato se:
j ad ogni nodo la differenza fra le profondit dei suoi due
Albero generato dalla sequenza
sottoalberi al pi 1.
j,d,b,g,n,l,q; b
d l
n
La profondit di un albero bilanciato sempre O(log n).
b h
g l n
q
quali altre sequenze lo generano?
Un albero bilanciato pu essere ottenuto per ridefinizione delle
bj
operazioni su BST: in questo caso si parla di AVL-tree (dai nomi degli
l
d inventori Adel'son-Velskii e Landis, 1962).
Albero generato dalla sequenza g
n gli algoritmi di inserimento e cancellazione sono estesi con
b,d,g,j,l,n,q; operazioni di soccorso, chiamate per ristabilire il bilanciamento in
nj
caso di necessit.
nl
quali altre sequenze generano un le operazioni di soccorso si basano sul concetto di rotazione di
albero di profondit massima? n una porzione di albero, come mostrato in figura
q
n la struttura di un nodo deve venire estesa per contenere l'altezza
del sottoalbero di cui il nodo radice.
In particolare, il massimo valore di h n-1, 10
j 14
l
nel caso peggiore la complessit diventa lineare.
Viceversa b
4 14
l
10 17
n
h minima se tutti i percorsi dalla radice alle foglie hanno
12 17
n
lunghezza h-1 o h. b
4 12
h
22
h
22
Supponendo che tutti i percorsi siano uguali di lunghezza h, vale la
relazione n = 2h+1-1, quindi se h minima h O(log n), e quindi
le operazioni hanno complessit O(log n).
Gli Alberi 29

Rotazione Doppia Rotazione


La figura mostra come variano le altezze dei sottoalberi di un nodo Se invece lo sbilanciamento nel nodo x dovuto al figlio sinistro del
sottoposto a rotazione. figlio destro si deve fare una doppia rotazione
una rotazione verso destra su y riporta al caso precedente,
Se lo sbilanciamento di x dovuto al figlio destro del figlio destro:
e una rotazione verso sinistra su x ristabilisce il bilanciamento.
una rotazione verso sinistra su x ristabilisce il bilanciamento. Vedi figure sotto.
h+3
h+3 h+3
x
h+2 x x
h h+2 h+2
A y h h
A y A z
h+1 h+1
h h+1 h h
B C z C y
B'
h+2 h h h h
B' B'' B'' C
y
h+1
x h+1 h+2
C
h h z
A B
h+1 h+1
x y
h h h h
A B' B'' C

Ogni operazione di rotazione richiede la ristrutturazione dei


puntatori e il ricalcolo delle altezze ai nodi.
modifica le operazioni di inserimento e cancellazione, inserendo
le opportune chiamate alle operazioni di soccorso
dopo una modifica dellalbero si pu chiamare una procedura
FissaAltezza per ricalcolare laltezza dellalbero
per valutare se un albero bilanciato, confrontando le altezze
dei suoi figli chiamare una procedura BilanciaSx o
BilanciaDx
le procedure di bilanciamento dovranno poi chiamare in modo
opportuno le procedure di rotazione RuotaSx, RuotaDx,
che implementano le operazioni illustrate nelle figure

GLI ALBERI