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?
© Alberto Montresor
5
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 ← new T REE
t.value ← v
t.parent ← t.child ← t.sibling ← nil
return t
insertChild(T REE t)
t.parent() ← this
t.rightSibling() ← child
% Puntatore al padre
% Puntatore al primo figlio
% Puntatore al successivo fratello
% Valore del nodo
% Crea un nuovo nodo
% Inserisce t prima dell’attuale primo figlio
child ← t
insertSibling(T REE t)
t.parent() ← this
t.rightSibling() ← sibling
% Inserisce t prima dell’attuale fratello
sibling ← t
deleteChild()
© Alberto Montresor
14
t.rightSibling() ← 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
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 ← this
left ← T
insertRight(T REE T )
T.parent ← this
right ← T
T REE
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
Figura 5.7: Alberi ordinati e alberi binari.
Limite inferiore complessità ordinamento
✦
✦
Esempio 5.7 (Albero delle scelte). Le possibili sequenze di confronti (a due alt
fettuatedi
da ordinamento
un algoritmo per risolvere un problema possono essere visualizzate co
Albero delle scelte in algoritmi
binario, detto albero delle scelte. In tale albero, i nodi con figli rappresentano
✦ Sequenze di confronti (a
dati
delalternative)
problema, le coppie
padri-figli i risultati
confronti,
e le foglie soluzi
due
rappresentabile
comedeialbero
binario
del problema. Un percorso radice-foglia rappresenta la sequenza di confronti e
✦ Nodi interni → confronti, foglie → soluzioni del problema
individuare una soluzione e il livello massimo di una foglia dà il numero di confro
nel caso pessimo. Un albero delle scelte che minimizzi il livello massimo delle fo
✦ Percorso radice-foglia: insieme di confronti per individuare una soluzione
con tale valore una limitazione inferiore al numero di decisioni che ogni algoritmo
il problema deve effettuare nel caso peggiore.
Limite inferiore ordinamento
✦
✦
✦
✦
Sia n la dimensione del vettore
Numero di possibili soluzioni: n!
Altezza minima albero:
log2 n! ≥ n/2 log2 n/2
© Alberto Montresor
no
b<c
si
c<b
no
si
a<c
a,b,c
Da cui deriva che qualunque
algoritmo di ordinamento
richiede Ω(n log n) confronti
a<b
si
si
a,c,b
no
c,b,a
no
c,a,b
a<c
si
b,a,c
no
b,c,a
Figura 5.8: Albero delle scelte per l’ordinamento di tre numeri.
La Fig. 5.8 illustra un albero delle scelte per il problema di ordinare tre nu
Poiché un qualsiasi algoritmo di ordinamento di n numeri deve fare una sequen
24
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
© Alberto Montresor
25