Salta al contenuto
Note per Studenti Esercizio 14 · k-esimo elemento più piccolo con uno heap

Esercizio 14k-esimo elemento più piccolo con uno heap

Esame
In questa pagina 6

Testo (esempio di tema d'esame, seconda parte, esercizio 1, 5 punti). Progettare un algoritmo che calcola il kk-esimo elemento più piccolo di un insieme di nn interi distinti, in tempo O(n+klog⁡n)O(n + k \log n) e senza ricorrere all'ordinamento.


Idea

Il termine nn suggerisce una costruzione bottom-up di uno heap, che costa Θ(n)\Theta(n); il termine klog⁡nk \log n suggerisce kk operazioni di removeMin, ciascuna Θ(log⁡n)\Theta(\log n). Il minimo estratto per kk-esimo è il kk-esimo più piccolo (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 → e Costruzione di uno heapCostruire uno heap da un array di n entry: approccio top-down con n-1 insert, Theta(n log n); approccio bottom-up con down-heap dalle foglie verso la radice, Theta(n), con dimostrazione della somma; esempi svolti, in loco; unione di due alberi con heap-order.Costruzione di uno heap →). Ordinare tutto l'insieme costerebbe Θ(nlog⁡n)\Theta(n \log n), peggio quando kk è piccolo.

Algoritmo

Algoritmo kEsimoMinimo(A, n, k)
Input: array A[1..n] di interi distinti, intero 1 <= k <= n
Output: il k-esimo elemento più piccolo di A
P <- A                                    (lavoro in loco sull'array)
for j <- floor(n/2) downto 1 do           (costruzione bottom-up di un min-heap)
    down-heap bubbling a partire da P[j]
last <- n
for t <- 1 to k - 1 do
    P[1] <- P[last]; last <- last - 1     (removeMin, scartando il minimo)
    down-heap bubbling a partire da P[1]
return P[1]                               (il k-esimo minimo: la radice)

L'ultima operazione non serve completarla: dopo k−1k - 1 rimozioni la radice dello heap è il kk-esimo più piccolo (equivalentemente, si può fare removeMin() kk volte e restituire l'ultimo valore estratto).

Correttezza. Lo heap contiene sempre gli elementi non ancora scartati. Dopo la costruzione la radice è il minimo (1° più piccolo). A ogni removeMin si toglie il minimo corrente, quindi dopo tt rimozioni sono stati tolti i tt elementi più piccoli e la radice è il (t+1)(t+1)-esimo. Dopo k−1k - 1 rimozioni la radice è il kk-esimo.

Esempio

A=[7,2,9,4,1,8]A = [7, 2, 9, 4, 1, 8], k=3k = 3.

  • Bottom-up (⌊6/2⌋=3\lfloor 6/2 \rfloor = 3): j=3j = 3: [7,2,8,4,1,9][7, 2, 8, 4, 1, 9]; j=2j = 2: [7,1,8,4,2,9][7, 1, 8, 4, 2, 9]; j=1j = 1: [1,2,8,4,7,9][1, 2, 8, 4, 7, 9] (il 77 scende prima scambiandosi con 11, poi con 22).
  • Rimozione 1 (si scarta 11): 99 in radice, [9,2,8,4,7]→[2,9,8,4,7]→[2,4,8,9,7][9, 2, 8, 4, 7] \to [2, 9, 8, 4, 7] \to [2, 4, 8, 9, 7].
  • Rimozione 2 (si scarta 22): 77 in radice, [7,4,8,9]→[4,7,8,9][7, 4, 8, 9] \to [4, 7, 8, 9].
  • La radice è 44: il terzo più piccolo, in accordo con l'ordinamento 1,2,4,7,8,91, 2, 4, 7, 8, 9.

Complessità

Totale: O(n+klog⁡n)O(n + k \log n). Per k=O(n/log⁡n)k = O(n / \log n) è lineare. Se kk è vicino a nn non conviene rispetto all'ordinamento (O(nlog⁡n)O(n \log n) in entrambi i casi), ma la richiesta dell'esercizio è proprio questo limite.

Varianti

Con una coda con priorità su array non ordinato la costruzione è banale ma ogni removeMin costa Θ(n)\Theta(n): Θ(kn)\Theta(kn). Con lista ordinata si paga Θ(nlog⁡n)\Theta(n \log n) per ordinare. Lo heap bilancia i costi (vedi Code con prioritàEntry chiave-valore; ADT coda con priorità (insert, min, removeMin) con chiave minima = priorità massima; esempio di esecuzione; applicazioni; implementazioni con lista non ordinata e ordinata e relativi costi; ordinamento tramite coda con priorità.Code con priorità →).

Errori comuni

  • Costruire lo heap con nn insert (top-down): Θ(nlog⁡n)\Theta(n \log n), che viola il limite richiesto.
  • Errore di uno: se si fanno kk estrazioni e si restituisce la radice dopo l'ultima, si ottiene il (k+1)(k+1)-esimo; vale o il valore estratto alla kk-esima estrazione, o la radice dopo k−1k - 1 estrazioni.
  • Usare un max-heap senza adattare kk (con un max-heap si cerca il kk-esimo più grande).
  • Dimenticare che gli elementi sono distinti: con ripetizioni "kk-esimo più piccolo" va ridefinito.

Versione ripasso

Testo. kk-esimo elemento più piccolo di nn interi distinti in O(n+klog⁡n)O(n + k \log n), senza ordinare.

Teoria collegata