Salta al contenuto
Note per Studenti Costruzione di uno heap

Costruzione di uno heap

In questa pagina 4

Problema. Input: array P[1..n]P[1..n] di entry. Output: lo stesso array riorganizzato in modo da rappresentare uno heap (vedi HeapAlbero binario completo e sua altezza floor(log2 n); heap = albero completo con heap-order property; proprietà (radice minima, cammini non decrescenti); rappresentazione su array con level numbering; insert con up-heap bubbling, removeMin con down-heap bubbling, rimozione di una entry qualsiasi; invarianti e costi Theta(log n); esempio svolto.Heap →). Entrambe le soluzioni lavorano in loco (senza array aggiuntivi). L'up-heap e il down-heap bubbling sono quelli di HeapAlbero binario completo e sua altezza floor(log2 n); heap = albero completo con heap-order property; proprietà (radice minima, cammini non decrescenti); rappresentazione su array con level numbering; insert con up-heap bubbling, removeMin con down-heap bubbling, rimozione di una entry qualsiasi; invarianti e costi Theta(log n); esempio svolto.Heap →; per i conti sulle somme servono SommatorieIl simbolo di sommatoria, le sue proprietà (linearità, additività, cambio di indice) e le somme notevoli di Gauss e geometrica.Sommatorie → e la serie geometrica (Serie notevoli - geometrica, telescopica, armonicaLe serie di cui si conosce il carattere e da usare come termine di paragone: geometrica (converge a 1/(1-q) se |q|<1), telescopiche (somma b_1 - lim b_n, come Mengoli), armonica generalizzata (1/n^alpha converge se e solo se alpha>1).Serie notevoli - geometrica, telescopica, armonica →), e per le dimostrazioni per induzione Principio di induzionePer dimostrare una proprietà per ogni n basta il passo base e il passo induttivo da n a n+1.Principio di induzione →.

Top-down: n−1n-1 inserimenti successivi

Definizione (costruzione top-down con inserimenti successivi). Si considera P[1]P[1] uno heap di un elemento e si inserisce P[2],P[3],…,P[n]P[2], P[3], \dots, P[n] con l'up-heap bubbling.

Esempio. Con P=[3,2,1]P=[3,2,1]: P[1]=3P[1]=3 è uno heap; insert di 22 lo fa salire sopra 33 ([2,3][2,3]); insert di 11 sale fino alla radice ([1,3,2][1,3,2]).

for j <- 2 to n do
    up-heap bubbling a partire da P[j]

Invariante (alla fine dell'iterazione jj): P[1..j]P[1..j] rappresenta uno heap, P[j+1..n]P[j+1..n] è immutato.

Formula (costo della costruzione top-down). Costo. L'iterazione jj costa O(log⁡j)O(\log j), quindi O(∑j=2nlog⁡j)=O(nlog⁡n)O\big(\sum_{j=2}^{n} \log j\big) = O(n \log n). È anche Ω(nlog⁡n)\Omega(n \log n): se PP è in ordine decrescente ogni nuova chiave è minore di tutte le precedenti e sale fino alla radice, e

∑j=⌊n/2⌋nlog⁡2j ≥ n2log⁡2⌊n2⌋∈Ω(nlog⁡n).\sum_{j=\lfloor n/2 \rfloor}^{n} \log_2 j \ \ge\ \frac n2 \log_2 \Big\lfloor \frac n2 \Big\rfloor \in \Omega(n \log n).

Quindi Θ(nlog⁡n)\Theta(n \log n) (anche nn insert consecutive in uno heap vuoto, vedi Esercizio 13 · costruzione di uno heap top-down e bottom-up).

Perché la disuguaglianza della somma. I termini log⁡2j\log_2 j con ⌊n/2⌋≤j≤n\lfloor n/2 \rfloor \le j \le n sono almeno n/2n/2 (per nn pari sono n/2+1n/2+1) e ciascuno vale almeno log⁡2⌊n/2⌋\log_2\lfloor n/2\rfloor, perché il logaritmo è crescente (Esponenziale e logaritmoLa funzione esponenziale a^x (base positiva diversa da 1) e la sua inversa, il logaritmo in base a, con grafici e proprietà.Esponenziale e logaritmo →). Inoltre log⁡2⌊n/2⌋≈log⁡2n−1\log_2\lfloor n/2\rfloor \approx \log_2 n - 1, che per nn grande è Θ(log⁡n)\Theta(\log n): il prodotto è Ω(nlog⁡n)\Omega(n\log n). Con l'array decrescente ogni iterazione jj fa tanti scambi quanti il livello del nodo jj, cioè ⌊log⁡2j⌋\lfloor\log_2 j\rfloor.

Esempio. P=[2,10,27,9,11,13,5]P = [2, 10, 27, 9, 11, 13, 5]: dopo j=2,3j = 2,3: [2,10,27][2, 10, 27]; j=4j = 4 (99 sale sopra 1010): [2,9,27,10][2, 9, 27, 10]; j=5j = 5: [2,9,27,10,11][2, 9, 27, 10, 11]; j=6j = 6 (1313 sopra 2727): [2,9,13,10,11,27][2, 9, 13, 10, 11, 27]; j=7j = 7 (55 sopra 1313, poi 5>25 > 2 stop): [2,9,5,10,11,27,13][2, 9, 5, 10, 11, 27, 13]. Gli scambi sono stati 0,0,1,0,1,10,0,1,0,1,1, in tutto 33.

Bottom-up: dalle foglie verso la radice

Definizione (costruzione bottom-up). Le posizioni ⌊n/2⌋+1,…,n\lfloor n/2 \rfloor + 1, \dots, n sono foglie e quindi già heap da un elemento. Si procede a ritroso sui nodi interni, "sistemando" ciascuno con il down-heap bubbling: ogni nodo ha come sottoalberi due heap già costruiti.

Esempio. Con n=6n=6 si ha ⌊n/2⌋=3\lfloor n/2\rfloor=3: P[4..6]P[4..6] sono foglie e si sistemano j=3,2,1j=3,2,1 con il down-heap bubbling.

for j <- floor(n/2) downto 1 do
    down-heap bubbling a partire da P[j]

Invariante (alla fine dell'iterazione jj): P[j..n]P[j..n] rappresenta una foresta di heap, P[1..j−1]P[1..j-1] è immutato. Alla fine j=1j = 1 e P[1..n]P[1..n] è uno heap.

Formula (costo della costruzione bottom-up). Costo. Sia h=⌊log⁡2n⌋h = \lfloor \log_2 n \rfloor. Il down-heap da un nodo al livello ii (0≤i≤h−10 \le i \le h-1) costa O(h−i)O(h - i) (al più la distanza dalle foglie), e al livello ii ci sono al più 2i2^i nodi. Il costo totale è

O(∑i=0h−12i(h−i))=O(2h∑i=0h−1h−i2h−i)=O(2h∑ℓ=1hℓ2ℓ).O\Big( \sum_{i=0}^{h-1} 2^i (h - i) \Big) = O\Big( 2^h \sum_{i=0}^{h-1} \frac{h - i}{2^{h-i}} \Big) = O\Big( 2^h \sum_{\ell=1}^{h} \frac{\ell}{2^\ell} \Big).

Si dimostra per induzione che ∑ℓ=1hℓ2ℓ=2−h+22h<2\sum_{\ell=1}^{h} \frac{\ell}{2^\ell} = 2 - \frac{h+2}{2^h} < 2: per h=1h = 1 è 12=2−32\frac12 = 2 - \frac32; passando da hh a h+1h+1 si somma h+12h+1\frac{h+1}{2^{h+1}} e si ottiene 2−2(h+2)−(h+1)2h+1=2−h+32h+12 - \frac{2(h+2) - (h+1)}{2^{h+1}} = 2 - \frac{h+3}{2^{h+1}}. (Nel corso si dimostra la limitazione più debole ∑ℓ(1/2)ℓ<3\sum \ell (1/2)^\ell < 3, che basta.) Dunque il costo è O(2h)=O(n)O(2^h) = O(n), perché 2h≤n2^h \le n.

Come si arriva alla somma ∑ℓ/2ℓ\sum \ell/2^\ell. Un nodo al livello ii ha sotto di sé al massimo h−ih-i livelli, quindi il suo down-heap fa al più h−ih-i scambi. Con la sostituzione ℓ=h−i\ell=h-i (distanza dalle foglie) si ha 2i=2h−ℓ=2h/2ℓ2^i=2^{h-\ell}=2^h/2^\ell, da cui ∑i2i(h−i)=2h∑ℓℓ/2ℓ\sum_i 2^i(h-i)=2^h\sum_{\ell}\ell/2^\ell. Il punto chiave è che il peso ℓ\ell cresce linearmente mentre il numero di nodi con quella distanza si dimezza a ogni passo verso l'alto: la serie converge (vedi Criteri del rapporto e della radice per le seriePer una serie a termini positivi, se il limite di a_(n+1)/a_n (criterio del rapporto) o di radice n-esima di a_n (criterio della radice) è L < 1 la serie converge, se è L > 1 diverge; con L = 1 il criterio non dice nulla.Criteri del rapporto e della radice per le serie →: il rapporto tra termini consecutivi tende a 12<1\frac12<1) e la somma resta limitata da una costante. Controllo con h=3h=3: 12+24+38=118=2−58\frac12+\frac24+\frac38=\frac{11}{8}=2-\frac{5}{8}, coerente con la formula 2−h+22h=2−582-\frac{h+2}{2^h}=2-\frac58. Ed è Ω(n)\Omega(n): servono almeno ⌊n/2⌋\lfloor n/2 \rfloor iterazioni. Θ(n)\Theta(n).

Perché il bottom-up è più veloce. Nel top-down le iterazioni costose sono molte: i 2i2^i nodi del livello ii costano Θ(i)\Theta(i) ciascuno (più sono numerosi, più costano). Nel bottom-up i livelli più numerosi (vicini alle foglie) costano poco, Θ(h−i)\Theta(h - i), e solo i pochi nodi in alto costano Θ(log⁡n)\Theta(\log n).

Grafico interattivo: Scambi al caso peggiore nella costruzione di uno heap con n elementi: top-down su array decrescente, (n+1)·⌊log₂n⌋ − 2^(⌊log₂n⌋+1) + 2 (34 per n = 15, 258 per n = 63, 1538 per n = 255), bottom-up al più n − 1 (somma delle altezze dei nodi: 11, 57 e 247 se n = 2^k − 1)

Numero massimo di scambi del bottom-up, per n=15n=15 (albero perfetto, h=3h=3): 88 foglie a distanza 00 dal fondo, 44 nodi con al più 11 scambio, 22 con al più 22, 11 con al più 33: 4⋅1+2⋅2+1⋅3=114\cdot1+2\cdot2+1\cdot3=11, contro i 3434 del top-down sull'array decrescente. Per questo il costo è lineare e non nlog⁡nn\log n.

Esempio. P=[2,10,27,9,11,13,5,30,18,1,14,15,6]P = [2, 10, 27, 9, 11, 13, 5, 30, 18, 1, 14, 15, 6] (n=13n = 13, ⌊n/2⌋=6\lfloor n/2 \rfloor = 6):

Dopo jj Array
6 [2,10,27,9,11,6,5,30,18,1,14,15,13][2, 10, 27, 9, 11, 6, 5, 30, 18, 1, 14, 15, 13] (13↔613 \leftrightarrow 6)
5 [2,10,27,9,1,6,5,30,18,11,14,15,13][2, 10, 27, 9, 1, 6, 5, 30, 18, 11, 14, 15, 13] (11↔111 \leftrightarrow 1)
4 invariato (9<30,189 < 30, 18)
3 [2,10,5,9,1,6,27,30,18,11,14,15,13][2, 10, 5, 9, 1, 6, 27, 30, 18, 11, 14, 15, 13] (27↔527 \leftrightarrow 5)
2 [2,1,5,9,10,6,27,30,18,11,14,15,13][2, 1, 5, 9, 10, 6, 27, 30, 18, 11, 14, 15, 13] (10↔110 \leftrightarrow 1)
1 [1,2,5,9,10,6,27,30,18,11,14,15,13][1, 2, 5, 9, 10, 6, 27, 30, 18, 11, 14, 15, 13] (2↔12 \leftrightarrow 1)

Un altro esempio (da un tema d'esame): P=[11,9,7,13,3,4]P = [11, 9, 7, 13, 3, 4] con ⌊6/2⌋=3\lfloor 6/2 \rfloor = 3: j=3j = 3: P[3]=7P[3]=7 ha come unico figlio P[6]=4<7P[6]=4<7, scambio, [11,9,4,13,3,7][11, 9, 4, 13, 3, 7]; j=2j = 2: P[2]=9P[2]=9 ha figli P[4]=13P[4]=13 e P[5]=3P[5]=3, il minore è 3<93<9, scambio, [11,3,4,13,9,7][11, 3, 4, 13, 9, 7]; j=1j = 1: P[1]=11P[1]=11 ha figli 33 e 44, minore 33, scambio (ora 1111 è in posizione 22 con figli 1313 e 99), minore 9<119<11, scambio, [3,9,4,13,11,7][3, 9, 4, 13, 11, 7] (1111 scende scambiando con 33, poi con 99). Totale 44 scambi.

Altre costruzioni

Fusione di due heap come alberi (non necessariamente completi, altezze h1,h2h_1, h_2) in tempo O(h1+h2)O(h_1 + h_2): si rimuove una foglia vv da T1T_1, si crea una nuova radice con la entry di vv e come figli T1T_1 e T2T_2, poi si esegue il down-heap dalla radice.

Definizione (heap sort). Costruzione bottom-up Θ(n)\Theta(n) e poi nn removeMin da Θ(log⁡n)\Theta(\log n): Θ(nlog⁡n)\Theta(n \log n) (vedi Algoritmi di ordinamentoSelection sort e insertion sort (quadratici, con invarianti), merge sort (divide et impera, Theta(n log n)), quick sort (caso pessimo quadratico, medio n log n), heap sort (in loco, Theta(n log n)), ordinamento senza confronti per chiavi intere in un intervallo piccolo, limite inferiore Omega(n log n) per gli algoritmi basati su confronti.Algoritmi di ordinamento →).

Esempio. Con n=1000n=1000 chiavi: costruzione bottom-up Θ(n)\Theta(n) e poi 10001000 removeMin, ciascuno con circa log⁡21000≈10\log_21000\approx10 scambi al più.

Errori comuni

  • Eseguire il bottom-up partendo dalla fine dell'array (le foglie sono già heap): si parte da ⌊n/2⌋\lfloor n/2 \rfloor.
  • Percorrere jj in avanti nel bottom-up: il down-heap richiede che i sottoalberi siano già heap.
  • Dire che il bottom-up costa nlog⁡nn \log n perché ogni down-heap è log⁡n\log n: la somma dei costi per livello è lineare.
  • Dimenticare il caso peggiore del top-down: array in ordine decrescente.

Versione ripasso

Esercizi su questo argomento

Teoria collegata