Ordinamento: generalità Spesso si tratta di ordinare non già dei

Ordinamento: generalità
Spesso si tratta di ordinare non già dei singoli valori ma dei record in base al
valore assunto da un campo chiave → il metodo usato per effettuare
l’ordinamento non cambia ma è più efficiente permutare un array di puntatori ai
record piuttosto che i record stessi
Marina Zanella – Algoritmi e strutture dati – Ordinamento
1
Heap
Attenzione! Qui heap è una struttura dati contenuta in un array, non una zona di
memoria gestita da un garbage-collector
Un array A che rappresenta uno heap è un oggetto con due attributi:
• length[A] = n° elementi di A
• heap-size[A] = n° elementi dello heap, memorizzati in A[1 .. heap-size[A]]
dove heap-size[A] ≤ length[A]
Marina Zanella – Algoritmi e strutture dati – Ordinamento
2
Heap binario
Albero binario riempito completamente su tutti i livelli tranne, eventualmente, il
più basso che è riempito da sx in poi
radice dell’albero = elemento A[1]
nodo dell’albero = elemento del sottoarray A[1 .. heap-size[A]]
Sussiste il vincolo che il valore del genitore è sempre ≥ di quello del figlio
(proprietà dello heap, vedi oltre) → il valore massimo dello heap è memorizzato
nella radice
Marina Zanella – Algoritmi e strutture dati – Ordinamento
3
Esempio: heap binario
Esempio
1 2
3 4 5 6 7 8 9 10 11 12
A = <16,14,10,8,7,9,3,2,4, 1, 5,15>
heap-size[A] = 10
1
16
4
2
3
14
10
5
8
7
8
7
6
1
9
3
9
10
2
4
1
Marina Zanella – Algoritmi e strutture dati – Ordinamento
4
Heap binario: operazioni
PARENT(i)
restituisce l’indice del padre del nodo di indice i
return i/2
realizzabile come macro con lo scorrimento della codifica binaria di i di una
posizione verso dx e aggiunta di 0 come bit più significativo
Proprietà (di ordinamento parziale) dello heap:
∀i >1, A[PARENT(i)] ≥ A[i]
Marina Zanella – Algoritmi e strutture dati – Ordinamento
5
Heap binario: operazioni (cont.)
RIGHT(i)
restituisce l’indice del figlio destro
return 2i+1
realizzabile come macro con lo scorrimento della codifica binaria di i di una
posizione verso sx e aggiunta di 1 come bit meno significativo
LEFT(i)
restituisce l’indice del figlio sinistro
return 2i
realizzabile come macro con lo scorrimento della codifica binaria di i di una
posizione verso sx e aggiunta di 0 come bit meno significativo
Marina Zanella – Algoritmi e strutture dati – Ordinamento
6
Heap: altezza
Altezza di un nodo di un albero = n° di archi sul più lungo cammino discendente
che va dal nodo a una foglia
Altezza di un albero = altezza della radice dell’albero
↓
Altezza di uno heap binario di n elementi = lgn = Θ( lgn)
Le operazioni di base sugli heap hanno un tempo di esecuzione al più
proporzionale all’altezza dell’albero e quindi impiegano un tempo dell’ordine di
O(lgn)
Marina Zanella – Algoritmi e strutture dati – Ordinamento
7
HEAPIFY
Procedura che acquisisce in ingresso un array A e un indice i di tale array, dove
si assume che gli alberi binari con radice in LEFT(i) e RIGHT(i) siano heap ma
A[i] possa essere più piccolo dei suoi figli → A[i] viene fatto “scendere” in
modo tale che il sottoalbero con radice in A[i] diventi uno heap
Dimensione dell’input = n° elementi del sottoalbero con radice in A[i] contenuto
nelle prime heap-size[A] posizioni di A
Marina Zanella – Algoritmi e strutture dati – Ordinamento
8
HEAPIFY (cont.)
HEAPIFY(A, i)
1 l ← LEFT(i)
2 r ← RIGHT(i)
3 if l ≤ heap-size[A] e A[l] > A[i]
4 then largest ← l
5 else
largest ← i
6 if r ≤ heap-size[A] e A[r] > A[largest]
7 then largest ← r
8
in largest è ora memorizzato d’indice del
maggiore fra A[i], A[LEFT(i)] e A[RIGHT(i)]
9 if largest ≠ i
10 then scambia A[i] ↔ A[largest]
11
HEAPIFY(A, largest)
Θ(1)
T(2n/3)
2n/3 = caso pessimo, dimensione max del sottoalbero (si verifica quando
l’ultimo livello dell’albero è pieno esattamente a metà)
Marina Zanella – Algoritmi e strutture dati – Ordinamento
9
HEAPIFY (cont.)
Ricorrenza: T(n) ≤ T(2n/3) + Θ(1)
↓
Soluzione: T(n) = O(lgn) (ovvero, T(n) = O(h) su un nodo di altezza h)
Marina Zanella – Algoritmi e strutture dati – Ordinamento
10
1
Esempio: HEAPIFY(A, 2)
16
2
3
1
i
4
4
10
14
7
8
7
6
1
5
3
9
9
4
10
2
8
1
2
3
14
10
7
7
8
7
6
1
9
10
4
10
7
6
1
3
9
10
9
2
14
9
2
5
8
3
5
8
16
8
2
4
1
4
16
1
Marina Zanella – Algoritmi e strutture dati – Ordinamento
1
Chiamata ricorsiva di
HEAPIFY(A, 4)
3
Chiamata ricorsiva di
HEAPIFY(A, 9)
11
Costruzione di uno heap binario a partire da un array
BUILD-HEAP(A)
1 heap-size[A] ← length[A]
2 for i ← length[A] / 2 downto 1
3 do HEAPIFY(A, i)
4
gli elementi del sottoarray A[length[A] / 2 + 1 .. length[A]]
sono tutte foglie → ciascuno di essi è uno heap di un solo elemento
Marina Zanella – Algoritmi e strutture dati – Ordinamento
12
1
Esempio: BUILD-HEAP(A)
Chiamata di
HEAPIFY(A, 5)
4
Situazione
iniziale
2
3
1
4
i
2
8
5
6
1
16
9
7
4
2
10
9
10
14
8
7
i
3
i
1
8
5
6
1
16
9
9
10
8
5
6
1
16
9
7
10
9
10
2
14
3
2
8
4
4
3
1
4
1
2
1
3
14
8
3
7
10
7
Chiamata di
HEAPIFY(A, 4),
che dà luogo
alla chiamata
ricorsiva di
HEAPIFY(A, 8)
7
Marina Zanella – Algoritmi e strutture dati – Ordinamento
13
Chiamata di HEAPIFY(A, 2),
che dà luogo alle chiamate
ricorsive di HEAPIFY(A, 5) e
HEAPIFY(A, 10)
1
Esempio: BUILD-HEAP(A)
(cont.)
2
i
Chiamata di
HEAPIFY(A, 3)
che dà luogo
alla chiamata
8
ricorsiva di
HEAPIFY(A, 7)
2
4
3
1
4
10
7
6
1
5
14
1
16
3
9
9
10
8
4
7
4
2
3
16
10
5
14
1
2
3
14
10
5
8
7
8
9
10
4
3
9
9
2
1
Marina Zanella – Algoritmi e strutture dati – Ordinamento
8
1
7
6
1
9
2
7
6
1
10
16
4
7
8
i
3
Chiamata di HEAPIFY(A, 1), che dà
luogo alle chiamate ricorsive di
HEAPIFY(A, 2),
HEAPIFY(A, 4) e
HEAPIFY(A, 9)
14
Analisi di BUILD-HEAP
HEAPIFY: T(n) = O(h) su un nodo di altezza h →
BUILD-HEAP: T(n) =
lg n 
lg n 
 ∞ h 
h
 n 
 = O ( n)
∑
h
 2 h +1 O (h ) = O (n ∑ 2 h ) = O n∑
h =0 2 
h =0
h =0
∞
 n 
 2 h +1 
= n° max di
nodi di altezza h
Marina Zanella – Algoritmi e strutture dati – Ordinamento
h
1/ 2
=
=2
∑
h
2
2
(1 − 1 / 2)
h=0
15
Ordinamento di un vettore: HEAPSORT
Dato un array A, lo trasforma in uno heap; poi scambia l’elemento radice A[1],
che è il max, con l’ultimo elemento dell’array, quindi trasforma in uno heap il
sottoarray A[1 .. length[A] – 1], scambia l’elemento radice con l’ultimo
elemento del sottoarray, quindi considera il sottoarray decurtato dell’elemento
finale, e così via. Alla fine, A contiene gli elementi ordinati in ordine non
decrescente.
HEAPSORT(A)
1 BUILD-HEAP(A)
2 for i ← length[A] downto 2
3 do scambia A[1] ↔ A[i]
4
heap-size[A] ← heap-size[A] – 1
5
HEAPIFY(A, 1)
O(n)
O(lgn)
T(n) = O(n) + (n – 1)O(lgn) = O(nlgn) (come MERGE-SORT)
Marina Zanella – Algoritmi e strutture dati – Ordinamento
16
1
Esempio: HEAPSORT(A)
16
2
3
14
7
4
5
6
2
1
1
2
3
8
7
4
14
2
3
14
1
i=6
1
7
4
5
2
8
5
2
8
1
16
Marina Zanella – Algoritmi e strutture dati – Ordinamento
17
Esempio: HEAPSORT(A) (cont.)
i=5
1
1
2
2
3
8
14
2
3
2
8
14
16
1
7
2
7
7
1
i=4
1
3
4
2
2
8
2
7
4
1
1
3
1
16
Marina Zanella – Algoritmi e strutture dati – Ordinamento
18
Esempio: HEAPSORT(A) (cont.)
1
i=3
1
1
2
2
2
2
1
7
8
1
14
16
i=2
1
2
7
8
14
16
Marina Zanella – Algoritmi e strutture dati – Ordinamento
19
Confronto
T(n)
INSERTION-SORT O(n2)
MERGE-SORT
O(nlgn)
HEAPSORT
O(nlgn)
Metodo
“in loco”
non “in loco”
“in loco”
→ HEAPSORT presenta i vantaggi dei due altri algoritmi di ordinamento
Marina Zanella – Algoritmi e strutture dati – Ordinamento
20
Coda con priorità
È uno degli impieghi più conosciuti dello heap; consente di mantenere un
insieme S di elementi, ciascuno dotato di chiave, attraverso le seguenti
operazioni:
INSERT(S, X):
inserisce l’elemento x
l’ordinamento delle chiavi
nell’insieme
S,
MAXIMUM(S):
restituisce l’elemento di S con chiave più grande
rispettando
EXTRACT-MAX(S): rimuove e restituisce l’elemento di S con chiave più grande
Es. di applicazioni delle code con priorità:
• allocazione di processi su un calcolatore (chiave = priorità relativa)
• simulazione di un sistema guidato da eventi (chiave = istante di occorrenza di
un evento); in questo caso, si usano le operazioni duali MINIMUM e
EXTRACT-MIN
Marina Zanella – Algoritmi e strutture dati – Ordinamento
21
Coda con priorità (cont.)
HEAP-EXTRACT-MAX(A)
1 if heap-size[A] < 1
2 then error “heap underflow”
3 max ← A[1]
4 A[1] ← A[heap-size[A]]
5 heap-size[A] ← heap-size[A] – 1
6 HEAPIFY(A, 1)
7 return max
O(1)
O(lgn)
T(n) = O(lgn)
Marina Zanella – Algoritmi e strutture dati – Ordinamento
22
Coda con priorità (cont.)
HEAP-INSERT(A, key)
1 heap-size[A] ← heap-size[A] + 1
2 i ← heap-size[A]
3 while i > 1 e A[PARENT(i)] < key
4 do A[i] ← A[PARENT(i)]
5
i ← PARENT(i)
6 A[i] ← key
T(n) = O(lgn) (cammino seguito da una nuova foglia fino alla radice)
Marina Zanella – Algoritmi e strutture dati – Ordinamento
23
Esempio: HEAP-INSERT(A, 15)
16
14
10
8
2
7
4
3
9
2
2
i
1
i
4
1
3
9
7
10
14
4
10
8
16
8
14
i
1
15
16
9
3
7
Marina Zanella – Algoritmi e strutture dati – Ordinamento
24