Introduzione
Algoritmi e Strutture Dati
✦
Strutture dati viste finora
✦
✦
Capitolo 10 - Code con priorità e insiemi disgiunti
Sequenze, insiemi e dizionari
Strutture “speciali”
✦
✦
Le operazioni base (inserimento, cancellazione, lettura, etc.) possono non essere
sufficienti: a volte è necessario inventarsene di nuove
Se non tutte le operazioni sono necessarie, è possibile realizzare strutture dati più
efficienti, “specializzate” per particolare compiti
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.
1
© Alberto Montresor
Code con priorità
✦
✦
Alberi binari
L'idea
✦
✦
Una struttura dati – detta heap – che mantiene dati in modo parzialmente ordinato
Albero binario perfetto
✦
Utilizzazioni
✦
✦
Code con priorità
✦
✦
Gli oggetti vengono estratti dalla coda in base alla loro priorità
✦
Algoritmo di ordinamento
✦
Costo computazionale: O(n log n)
✦
Ordinamento sul posto
© Alberto Montresor
Tutte le foglie hanno la stessa
profondità h
Nodi interni hanno grado 2
✦
Albero binario completo
✦
✦
Un albero perfetto
✦
Heapsort
✦
2
© Alberto Montresor
3
✦
Ha altezza ⎣log N⎦
✦
Altezza h → #nodi= 2h+1-1
© Alberto Montresor
Tutte le foglie hanno profondità h o
h-1
Tutti i nodi a livello h sono
“accatastati” a sinistra
Tutti i nodi interni hanno grado 2,
eccetto al più uno
4
Alberi binari heap
✦
Max heap
Un albero binario completo è un
albero max-heap sse
✦
✦
✦
Alcune informazioni sugli alberi heap
16
Ad ogni nodo i viene associato un valore A[i]
✦
A[p(i)] ≥ A[i]
14
Un albero binario completo è un
albero min-heap sse
✦
Ad ogni nodo i viene associato un valore A[i]
✦
A[p(i)] ≤ A[i]
10
8
7
9
3
✦
2 4
✦
✦
Le definizioni e gli algoritmi di max-heap sono simmetrici rispetto a min-heap
5
Vettore heap
✦
✦
A[2]
14
A[3]
10
A[4] A[5]
A[6] A[7]
Come è organizzato?
A[1] contiene la radice
p(i) = ⎣i/2⎦
✦
l(i) = 2i
✦
✦
Antisimmetrico: se n≥m e m≥n, allora m=n
✦
Transitivo: se n≥m e m≥r, allora n≥r
Ordinamenti parziali
10
{a,b}
{a}
{b}
∅
Utili per modellare gerarchie complesse o mantenere informazioni parziali
✦
Nozione più debole di un ordinamento totale...
✦
Ma più semplice da costruire
6
8
7
Ricapitolando:
✦
A[1]
16
✦
Vettore A[1..n]
✦
Riflessivo: Ogni nodo è ≥ di se stesso
14
© Alberto Montresor
✦
Come è fatto?
✦
✦
16
Vettore heap
E' possibile rappresentare un albero binario heap tramite un
vettore heap (oltre che tramite puntatori)
✦
Un albero heap è un ordinamento parziale
✦
1
© Alberto Montresor
✦
Un albero heap non impone alcuna relazione di ordinamento fra i figli di un nodo
9
Procedure per gestire heap
✦
maxHeapRestore - O(log n)
✦
✦
3
A[8] 2 4 1 A[10]
A[9]
Un algoritmo che mantiene la proprietà di max-heap
heapBuild - O(n)
✦
✦
r(i) = 2i+1
Vettore max-heap: A[i] ≥ A[2i], A[i] ≥ A[2i+1]
Un algoritmo che costruisce un max-heap da zero
heapsort - O(n log n)
✦
Ordina sul posto un vettore
n = 10
16 14 10 8 7 9 3 2 4 1
© Alberto Montresor
7
© Alberto Montresor
www.xkcd.com
8
in input.
maxHeapRestore()
✦
maxHeapRestore(I TEM[ ] A, integer i, integer dim)
Input
✦
Un vettore A e un indice i
✦
Gli alberi binari con radici l(i) e r(i) sono max-heap
✦
E' possibile che A[i] sia minore di A[l(i)] o A[r(i)]
✦
✦
maxHeapRestore()
integer max ⇥ i
if l(i) dim and A[l(i)] > A[max] then max ⇥ l(i)
if r(i) dim and A[r(i)] > A[max] then max ⇥ r(i)
if i ⌅= max
CAPITOLO
10.then
CODE CON PRIORITÀ E INSIEMI DISGIUNTI
A[i] ⇤ A[max]
maxHeapRestore(A, max, dim)
Benché la procedura sort() abbia complessità ottima se si usa uno hea
In altre parole: il sottoalbero con radice i può non essere un max-heap
Scopo
✦
Ripristinare la proprietà di max-heap sul sottoalbero con radice i
✦
Facendo “scendere” l'elemento A[i] nel vettore
© Alberto Montresor
svantaggio di richiedere un doppio trasferimento di elementi dal vettore A
heap di nuovo al vettore A, con conseguente spreco di memoria. È possib
l’ordinamento
in loco,
cioè direttamente
nel vettore
A senza un valutare
secondo v
Per valutare
la complessità
di heapBuild
(), dobbiamo
dopo aver(),
“ristrutturato”
stesso
in modo
verifichi ledel
proprietà
un so
m
pRestore
che dipendeAda
i e dim.
Siache
m l’altezza
nodo idinel
perheap
cui è A[i
necessario
un max-heap
sarà chiarito
lo
. . . dim],
definita come
segue:a breve.
l’altezza di una foglia
9
maxHeapRestore()
✦
✦
5, 15, 12, 9, 10, 7, 4, 3, 6, 8, 2
✦
E' un vettore max-heap?
✦
E' possibile applicare maxHeapRestore() alla radice?
Domanda:
✦
✦
soddisfi le proprietà di max-heap. Poiché maxHeapRestore() lavora assum
Si notisiano
che in
heap di
n elementi
ci dalle
sonofoglie
al piùe risalire
⌥n/2m+1
giàuno
max-heap,
dobbiamo
partire
verso
✦ sottostanti
Principio generale
maxHeapRestore
()
O(m)
è richiamata
su
elementi
compresi
frarichiede
⌅n/2⇧
+ 1tempo
e n sono
fogliequando
nell’albero
B; sono quind
✦ Sia A[1..n]
un vettore
da ordinare
per✦ definizione,
essendo
composti
dadell'albero
un solo eelemento.
Applicando
Tutti i nodi A[⎣n/2⎦+1
... n]
sono foglie
quindi heap di
un elemento maxH
da
tendocuidainiziare
⌅n/2⇧ e risalendo fino ad 1, si restaurano le proprietà di max-hea
i sottoalberi
siano max-heap (infatti, o sono foglie o sono nodi su cui abb
✦ La procedura heapBuild() attraversa i restanti nodi dell'albero ed esegue
maxHeapRestore
()).
maxHeapRestore()
Domanda: Esempio di funzionamento
✦
Qual è la complessità in tempo di maxHeapRestore()?
heapBuild(I TEM[ ] A, integer n)
Domanda
✦
10 d
Dato ilche
vettore
. . . n]
in cuiègli
elementi
sono disposti
in un
ordine
q
un nodo
non A[1
è una
foglia
uguale
al massimo
delle
altezze
bile permutarli richiamando maxHeapRestore() più volte in modo che al te
procedura
heapBuild() si basa su una discesa dal nodo i fino ad una foglia, essa
© Alberto Montresor
for integer i ⇥ ⌅n/2⇧ downto 1 do
maxHeapRestore(A, i, n)
Dimostrare la correttezza di maxHeapRestore()
© Alberto Montresor
11
Invece di operare su una struttura di dati a parte e su associazioni e
versione di maxHeapRestore() utilizzata qui lavora direttamente sul vettor
12
© Alberto Montresor
• Conclusione: Al termine, i = 0. Quindi il nodo 1 è una radice di uno heap.
1.2
heapBuild()
complessità di heapBuild() è
✦
Domanda: Esempio di funzionamento
✦
✦
n
2m+1
m=0
14, 45, 28, 34, 15, 20, 12, 30, 21, 25, 16, 22
⌅
·m ,
Domanda:
che
è delloCorrettezza
stesso ordine di grandezza di
✦
✦
✦
O
⇤log n
⇧
Esprimere un'invariante di ciclo
Dimostrare la sua correttezza
Domanda – Complessità
✦
Qual è la complessità di Dato
che
heapBuild()?
⇤
O n
log
⇧n
m
2m
m=0
34
log
⇧n
45⌅
.
15
1.2
14
Domanda 5.6 - Costo computazionale build-max-heap()
Complessità
Domanda
5.6 che
- Costo
computazionale
Abbiamo detto
max-heapify()
costabuild-max-heap()
O(log n); viene eseguito n/2 volte. Quindi una stim
costo computazionale è O(n log n). Ma questa stima non è stretta. Il fatto è che i passi vengono ese
✦ max-heapify()
Abbiamo detto che
costa O(log n);vengono
viene eseguito
n/2
una
stima del
Le operazioni maxHeapRestore()
eseguite
su volte.
heap diQuindi
altezza
variabile
su heap di altezza
variabile.
costo computazionale
è O(n
log n). Ma questa stima non è stretta. Il fatto è che i passi vengono eseguiti
✦ Viene eseguito ⎡n/2⎤ volte su heap di altezza 0
eseguito
n/2 volte su heap di altezza 0
su heapViene
di altezza
variabile.
Viene eseguito
n/4
voltedisualtezza
heap di
1
Viene eseguito
n/2 volte
su heap
0 altezza
✦ Viene
eseguito ⎡n/4⎤
volte su heap di altezza 1
Viene eseguito
n/8
voltedisualtezza
heap di
Viene eseguito
n/4 volte
su heap
1 altezza 2
✦ Viene
...
Viene eseguito
n/8 volte
su heap
di altezza
2 volte su heap di altezza 2
eseguito
⎡n/8⎤
28
h
...
Viene eseguito n/2 + 1 volte su heaph+1di altezza h.
⎡n/2 h.
⎤ volte su heap di altezza h
Viene eseguito n/2h +✦ 1...Viene
volte su eseguito
heap di altezza
Quindi si ha la seguente relazione:
✦ Da cui si deduce
Quindi si ha la seguente
relazione:
20
12
⇥log n⇤
⇥log n⇤
⇥ n ⇥log n⇤ ⇥log
⇥n⇤
⇥
⇥
⇥log n⇤
⇥log n⇤
h+1
h
⇥ n
⇥
⇥
⇥
h
=
n
(1/2)
h
=
n/2
(1/2)
h
⇤
n/2
1/2)h h = O(n)
h+1
h
h
h+1
h
=
n
(1/2)
h
=
n/2
(1/2)
h
⇤
n/2
1/2)
h
=
O(n)
2
h=1
h=1
h=1
h=1
2h+1
1
⇧ m
m
h=1
h=1
2
<
=
⇥2 = 2
m
m
1
2
230 21 125 16 22 Vi ricordo
Vi ricordo
che: ✦che:
m=0
m=0
2
Ricordate:
h=1
⇥
⇥
hx =
h
xh=
hx
h=1
x
la complessità di heapBuild() è O(n).
(1 x)2 (1 x)2
h=1
h=1
Terminata la ristrutturazione, l’elemento massimo di A[1 . . . se
n]xsi< se
trova
1. x
Poiché
= 1/2,La
consegue
che la sommatoria
dà origine aldàvalore
2, che
moltiplicato
n/2
<in
1. xA[1].
Poiché
xne
=posi1/2, ne consegue
che la sommatoria
origine
al valore
2, cheper
moltiplicato
per
n.
zione che gli compete è A[n]; facendo un’operazione deleteMaxdà
(),come
l’elemento
in
A[n]
viene
dàrisultato
come
risultato
n.
13
14
sistemato nello heap A[1 . . . n 1] e A[n] diviene libera. È quindi possibile scrivere l’elemento
Ordinamento
heap
massimo in tramite
A[n]. L’operazione
può quindi essere ripetuta con lo heap A[1 . . . nheapsort()
1], poi con
lo heap A[1 . . . n 2], e cosı̀ via fino allo heap A[1 . . . 2]; terminata quest’ultima operazione,
1
1
✦ Intuizione
✦ Domanda: Esempio di funzionamento
il minimo si trova in A[1].
45
✦ Il primo elemento dello heap è sempre il massimo
✦ 14, 45, 28, 34, 15, 20, 12, 30, 21, 25, 16, 22
La procedura di ordinamento risultante è detta heapsort() ed è dovuta a Williams (1964);
✦ Andrebbe collocato nell'ultima posizione
si noti che invece di chiamare max() per leggere il massimo e una deleteMax() per
rimuoverlo,
✦ Domanda:
Correttezza
ad✦ ogni
passoinilultima
primo
e l’ultimo
elemento vengono scambiati e maxHeapRestore
()
viene
L'elemento
posizione?
In testa!
✦ Esprimere un'invariante di ciclo
34
28
chiamata
direttamente.
✦ Chiama
maxHeapRestore() per ripristinare la situazione
✦
✦
© Alberto Montresor
Per x < 1
© Alberto Montresor
Dimostrare la sua correttezza
heapsort(I TEM[ ] A, integer n)
heapBuild(A, n)
for integer i ⇥ n downto 2 do
A[1] ⇤ A[i]
maxHeapRestore(A, 1, i 1)
✦
Domanda: Complessità
✦
Qual è la complessità di heapsort()?
14
La chiamata iniziale a heapBuild() costa (n). Poiché lo heap A[1 . . . n] ha n elementi,
15
l’altezza della radice (il nodo 1) è O(log n). Ciascuna chiamata a maxHeapRestore
() sul-
© Alberto Montresor
30
© Alberto Montresor
25
21
15
22
16
12
20
16
solo percorso radice-foglia, entrambi possono essere realizzati in modo da richiedere al più
O(log n) passi.
Code con priorità
Vediamo ora in dettaglio le procedure in pseudocodice che realizzano la coda con priorità
basata su heap. Per semplicità, le priorità saranno rappresentate da numeri interi, ma qua✦ Coda con priorità
lunque
tipo dotato di una relazione di ordinamento ⇥ potrebbe andare bene. La struttura di
✦
dati è mantenuta
in undati
vettore
di tipoun
P RIORITY
Una struttura
cheheap
servediarecord
mantenere
insiemeI TEM
S di;
ognuno di questi record
contiene,
oltre
ad
una
coppia
elemento-priorità,
la
posizione
attuale
elementi x, ciascuno con un valore associato di priorità del record all’interno del
vettore. Questa informazione è necessaria per realizzare decrease().
Esercizi
✦
Esercizio
✦
✦
Esercizio
✦
✦
Scrivere un'implementazione iterativa di maxHeapRestore()
(in pseudo-codice)
P RIORITY I TEM
integer priorità
I TEM valore
integer pos
Esercizio
✦
✦
Dimostrare che uno heap con n nodi ha altezza θ(log n)
Dimostrare che il tempo di esecuzione nel caso peggiore di maxHeapRestore() è Ω(log n)
Oltre al vettore heap H, la struttura di dati contiene un intero capacità che indica la dimensione massima del vettore, inizializzata al momento di costruire la struttura di dati, e un
intero dim che indica quanti elementi sono attualmente presenti nel vettore e corrisponde alla
posizione (nel vettore heap) della foglia di B che sta più a destra fra quelle di livello massimo.
Esercizio
✦
Implementare heapsort() nel vostro linguaggio preferito
170
% Priorità
% Elemento
% Posizione nel vettore heap
Algoritmi e Strutture di Dati
© Alberto Montresor
17
insert() rimane inalterato e le operazioni max(), deleteMax() e increase() sostituiscono le altre
operazioni.
Specifica
Code con priorità
P RIORITY Q UEUE
u
% Crea una coda con priorità vuota
PriorityQueue()
% Restituisce true se la coda con priorità è vuota
boolean isEmpty()
Esempio di utilizzo
u
Simulatore event-driven
u
Ad ogni evento è associato un timestamp di esecuzione
u
Ogni evento può generare nuovi eventi, con timestamp arbitrari
u
% Restituisce l’elemento minimo di una coda con priorità non vuota
I TEM min()
% Rimuove e restituisce il minimo da una coda con priorità non vuota
I TEM deleteMin()
u
Una coda con min-priorità può essere utilizzata per eseguire gli eventi in ordine
di timestamp
Esempi di organizzazione
p 3 ev
% Inserisce l’elemento x con priorità p in una coda con priorità non piena
% Restituisce un oggetto P RIORITY I TEM che identifica x all’interno della coda
P RIORITY I TEM insert(I TEM x, integer p)
p 7 ev
% Diminuisce la priorità dell’elemento identificato da x portandola al valore p
decrease(P RIORITY I TEM x, integer p)
© Alberto Montresor
18
© Alberto Montresor
19
© Alberto Montresor
evento
p 5 ev
p3
p4
p5
p6
p8
p7
ev
ev
ev
ev
ev
ev
20
caso pessimo, la risalita continua fino alla radice (i = 1). La procedura termina restituendo
Il valore 0radicati
in dim indica
una coda
con priorità
vuota.
Laradice
funzione
min() è molto
semplice: se
toalberi
nei figli
sinistro
e destro
della
rispettano
la proprietà
(3), mentre
l’unica
l’oggetto P RIORITY I TEM in cui l’elemento è stato inserito.
laCode
coda con
priorità
non
è
vuota,
è
sufficiente
restituire
il
primo
elemento
del
vettore.
con
priorità
Inserimento
possibile eccezione è proprio la radice, che potrebbe essere maggiore dei propri figli.
La funzione swap(), oltre a scambiare le posizioni i e
P RIORITY I TEM insert(I TEM x, integer p) posizione dei record mano a mano che vengono spostati
P RIORITY
Q UEUE minHeapRestore(A, i, dim) ha il compito di rettificare questa situazione;
La procedura
parprecondition: dim < capacità
integer capacità
di elementi
nella coda
te dall’assunto
che gli alberi radicati in %
l(i)Numero
e r(i) massimo
(se esistenti)
rispettino
già le proprietà dello
swap(P RIORITY I TEM[ ] H, integer i, integer j)
dim
dim + 1
integer dim
% Numero attuale di elementi nella coda
heap, e verifica se uno dei figli di i è minore di i stesso. Se questo accade (i ⌅= min), gli
H[i] ⇤ H[j]
H[dim]
new P RIORITY I TEM()
P RIORITY I TEM[ ] H
% Vettore heap
H[i].pos ⇥ i
elementi in A[i] e A[min] vengono scambiati. A questo punto, il problema può essersiH[dim].valore
spostato
x
P RIORITY Q UEUE PriorityQueue(integer n)
H[j].pos ⇥ j
nella posizione
che Qi UEUE
sottoalberi radicati in l(min) e r(min) sono H[dim].priorità
heap (lo
p
P RIORITY Qmin:
UEUE tsiamo
newcerti
P RIORITY
H[dim].pos
dim
t.capacità
n
erano prima
di eseguire
questa chiamata di minHeapRestore()), ma è possibile che l’elemento
integer i
dim
t.dim
0
Per realizzare deleteMin() useremo una procedura a
min violi
la proprietà
(3) dello heap, ovvero che A[min] sia maggiore di A[l(min)] o A[r(min)].
while i > 1 and H[i].priorità < H[p(i)].priorità
do nella sezione successiva. deleteMin() s
t.H
new P RIORITY I TEM[1 . . . n]
utilizzata anche
La procedura minHeapRestore() viene quindi chiamata ricorsivamente su min. La chiamata
swap
(H,
i,
p(i))
scriviamo la prima posizione dello heap (quella che co
return t
ricorsiva termina quando i e i suoi figli rispettano le proprietà di uno heap.
i
p(i)
si trova nella foglia più a destra del livello massimo, e d
I TEM min()
numero di elementi presenti è diminuito di 1. L’albero
return
H[i]
precondition: dim > 0
Il codice della deleteMin() e della minHeapRestore() è mostrato di seguito; si noti che
return
H[1].valore
deleteMin
() chiama
minHeapRestore() sulla radice, subito dopo aver eseguito lo scambio con
l’ultimo elemento.
toalberi radicati nei figli sinistro e destro della radice risp
possibile eccezione è proprio la radice, che potrebbe ess
La procedura minHeapRestore(A, i, dim) ha il comp
te dall’assunto che gli alberi radicati in l(i) e r(i) (se esi
Nella insert(), si incrementa il valore di dim per far spazio al nuovo elemento x. Questo
Concludiamo con la procedure decrease(), che prende in input un oggetto di tipo P RIORI heap, e verifica se uno dei figli di i è minore di i stes
corrisponde ad aggiungere una foglia nel livello massimo di B, nella posizione più a sinistra
176
Algoritmi
e Strutture
di
D
elementi in A[i] e A[min] vengono
scambiati.
A questo
TY
Iquelle
TEM
e ne aggiorna la priorità, riposizionandolo nel vettore heap se necessario.
Poiché la
22 p
©fra
Alberto
Montresor disponibili. In questa posizione, l’elemento x potrebbe non rispettare la proprietà21
© Alberto Montresor
nella posizione min: siamo certi che i sottoalberi radic
priorità
è diminuita,
viene
quella
del padre
scambiata asedim;
necessario.
(3), ovvero
potrebbe essere
più confrontata
piccolo di suocon
padre.
Il cursore
i vienee inizializzato
erano prima di eseguire questa chiamata di minHeapRes
priorità
Rimozione del minimo e riduzione priorità
seCode
i noncon
coincide
con-laminHeapRestore()
radice e l’elemento contenuto in H[i] è più piccolo di quello di suo
min violi la proprietà (3) dello heap, ovvero che A[min] s
padre, questi vengono scambiati dall’operazione swap() e il cursore i risale alla posizione del
La procedura minHeapRestore() viene quindi chiamata
minHeapRestore
(P RIORITY
I TEM
[ ] A, integer
i, integer
dim) rispettata. Nel
padre
p[i]. Il ciclo while
si ripete fino
a quando
la proprietà
(3) non viene
I TEM deleteMin(I TEM x)
ricorsiva termina quando i e i suoi figli rispettano le prop
casointeger
pessimo, min
la risalita
⇥ i continua fino alla radice (i = 1). La procedura termina restituendo
l’oggetto P RIORITY I TEM in cui l’elemento è stato inserito.
if l(i) dim and A[l(i)].priorità < A[min].priorità then min ⇥ l(i)
if r(i) I TEM
dim
and
A[r(i)].priorità
P RIORITY
insert
(I TEM
x, integer p) < A[min].priorità then min ⇥ r(i)
if i ⌅= min then
precondition:
dim < capacità
swap
(A,
i, min)
dim
dim + 1
minHeapRestore
H[dim]
new P RIORITY(A,
I TEMmin,
() dim)
swap(H, 1, dim)
dim ⇥ dim
1
minHeapRestore(H, 1, dim)
return H[dim + 1]
H[dim].valore
x
H[dim].priorità
p
H[dim].pos
dim
integer i
dim
while i > 1 and H[i].priorità < H[p(i)].priorità do
swap(H, i, p(i))
i
p(i)
return H[i]
© Alberto Montresor
precondition: dim > 0
decrease(P RIORITY I TEM x, integer p)
Il codice della deleteMin() e della minHeapRestore
deleteMin() chiama minHeapRestore() sulla radice, subi
l’ultimo elemento.
Concludiamo con la procedure decrease(), che prend
e ne aggiorna la priorità, riposizionandolo nel
priorità è diminuita, viene confrontata con quella del pad
TY I TEM
minHeapRestore(P RIORITY I TEM[ ] A, integer i, integ
integer min ⇥ i
precondition: p < x.priorità
if l(i) dim and A[l(i)].priorità < A[min].priorità
x.priorità ⇥ p
if r(i) dim and A[r(i)].priorità < A[min].priorità
integer i ⇥ x.pos
if i ⌅= min then
while i > 1 and H[i].priorità < H[p(i)].priorità
do i, min)
swap(A,
swap(H, i, p(i))
minHeapRestore(A, min, dim)
i ⇥ p(i)
23
© Alberto Montresor
24
Code con priorità
✦
Esercizio
✦
✦
Struttura dati per insiemi disgiunti
✦
Qual è la complessità delle operazioni sulle code con priorità?
Motivazioni
✦
Esercizio
✦
✦
In alcune applicazioni siamo interessati a gestire insiemi disgiunti CAPITOLO
di oggetti 10. CODE CON PRIORITÀ E INSIEMI DISGIUNTI
Scrivere lo pseudocodice di una versione heapBuild() basata sulla insert()
Esempio: componenti di un grafo
Operazioni fondamentali:
e un’operazione
di unione (che unisce due componenti distinte X
S
unire più insiemi
nuova componente
X ⇤Y , distruggendo X e Y e lasciando inalterate tutte
identificare l'insieme a cui appartiene un oggetto
La diversità rispetto all’usuale tipo di dato insieme consiste nel fatto
Struttura dati
operazioni
di inserimento o di cancellazione; tutti gli elementi vengono i
S = { S1, S2disgiunta
, ..., Sn } di insiemi
dinamici disgiunti
divisiUna
in collezione
una componente
per elemento,
e le successive operazi
che leOgni
componenti
rimangano
disgiunte.
insieme è identificato
da uncomunque
rappresentante
univoco
Nel seguito, assumeremo che l’insieme generativo sia costituito dag
1 e n; in gran parte delle applicazioni pratiche, questa scelta non ridu
struttura, in quanto insiemi di dimensione costante n possono essere ma
26
stessa dimensione, e i valori 1 . . . n rappresentano gli indici di tale vettor
seguente
specifica:
Primitive degli
insiemi disgiunti (Merge-Find)
✦
✦
Le due procedure creano lo stesso heap? Dimostrare o produrre un esempio contrario
✦
✦
✦
Qual è la complessità di questa versione di heapBuild()?
✦
✦
Esercizio
✦
✦
L'azione di delete(PRIORITYITEM x) cancella l'elemento x dallo heap. Scrivete una
versione di delete() che operi in tempo O(log n).
© Alberto Montresor
✦
25
Scelta del rappresentante
✦
✦
✦
M FSET
Il rappresentante può essere un qualsiasi membro dell’insieme Si
✦
Operazioni di ricerca del rappresentante su uno stesso insieme devono restituire
sempre lo stesso oggetto
% Crea n componenti {1}, . . . , {n}
M FSET Mfset(integer n)
Solo in caso di unione con altro insieme il rappresentante può
cambiare
% Restituisce il rappresentante della componente contenente x
integer find(integer x)
Il rappresentante può essere un elemento specifico dell’insieme
✦
✦
© Alberto Montresor
% Unisce le componenti che contengono x e y
merge(integer x, integer y)
Si devono definire le caratteristiche degli insiemi e una regola per caratterizzare il rappresentante
Esempio: l’elemento più piccolo/grande di un insieme
© Alberto Montresor
27
M FSET(n) inizializza la struttura di dati, creando n componenti, ognu
ne uno degli interi fra 1 e n. La funzione find(x) restituisce l’identificat
contenente x; tale funzione, applicata a due elementi della stessa comp
re lo stesso identificatore; applicata a due elementi contenuti in compo
28
© Alberto Montresor
Esempio
✦
Possiamo fare meglio? La struttura d
disgiunti, uno per nodo. Ogni volta che si a
due componenti associate ad u e v sono disg
Determinare le componenti connesse di un grafo non orientato dinamico
Poiché utilizzando la compressione dei per
costante e viene ripetuta m volte, e la creaz
Algoritmo
totaledadiunquesto
algoritmo è O(m + n), n
✦ Si inizia con componenti connesse costituite
unico vertice
struttura M FSET ci permette di gestire facil
✦ L'operazione merge(find(u), find(v)) viene eseguita per ogni arco (u,v)
in questa particolare versione di cc() la st
✦ Alla fine, avremo l'insieme delle componenti connesse
chiamante.
✦
Complessità
Esempio: come utilizzare Merge-Find
mfset(6)
1
2
3
4
5
6
merge(1,2)
1 1, 2 2
3
4
5
6
merge(3,4)
1 1, 2 2
3 3,4 4
5
6
merge(5,6)
1 1, 2 2
3 3,4 4
5 5,6 6
merge(1,3)
1
1,
2 2, 3, 34
merge(1,5)
1
2
4
✦
5 5,6 6
✦
✦
1, 2,
3 3, 4, 45, 6
5
6
M FSET cc(G RAPH G)
M FSET M
Mfset(|G.V |)
foreach u ⇥ G.V () do
foreach v ⇥ G.adj(u) do
M.merge(u, v)
Costo: O(n) + m operazioni merge()
Questo algoritmo è interessante per la capacità di gestire grafi dinamici (in cui gli archi vengono aggiunti)
return M
29
© Alberto Montresor
Implementazione di Union-Find
✦
✦
✦
find():
O(1);
merge():
O(n)
merge():
O(1);
find():
✦
Ogni elemento nella lista contiene:
O(n)
✦
un oggetto
✦
un puntatore all’elemento successivo
✦
un puntatore al rappresentante
Algoritmi basati su euristiche di bilanciamento
✦
Realizzazione basata su liste + Euristica sul peso
✦
Realizzazione basata su alberi + Euristica sul rango
#
head
tail
Algoritmi finale
✦
Alberi + rango + compressione dei percorsi
© Alberto Montresor
31
Reality check
Code con priorità La simulazione è una
Ogni insieme viene rappresentato con unafedelmente
lista concatenata
possibile, il comportamento di
✦ Il primo oggetto di una lista è il rappresentante
sistemi dell’insieme
che possono essere simulati con su
Realizzazione basata su alberi
✦
✦
✦
Realizzazione basata su liste
✦
10.3
Realizzazione basata su liste
Algoritmi elementari:
✦
30
© Alberto Montresor
© Alberto Montresor
c
h
e
b
Può essere
visto come un albero di
altezza 1
32
Realizzazione basata su liste
✦
L’operazione find(x) richiede tempo O(1)
✦
✦
QuickFind: Esempio - merge(h, g)
Si restituisce il rappresentante di x
c
L’operazione merge(x,y) è più complessa:
✦
Si “appende” la lista che contiene y alla lista che contiene x
✦
I puntatori ai rappresentanti della lista “appesa” devono essere modificati
✦
Costo nel caso pessimo per n operazioni: O(n2)
✦
Costo ammortizzato: O(n)
✦
merge(2,1), merge(3,1), merge(4,1), etc.
h
f
33
© Alberto Montresor
Realizzazione basata su alberi (foreste)
✦
Si rappresenta ogni insieme tramite un albero radicato
✦
Ogni nodo dell'albero contiene
✦
✦
✦
✦
l'oggetto
✦
un puntatore al padre
Nota: generalmente migliore, non più veloce delle liste nel caso pessimo
© Alberto Montresor
d
z
f
g
d
z
c
h
e
b
34
© Alberto Montresor
✦
Operazioni e costo
✦
find(x)
✦
h
b
✦
f
c
Il rappresentante è la radice dell'albero
La radice ha come padre un puntatore
a se stessa
g
b
Realizzazione basata su alberi
Implementazione basata su foresta
✦
e
e
✦
d
Risale la lista dei padri di x fino a trovare la radice e restituisce la radice come
oggetto rappresentante
Costo: O(n) nel caso pessimo
merge(x,y)
✦
Appende l'albero radicato in y ad x
✦
Costo: O(1)
g
35
© Alberto Montresor
36
Realizzazione basata su alberi: Esempio – merge(c, f)
f
c
h
e
b
Realizzazione basata su alberi: Esempio – merge(c, f)
c
d
h
g
b
f
c
e
f
e
d
c
d
d
g
g
g
37
© Alberto Montresor
Considerazioni
✦
✦
Quando le merge() sono rare e le find() frequenti
Realizzazione basate su alberi?
✦
✦
✦
Realizzazione basata su liste?
✦
38
© Alberto Montresor
Realizzazione basata su liste: Euristica peso
Quando usare....
✦
f
e
Una strategia per diminuire il costo dell’operazione merge():
✦
Memorizzare l’informazione sulla lunghezza della lista
✦
Appendere la lista più corta a quella più lunga
✦
La lunghezza della lista può essere mantenuta in tempo O(1)
Quando le find() sono rare e le merge() frequenti
E' importante sapere che esistono tecniche euristiche che permettono di
migliorare questi risultati:
✦
4
Fino a operazioni in tempo ammortizzato O(1) per tutte le utilizzazioni pratiche
© Alberto Montresor
2
c
39
© Alberto Montresor
h
e
b
f
g
40
Realizzazione basata su liste: Euristica peso
✦
Realizzazione basata su alberi: Euristica sul rango
Una strategia per diminuire il costo dell’operazione merge():
Ogni nodo mantiene informazioni sul proprio rango
✦
✦
Memorizzare l’informazione sulla lunghezza della lista
✦
Appendere la lista più corta a quella più lunga
✦
La lunghezza della lista può essere mantenuta in tempo O(1)
il rango rango[x] di un nodo x è il numero di archi del cammino più lungo fra x e
una foglia sua discendente
✦
rango ≡ altezza del sottoalbero associato al nodo
✦
Unione di due alberi
con rango diverso
✦
c
h
6
c
h
e
b
f
e
+
f
d
=
Unione di due alberi
con rango uguale
✦
c
h
c
e
f
h
d
b
e
+
f
d
=
c
h
e
f
g
b
b
g
b
d
g
41
© Alberto Montresor
Compressione dei cammini
✦
✦
✦
Utilizzata durante le operazioni find(x)
L'albero viene modificato in modo che ricerche successive di x possano
completare in O(1)
✦
✦
Esempio: operazione find(f)
Quando si utilizzano entrambe le euristiche:
✦
Il rango non è più l'altezza del nodo ...
✦
...ma il limite superiore all'altezza del nodo
In altre parole:
✦
✦
b
e
d
42
Compressione dei cammini + rango
Compressione dei cammini: idea
✦
© Alberto Montresor
b
f
d
✦
e
Le operazioni di compressione dei cammini NON riducono il rango
Sarebbe troppo complesso mantenere le informazioni di altezza
corrette
In ogni caso, non è necessario
f
© Alberto Montresor
43
© Alberto Montresor
44
y
rango[ry ] = rango[rx ], si sceglie arbitrariamente quale albero
rendere figlio dell’altro e si
meglio.
euristica,
chiamata
compressione
dei
percorsi,
la
l’indice
rx Un’ulteriore
e rendendo
(ex)
radice
ry figlia
radice
rx . inconsiste
1 alladella
relativa
posizione
rango. nel modificare
Realizzazione
basata suquindi
alberi la
+ rango
+aggiunge
compressione
dei cammini
Complessità
find() in modo che alteri l’albero durante la sua esecuzione, cosı̀ da rendere più vantaggiose le
integer
y) che viene incontrato da
M
FSET
esecuzioni
successive di find() e mergemerge
(). In(integer
pratica,x,ogni
nodo
find() ora la complessità di:
Valutiamo
r
⇥
find
(x)
x
integer[
]
p
nel percorso di risalita dal generico nodo x alla radice, viene reso figlio della radice. Per Liste
far + Euristica sul peso
r
⇥
find(y)
y
integer[
]
rango
questo, la chiamata ricorsiva all’interno di find() è modificata introducendo un assegnamento al
Alberi + Euristica sul rango
if rx ⇤= ry then
padre
del
nodo x,n)se questo è diverso da x. In
tal modo, durante l’esecuzione, restano “sospesi”
Mfset
(integer
if rango[rx ] > rango[ry ] then
gli assegnamenti
dei nodi nel percorso
verso la radice. Quando conAlberi
la + Euristica sul rango + Compressione dei cammini
M FSET t sui
newpadri
M FSET
p[ry ]di⇥risalita
rx
chiamata
più.interna
radice,
la ricorsione
di find
() termina restituendo
la
if rango[r
t.p ricorsiva
integer[1
. . n] si giunge alla else
y ] > rango[r
x ] then
Alla lavagna!
p[r
]
⇥
r
y
radicet.rango
stessa. La integer[1
radice viene
“a ritroso”x ai padri
in tutti gli assegnamenti “sospesi”,
. . . assegnata
n]
else
Ne segue
via via
si richiudono
tutte
È importante notare che il numero
di che tutte queste implementazioni (e in particolare l'ultima) sono
forche
integer
i
1 to n
do le chiamate ricorsive.
p[rx ] ⇥ ry
ampiamente utilizzabili in pratica
t.p[i]
i
operazioni
della nuova
versione di find() aumenta
solo
per un fattore costante rispetto alla
rango[r
y ] ⇥ rango[ry ] + 1
t.rango[i]
vecchia versione
e che 0pertanto la sua complessità nel caso pessimo non aumenta.
return t
Per giustificare la bontà di questa scelta, cerchiamo di calcolare una limitazione superiore
integer find
integer
find(integer
(integer x)
x)
u
u
u
u
u
u
ififp[x]
p[x]⌅==x xthen
then
return
x (p[x])
p[x]
⇤ find
else p[x]
return
© Alberto Montresor
return find(p[x])
all’altezza dell’albero.
Teorema 10.1. Un albero M FSET con radice r ottenuto tramite euristica sul rango ha almeno
2rango[r] nodi.
45
© Alberto Montresor
Dimostrazione. Procediamo per induzione sul numero
di operazioni merge() effettuate. All’inizio (nessuna operazione merge()), tutti gli alberi hanno rango 0 ed esattamente 1 nodo;
merge(integer x, integer y)
poiché 20 = 1, il teorema è dimostrato nel caso base. Consideriamo l’(n + 1)-esima operaEsempio
(Compressione dei percorsi).
dell’esecuzione
find() sia
convera
la comrx 10.10
find(x)
zione mergeL’effetto
(x, y), e supponiamo
che la di
proprietà
per tutte le operazioni precedenti.
Siano
Y gli insiemi contenenti x e y, con X ⇤= Y ; siano rango[rx ] e rango[ry ] il rango delle
pressione
percorsi
è mostrato nella
Fig.X,10.6.
ry dei
find
(y)
radici
r
e
x ry , rispettivamente; sia rango[r] il rango della radice risultante dalla (n + 1)-esima
if rx ⇥= ry then
operazione merge(). Sono dati tre casi:
p[ry ]
rx
Il vantaggio introdotto dalla compressione dei percorsi si ha quando viene effettuata una
rango[r
abbiamo
rango[r]
= rango[rx ]; inoltre, per ipotesi induttiva:
y ] < rango[r
x ]: m
sequenza di operazioni formata da m•find
() e merge
(), con
⇥ n.che
Senza
la compressione
dei percorsi, la complessità dovuta all’esecuzione|X
della
sarebbe
ovviamente
x]
y]
⌅ Y sequenza
| = |X| + |Y
| 2rango[r
+ 2rango[rO(n
>+
2rango[rx ] = 2rango[r]
m log n), mentre con la compressione dei percorsi èrango[r
possibile
dimostrare che tale costo scende
y]
(in quanto 2
> 0)
ad O(n +Euristica
m (m, n)),del
dove
(m, n) è la funzione inversa della funzione di Ackermann.
10.2.3
rango
rango[rx ] < rango[r
y ]: simmetrico
(m, n) è una funzione che cresce•lentissimamente,
tanto
che (m,del
n) precedente;
4 per ogni m ed
Nella realizzazione basata su foresta, la creazione di un M FSET ha costo O(n), che è ovvia• rango[rx ] =Ne
rango[r
la radice
ry prende
rx come
nuovo figlio, e il suo rango viene
n rappresentabile con un calcolatore elettronico!
seguey ]:che
ciascuna
delle m
operazioni
mente ottimale; mentre il costo di find() e merge
()
dipende
dall’altezza
degli
alberi.
Purtroppo,
incrementato
di uno: rango[r]
=+
rango[r
1. Dall’ipotesi
y ] +n))/m),
find() e merge() della sequenza ha complessità
“ammortizzata”
O((n
m (m,
che èinduttiva, abbiamo che:
è possibile scegliere una sequenza particolarmente sfortunata di unioni, che
renda
l’albero
una
pressoché O(1) per ogni valore “realistico” di n |X
ed ⌅
m.Y | = |X| + |Y | 2rango[ry ] + 2rango[ry ] = 2rango[ry ]+1 = 2rango[r]
lista di lunghezza n; in questo caso, le operazioni find() and merge() hanno costo O(n).
Esempio 10.9 (Alberi di altezza massima). Nella merge(x, y), l’albero contenente y diviene
sempre figlio dell’albero contenente x; considerando il M FSET di Fig. 10.5(a), la sequenza di
operazioni merge(5, 6), merge(4, 5), merge(3, 4), merge(2, 3), merge(1, 2) produce un albero
46