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