!
!
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?
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
t.value
t.parent
return t
% Puntatore al padre
% Puntatore al primo figlio
% Puntatore al successivo fratello
% Valore del nodo
new T REE
v
t.child
t.sibling
% Crea un nuovo nodo
nil
insertChild(T REE t)
t.parent
this
t.sibling
child
child
t
% Inserisce t prima dell’attuale primo figlio
insertSibling(T REE t)
t.parent
this
t.sibling
sibling
sibling
t
deleteChild()
© Alberto Montresor
% Inserisce t prima dell’attuale fratello
14
t.parent
this
t.sibling
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
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
left
T
this
insertRight(T REE T )
T.parent
this
right
T
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
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
www.xkcd.com
© Alberto Montresor
24