ESERCIZI SUGLI HEAP BINOMIALI (CAPITOLO 20)
Catalano Pietro 56/100592
20.1-1
Supponiamo che x sia un nodo di un albero binomiale contenuto in uno heap
binomiale e che sibling[x] ≠ NIL. Se x non è una radice qual è la relazione tra
esistente tra degree[sibling[x]] e degree[x]? Cosa succede quando x è una radice?
Supponiamo che x non è una radice. Per una proprieta’ degli alberi binomiali, i figli
di p[x] sono in ordine radici di alberi binomiali di grado decrescente. Quindi
degree[x] – degree[sibling[x]] = 1. Quando invece x è una radice, allora degree[x] <
degree[sibling[x]].
20.1-2
Sia x un nodo diverso da un nodo radice, di un albero binomiale contenuto in uno
heap binomiale: qual è la relazione che esiste tra degree [p[x]] e degree[x]?
Ovviamente degree[p[x]] > degree[x]. Inoltre se x e’ l’i-esimo figlio da sinistra di
p[x] allora degree[p[x]] = degree[x] + i.
20.2-2
Si scrivano le istruzioni della procedura BINOMIAL-HEAP-MERGE.
BINOMIAL-HEAP-MERGE (H1, H2)
1. P1 = Head[H1]
2. P2 = Head[H2]
3. testa = NIL
4. if (P1 = = NIL)
5.
then testa = P2
6.
return testa
7. if (P2 = = NIL)
8.
then testa = P1
9.
return testa
10. if ( degree[P1] ≤ degree[P2] )
11.
then testa = P1
12.
P1 = sibling[P1]
13.
else testa = P2
14.
P2 = sibling[P2]
15. attuale = testa
16. while (P1 ≠ NIL and P2 ≠ NIL)
1
17.
do if ( degree[P1] ≤ degree[P2] )
18.
then sibling[attuale] = P1
19.
P1 = sibling[P1]
20.
else sibling[attuale] = P2
21.
P2 = sibling[P2]
22.
attuale = sibling[attuale]
23. if ( P1 = NIL )
24.
then while ( P2 ≠ NIL )
25.
do sibling[attuale] = P2
26.
P2 = sibling[P2]
27.
attuale = sibling[attuale]
28.
else while ( P1 ≠ NIL )
29.
do sibling[attuale] = P1
30.
P1 = sibling[P1]
31.
attuale = sibling[attuale]
32. return testa
20.2-3
Si mostri lo heap binomiale che si ottiene come risultato dell’operazione che inserisce
la chiave di valore 24 nello heap della figura 20.7(d).
Figura 20.7(d)
head[H]
25
6
12
37
18
10
41
16
26
23
28
77
8
13
11
14
17
29
38
27
42
2
head[H]
x
next-x
25
55
24
6
12
37
18
41
10
16
26
23
28
8
13
77
11
14
17
29
38
27
42
head[H]
x
next-x
24
25
6
12
37
18
10
41
16
26
23
28
77
8
13
11
14
17
29
38
27
42
L’algoritmo fa avanzare prev-x, x, next-x, fino a quando quest’ultimo diventa = NIL.
20.2-4
Si mostri lo heap binomiale che si ottiene come risultato dell’operazione che elimina
la chiave di valore 28 dallo heap di figura 20.8(c).
3
head[H]
25
12
37
6
18
7
41
10
16
8
28
23
13
11
77
14
17
29
38
27
42
head[H]
25
12
6
7
10
16
8
29
13
-∞
23
14
77
42
head[H]
25
12
6
-∞
10
16
7
23
13
77
4
42
head[H]
25
12
37
-∞
18
6
41
10
16
8
13
7
23
11
77
14
17
29
38
27
42
head[H]
-∞
head[H1]
25
12
37
29
14
18
38
41
8
11
erase
6
17
27
16
10
7
23
77
13
42
head[H]
x
next-x
25
29
14
38
caso 3
12
37
8
18
41
11
6
17
27
16
10
7
23
77
13
42
head[H]
x
next-x
25
14
29
38
12
37
41
8
18
11
27
6
17
16
10
7
23
77
13
caso 4
42
5
25
head[H]
x
next-x
14
12
38
37
29
8
18
11
41
6
17
27
16
casi 1 e 2
10
7
23
77
13
42
25
head[H]
prev-x
x
next-x
14
12
8
38
37
29
18
11
41
6
17
27
16
10
7
23
77
13
caso 4
42
25
head[H]
prev-x
x
next-x
14
8
6
12
38
37
29
11
18
17
27
16
41
caso 4
25
10
7
23
77
42
head[H]
prev-x
x
14
6
8
38
29
37
13
12
11
18
27
17
16
10
7
23
77
13
42
41
6
20.2-5
Si spieghi il motivo per cui la procedura BINOMIAL-HEAP-MINIMUM, non opera
correttamente in presenza di chiavi con valore uguale a ∞. Si riscrivano le istruzioni
della procedura in modo che possa operare correttamente anche in presenza di chiavi
con valore uguale a ∞.
BINOMIAL-HEAP-MINIMUM( H )
1. y = NIL
2. x = head[H]
3. min = ∞
4. while( x≠ NIL )
5.
do if (key[x] < min)
6.
then min =key[x]
7.
y=x
8.
x = sibling[x]
9. return y
L’inizializzazione di min a ∞, assieme all’istruzione if della linea 5, fa in modo che
l’algoritmo non sia corretto, quando consideriamo nodi con chiavi di valore ∞. In
questo caso l’errore consiste nel non considerare, candidato ad essere il minimo, un
nodo con valore di chiave ∞. Per permettere all’algoritmo di operare correttamente
anche in presenza di nodi con valore di chiave ∞, basta modificare la linea 5 in : do if
(key[x] ≤ min).
20.2-6
Supponiamo che non sia possibile rappresentare in nessun modo la chiave con valore
uguale a -∞. Si riscriva la procedura BINOMIAL-HEAP-DELETE, in modo tale che
possa operare correttamente anche in questo caso. La procedura deve essere eseguita
ancora in tempo Ο(lgn).
NEW-BINOMIAL-HEAP-DELETE ( H, x )
1. k = SEARCH-RADIX-MIN ( H )
2. BINOMIAL-HEAP-DECREASE-KEY ( H, x, k-1 )
3. BINOMIAL-HEAP-EXTRACT-MIN ( H )
SEARCH-RADIX-MIN ( H )
1. chiave = key[head[H]]
2. x = sibling[head[H]]
3. while ( x ≠ NIL )
4.
do if (key[x] < chiave )
5.
then chiave = key[x]
6.
x = sibling[x]
7. return chiave
7
La procedura SEARCH-RADIX-MIN, scandisce tutta la lista delle radici di uno
heap binomiale, tale lista ha al più taglia lgn+1, dove n è il numero di nodi nello
heap binomiale.Quindi la procedura impiega tempo Ο(lgn) per trovare il nodo con
chiave minima nella lista delle radici. Siccome le tre procedure richiamate da
NEW-BINOMIAL-HEAP-DELETE impiegano tutte tempo Ο(lgn), si conclude
che l’intero algoritmo impiega tempo Ο(lgn).
20.2-7
Si analizzino, le relazioni esistenti tra l’operazione che inserisce una chiave in uno
heap binomiale e l’operazione che incrementa un numero binario. Si analizzino le
relazioni esistenti tra l’operazione di unione tra due heap binomiali e l’operazione
di somma di due numeri binari.
Quando inseriamo una chiave in uno heap binomiale, uniamo un albero binomiale
B0; quando incrementiamo un numero binario sommiamo al numero originario un
1-bit di posizione 0.
L’inserimento di un albero binomiale B0, può violare la proprietà 2 degli heap
binomiali, caso in cui si ha più di un albero con lo stesso grado. Analogamente
l’incremento di un numero binario può generare un riporto, nel caso in cui il bit di
posizione 0 del numero originario sia un 1-bit.
Se in uno heap binomiale ci sono, (causa l’inserimento), 2 alberi binomiali, con lo
stesso grado, questi vengono fusi, (vedi BINOMIAL-HEAP-UNION) e il
controllo va avanti fino a quando la proprietà 2 non è più violata. Analogamente
quando nell’incremento di un numero binario viene generato un riporto in
posizione i, questo verrà sommato (propagato) al bit di posizione i+1. Il
procedimento si ripete fino a quando il riporto non viene più generato.
Si osservi che l’insieme degli alberi che compongono uno heap binomiale di n
elementi (nodi), può essere visto come una stringa binaria di lgn+1 elementi,
dove il bit di posizione i, per lgn < i ≤ 0, ci dice se nello heap binomiale esiste o
meno l’albero binomiale di grado i, a seconda che tale bit sia rispettivamente
settato a 1 o a 0. Questo ci ricorda che uno heap binomiale con n nodi, ha un unico
insieme di alberi binomiali possibili, che possono essere stabiliti codificando in
binario il numero n (decimale) su lgn+1 bits. Quindi incrementare il numero
binario che identifica l’insieme degli alberi di uno heap binomiale, stabilisce
quali alberi, faranno parte dello heap, nel caso in cui inseriamo una nuova
chiave.
Considerando i concetti precedenti, possiamo avere un idea della relazione che
intercorre tra l’operazione di unione di 2 heap binomiali H1 e H2, e l’operazione
di somma di 2 numeri binari S1 eS2.
8
Quando uniamo H1 e H2 per prima cosa abbiamo una nuova lista delle radici,
dove in ordine non decrescente di grado, abbiamo le radici che prima
appartenevano ai due heap. Subito dopo la creazione della nuova lista delle radici,
possiamo aver al più 2 radici aventi lo stesso grado, mentre ad un generico passo
successivo possiamo avere al più 3 radici di stesso grado. A questo punto,
iniziamo a considerare i 2 numeri binari S1 e S2.
Siano S1 e S2 le codifiche binarie che identificano univocamente H1 e H2,
secondo le idee illustrate prima.
Se dopo aver fuso la lista delle radici, esistono 2 alberi binari di grado 0, questa
situazione corrisponde all’esistenza dell’1-bit in posizione zero in entrambe le
stringhe. L’operazione da compiere ora, è fondere i due alberi di grado 0 per
creare un nuovo albero di grado 1. Analogamente nella somma dei due numeri
binari, questo è evidenziato dalla somma dei 2 1-bit di posizione 0 che genera un
riporto per la posizione 1 (equivale alla creazione del nuovo albero di grado 1) e
che scrive un bit 0 nella posizione 0 (che evidenzia l’assenza di alberi di grado 0. I
due precedenti sono stati distrutti).
1
B0
B0
Bk
…………S1 …. …..
… ….
S2
S
…
…
Lgn-1
…………
B1
k
…
….
…
….
1
1
… …
k-1
0
1
0
Bk
Quando ad un generico passo i, oltre ad avere gli 1-bit di posizione k, (che
equivalgono ai 2 alberi di grado k originari), possiamo avere anche l’1-bit di riporto
destinato alla cella k-esima (l’albero di grado k generato dalla fusione di 2 alberi di
grado k-1), l’operazione di somma scrive 1 nella cella k (viene conservato l’albero Bk
creato dalla fusione di 2 alberi di grado k-1) e genera un riporto di un 1-bit per la
cella k+1, (che evidenzia la creazione dell’albero di grado k+1 per mezzo della
fusione dei 2 alberi di grado k).
1
……
Bk
Bk
Bk
S1 …
S2 …
…
Lgn-1
1
1
1
1
1S
k
k-1
1
0
9
…………
Bk
Bk+1
Quindi sommando le stringhe S1 e S2, che evidenziano l’insieme degli alberi di H1 e
H2 rispettivamente, otteniamo la stringa S che identifica univocamente gli alberi che
compongono lo heap binomiale H, ottenuto dall’unione di H1 e H2.
20.2.8
Alla luce della risposta dell’esercizio 20.2-7, si scriva la procedura BINOMIALHEAP-INSERT, in modo da non richiedere l’uso della procedura BINOMIALHEAP-UNION.
NEW-BINOMIAL-HEAP-INSERT( H, x)
1. p[x] = NIL
2. child[x] = NIL
3. sibling[x] = NIL
4. degree[x] = 0
5. secondo = head[H]
6. primo = x
7. testa = primo
8. while( secondo ≠ NIL)
9. do if( degree[primo] = = degree[secondo])
10.
then if (key[primo] <= key[secondo])
11.
then sibling[primo] = sibling[secondo]
12.
BINOMIAL-LINK( secondo, primo )
13.
else testa = secondo
14.
BINOMIAL-LINK( primo, secondo )
15.
primo=secondo
16.
secondo = sibling[secondo]
17. head[H]=testa
18. return H
20.2-9
Si mostri, che se le liste delle radici sono ordinate per grado decrescente, tutte le
operazioni su heap binomiali possono essere realizzate senza alcuna modifica del
tempo di esecuzione asintotico.
Poiché il numero delle radici è proporzionale a Ο(lgn), in un albero binomiale con
n nodi, possiamo continuare ad adoperare gli stessi algoritmi, invertendo la lista delle
radici. Quest’operazione viene effettuata in tempo Ο(lgn) e quindi non viene
modificato il tempo asintotico degli algoritmi.
10