Il problema dell’ordinamento
Una rapida panoramica (con qualche dettaglio)
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Il problema dell’ordinamento
Dato un insieme S di n oggetti presi da un
dominio totalmente ordinato, ordinare S
Esempi: ordinare una lista di nomi alfabeticamente, o un insieme di
numeri, o un insieme di compiti d’esame in base al cognome
dello studente
Si noti che:
• E’ un sottoproblema in molti problemi
• E’ possibile effettuare ricerche in array ordinati
in tempo O(log n)
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Algoritmi e tempi tipici
• Numerosissimi algoritmi
• Tre tempi tipici: O(n2), O(n log n), O(n)
Qualche esempio (per confrontare gli ordini di grandezza):
n
10
100
n log2 n
~ 33
~ 665
n2
100
104
1000
106
109
~ 104 ~ 2·107 ~ 3·1010
106
1012
1018
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Il modello basato su confronti
• In questo modello, per ordinare è possibile
usare solo confronti tra oggetti
• Primitive quali operazioni aritmetiche
(somme o prodotti), logiche (and e or), o altro
(shift) sono proibite
• Sufficientemente generale per catturare le
proprietà degli algoritmi più noti (e usati)
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Lower bound per il problema dell’ordinamento
(nel modello basato su confronti)
• Lower bound: delimitazione inferiore alla
quantità di una certa risorsa di calcolo
necessaria per risolvere il problema
• Ω(n log n) è un lower bound al numero di
confronti richiesti per ordinare n oggetti (nel
modello basato su confronti)
• Consideriamo un generico algoritmo A, che
ordina eseguendo solo confronti: dimostreremo
che A esegue Ω(n log n) confronti
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Albero di decisione per il problema dell’
ordinamento (nel modello basato su confronti)
Descrive le diverse sequenze di confronti che A
potrebbe fare su istanze di lunghezza n
1:2
≤Š
2:3
Š
≤
1,2,3
≤Š
1,3,2
1:3
>
1:3
>
≤Š
>
3,1,2
2,1,3
≤
Š
2,3,1
>
2:3
>
3,2,1
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Proprieta’:
• Per una particolare istanza, i confronti eseguiti
da A su quella istanza rappresentano un
cammino radice - foglia
• Il numero di confronti nel caso peggiore è pari
all’altezza dell’albero di decisione
• Un albero di decisione per l’ordinamento di n
elementi contiene almeno n! foglie
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Altezza in funzione delle foglie:
• Un albero binario con k foglie tale che ogni
nodo interno ha esattamente due figli ha
altezza almeno log2 k
• Dimostrazione per induzione su k
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Il lower bound Ω(n log n)
• L’altezza dell’albero di decisione è almeno
log2 (n!)
• Formula di Stirling: n! ~ (2πn)1/2 ·(n/e)n
Quindi: log (n!) ~ n log n - O(n)
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Ordinamenti quadratici
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
SelectionSort
Approccio incrementale: estende l’ordinamento da k a k+1
elementi, scegliendo il minimo degli n-k elementi non
ancora ordinati e mettendolo in posizione k+1.
Esempio di esecuzione:
7
2
4
5
3
1
1
2
3
4
5
7
1
2
4
5
3
7
1
2
3
4
5
7
1
2
4
5
3
7
1
2
3
4
5
7
1
2
3
5
4
7
Tempo di
esecuzione nei
casi
•pessimo: O(n2)
•ottimo: O(n2)
•medio: O(n2)
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
InsertionSort
Approccio incrementale: estende l’ordinamento da k a k+1
elementi, posizionando l’elemento (k+1)-esimo nella
posizione corretta rispetto ai primi k elementi.
Esempio di esecuzione:
7
2
4
5
3
1
2
4
5
7
3
1
2
7
4
5
3
1
2
3
4
5
7
1
2
4
7
5
3
1
1
2
3
4
5
7
Tempo di esecuzione
nei casi
•pessimo: O(n2)
•ottimo: O(n)
•medio: O(n2)
(Attenzione: nella
codifica in [Dem]
(pag. 87) il caso
ottimo non vale!)
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Una codifica di InsertionSort
(migliore di quella in [Dem] (capitolo 4, pag. 87))
Insertion-sort (A)
{ I.C.: A[1..j-1] ordinato, sia U= A[1..j −1] }
for j ← 2 to length(A) do
key ← A[j]
{inserisci A[j] in A[1. .j-1] spostando a destra gli elementi > di A[j]}
i ← j-1
{ I.C.: A[1..i] A[i+2..j] = U & A[i+2..j] > key }
while i > 0 and A[i] > key do
A[i+1] ← A[i]
i ← i-1
{A[1..i] A[i+2..j] = U & A[i+2..j] > key & A[i] ≤ key }
A[i+1] ← key
F. Damiani - Alg. & Lab. 04/05
{ A[1..length(A)] è ordinato}
BubbleSort
• Esegue una serie di scansioni dell’array
ƒ In ogni scansione confronta coppie di elementi
adiacenti, scambiandoli se non sono nell’ordine
corretto
ƒ Dopo una scansione in cui non viene effettuato
nessuno scambio l’array è ordinato
• Dopo la k-esima scansione, i k elementi più
grandi sono correttamente ordinati ed occupano le
k posizioni più a destra
correttezza e tempo
di esecuzione O(n2) nel caso pessimo e O(n) nel
caso ottimo. Il caso medio e’ O(n2).
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Esempio di esecuzione:
(1)
7 2 4 5 3 1
2 7
4 7
5 7
3 7
1 7
(2)
2 4 5 3 1 7
2 4
4 5
3 5
1 5
(3)
2 4 3 1 5 7
2 4
3 4
1 4
(4)
2 3 1 4 5 7
2 3
1 3
(5)
2 1 3 4 5 7
1 2
1 2 3 4 5 7
2 4 3 1 5 7
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Ordinamenti ottimi
(nel modello basato su confronti)
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
MergeSort
Usa la tecnica del divide et impera:
1. Divide: dividi l’array a metà
2. Risolvi il sottoproblema ricorsivamente
3. Impera: fondi le due sottosequenze ordinate
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Esempio di esecuzione:
7 2 4 5 3 1 5 6
input
1 2 3 4 5 5 6 7
output
7 2 4 5
3 1 5 6
2 4 5 7
1 3 5 6
7 2
4 5
3 1
5 6
2 7
4 5
1 3
5 6
Input ed
output delle
chiamate
ricorsive
7
2
4
5
3
1
5
6
7
2
4
5
3
1
5
6
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Codifica di MergeSort
(ci sono piccole differenze rispetto a quella in [Dem])
MergeSort (A, p, q) {1 ≤ p ≤ q ≤ length(A)}
if p < q then
r ← (p + q)/2
{1 ≤ p ≤ r ≤ length(A)}
MergeSort (A, p, r)
{A[p..r] ordinato}
{1 ≤ r+1 ≤ q ≤ length(A)}
MergeSort (A, r+1, q)
{A[p..r] ordinato & A[r+1..q] ordinato}
Merge (A, p, r, q)
{A[p..q] ordinato}
{ A[p..q] ordinato }
F. Damiani - Alg. & Lab. 04/05
La procedura Merge
• Due array ordinati A e B possono essere fusi
rapidamente:
ƒ estrai ripetutamente il minimo di A e B e copialo
nell’array di output, finché A oppure B non
diventa vuoto
ƒ copia gli elementi dell’array non vuoto alla fine
dell’array di output
• Tempo: O(n), dove n è il numero totale di
elementi
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Codifica di Merge (1/2)
(ci sono piccole differenze rispetto a quella in [Dem])
Merge (A, p, r, q) { A[p..r] e` ordinato & A[r +1..q] e` ordinato }
i ← p j ← r+1
k←p
B array di dimensione q-p+1
{ I.C.: B[1..k-1] ordinato & A[i..r] ordinato & A[j..q] ordinato
& A[i..r] ≥ B[1..k-1] & A[j..q] ≥ {B[1..k-1]
& BAG(B[1..k-1])=BAG(A[p..i-1]A[r+1..j-1]) }
while i ≤ r and j ≤ q do
if A[i] ≤ A[j] then B[k] ← A[i]
i ← i+1
else B[k] ← A[j]
j ← j+1
k ← k+1
{ B[1..k-1] ordinato & A[i..r] ordinato & A[j..q] ordinato
& A[i..r] ≥ B[1..k-1] & A[j..q] ≥ B[1..k-1] & (i > r ∨ j > q)
F. Damiani - Alg. & Lab. 04/05
& BAG(B[1..k-1])=BAG(A[p..i-1]A[r+1..j-1]) }
Codifica di Merge (2/2)
{ B[1..k-1] ordinato & A[i..r] ordinato & A[j..q] ordinato
& A[i..r] ≥ B[1..k-1] & A[j..q] ≥ B[1..k-1] & (i > r ∨ j > q)
& BAG(B[1..k-1])=BAG(A[p..i-1]A[r+1..j-1]) }
if j > q then
B[k..q-p+1] ← A[i..r]
else {i > r}
B[k..q-p+1] ← A[j..q]
{B[1..q-p+1] e` ordinato}
A[p..q] ← B[1..q-p+1]
{A[p..q] e` ordinato}
F. Damiani - Alg. & Lab. 04/05
Tempo di esecuzione
• Il numero di operazioni primitive del
MergeSort è descritto dalla seguente
relazione di ricorrenza:
T(n) = 2*T(n/2) + O(n)
• Usando il Teorema Master si ottiene
T(n) = O(n log n)
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Spazio occupato durante l’esecuzione
• Ogni record di attivazione di MergeSort richiede
spazio O(1) quando la procedura merge non e’
attiva
ƒ Ci possono essere alpiu’ O(log n) chiamate ricorsive
attive contemporaneamente
ƒ Ci puo’ essere una sola Merge attiva
• La procedura Merge alloca un vettore di
dimensione n (una codifica migliore richiederebbe
un vettore di dimensione n/2)
• Quindi lo spazio richiesto e’
S(n) = O(n)
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
HeapSort
•
•
•
Stesso approccio incrementale del selectionSort
Usa però una struttura dati per estrarre in tempo
O(log n) il massimo ad ogni iterazione
Struttura dati heap associata ad un insieme S = albero
binario radicato con le seguenti proprietà:
1. completo fino al penultimo livello
2. gli elementi di S sono memorizzati nei nodi dell’albero
(chiave(v) denota l’elemento memorizzato nel nodo v)
3. chiave(padre(v)) ≥ chiave(v) per ogni nodo v diverso dalla
radice
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
La struttura dati heap
Rappresentazione ad albero e con vettore posizionale
sin(i) = 2i
des(i) = 2i+1
parent(i) = i/2
37
22
31
13
7
15
12
3
25
14
9
vettore posizionale
0
37 22 31 13 15 25 14
7
3 12
1
8
9
2
3
4
5
6
7
9
10 11
Se le foglie
nell’ultimo livello
sono compattate a
sinistra (heap con
struttura rafforzata),
il vettore posizionale
ha esattamente
dimensione n
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Proprietà salienti degli heap
1) Il massimo è contenuto nella radice
2) L’albero ha altezza O(log n)
3) Gli heap con struttura rafforzata possono essere
rappresentati in un array di dimensione pari a n
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
La procedura fixHeap
Se tutti i nodi di H tranne v soddisfano la proprietà di
ordinamento a heap (ovvero v non e’ maggiore di
entrambi i suoi figli), possiamo ripristinarla come segue:
fixHeap(nodo v, heap H)
if (v è una foglia) then return
else
sia u il figlio di v con chiave
massima
if ( chiave(v) < chiave(u) ) then
scambia chiave(v) e chiave(u)
fixHeap(u,H)
Tempo di esecuzione:
O(log n)
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Estrazione del massimo
• Copia nella radice la chiave contenuta nella
la foglia più a destra dell’ultimo livello
• Rimuovi la foglia
• Ripristina la proprietà di ordinamento a
heap richiamando fixHeap sulla radice
Tempo di esecuzione: O(log n)
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Esempio di estrazione del massimo (1)
16
15
14
7
3
9
2
1
8
10
4
F. Damiani - Alg. & Lab. 04/05
Esempio di estrazione del massimo (2)
4
15
14
7
3
9
2
8
10
1
F. Damiani - Alg. & Lab. 04/05
Esempio di estrazione del massimo (3)
15
4
14
7
3
9
2
8
10
1
F. Damiani - Alg. & Lab. 04/05
Esempio di estrazione del massimo (4)
15
9
14
7
3
4
2
8
10
1
F. Damiani - Alg. & Lab. 04/05
Esempio di estrazione del massimo (5)
15
9
14
7
3
4
2
8
10
1
F. Damiani - Alg. & Lab. 04/05
Costruzione dell’heap (algoritmo ricorsivo)
Algoritmo ricorsivo basato sul divide et impera
heapify(heap H) {nota: H non e’
ancora uno heap}
if (H è vuoto) then return
else
heapify(sottoalbero sinistro di
H)
heapify(sottoalbero destro di
H)
Tempo di esecuzione: T(n)= 2T(n/2)+O(log n)
fixHeap(radice di H,H)
T(n) = O(n)
dal Teorema Master
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Costruzione dell’heap (algoritmo iterativo)
Algoritmo iterativo (per la rappresentazione con vettore)
heapifyI(heap H) {nota: H non e’
ancora uno heap}
for i=indexOfLastIn(H) to 1 do
fixHeap(nodo i, heap con
radice in i)
Tempo di esecuzione: le chiamate di fixheap
sono le stesse effettuate dalla versione
ricorsiva
T(n) = O(n)
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Esempio di costruzione dell’Heap (1)
2
6
18
3
42
44
94
12
55
79
2
6
18
3
42 12 55 44 94 79
1
2
3
4
5
6
7
8
9
10
F. Damiani - Alg. & Lab. 04/05
Esempio di costruzione dell’Heap (2)
2
6
18
3
42
44
94
12
55
79
2
6
18
3
42 12 55 44 94 79
1
2
3
4
5
6
7
8
9
10
F. Damiani - Alg. & Lab. 04/05
Esempio di costruzione dell’Heap (3)
2
6
18
3
79
44
94
12
55
42
2
6
18
3
79 12 55 44 94 42
1
2
3
4
5
6
7
8
9
10
F. Damiani - Alg. & Lab. 04/05
Esempio di costruzione dell’Heap (4)
2
6
18
94
79
44
3
12
55
42
2
6
18 94 79 12 55 44
3
42
1
2
3
9
10
4
5
6
7
8
F. Damiani - Alg. & Lab. 04/05
Esempio di costruzione dell’Heap (5)
2
6
55
94
79
44
3
12
18
42
2
6
55 94 79 12 18 44
3
42
1
2
3
9
10
4
5
6
7
8
F. Damiani - Alg. & Lab. 04/05
Esempio di costruzione dell’Heap (6)
2
94
55
44
79
6
3
12
18
42
2
94 55 44 79 12 18
6
3
42
1
2
8
9
10
3
4
5
6
7
F. Damiani - Alg. & Lab. 04/05
Esempio di costruzione dell’Heap (7)
94
79
55
44
42
6
3
12
2
94 79 55 44 42 12 18
1
18
2
3
4
5
6
7
6
3
2
8
9
10
F. Damiani - Alg. & Lab. 04/05
L’algoritmo HeapSort
• Costruisce un heap tramite
heapify
• Estrae ripetutamente il massimo
per n-1 volte
O(n)
O(n log n)
ƒ ad ogni estrazione memorizza il massimo
nella posizione dell’array che si è appena
liberata
ordina in loco in tempo O(n log n)
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Esempio di esecuzione:
37
22
31
13
15
25
31
25
13
15
22
25
22
13
15
37 31 22 13 15 25
31 25 22 13 15 37
25 15 22 13 31 37
(1)
(2)
(3)
22
15
15
13
13
13
22 15 13 25 31 37
15 13 22 25 31 37
13 15 22 25 31 37
(4)
(5)
(6)
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
ATTENZIONE
Alcuni autori usano nomi diversi per le operazioni
sull’heap. Ad esempio, in alcuni testi:
• fixHeap e’ chiamata heapify, e
• heapify e’ chiamata buildHeap
F. Damiani - Alg. & Lab. 04/05
QuickSort
Usa la tecnica del divide et impera:
1. Divide: scegli un elemento x della sequenza
(perno) e partiziona la sequenza in elementi
≤ x ed elementi >x
2. Risolvi i due sottoproblemi ricorsivamente
3. Impera: restituisci la concatenazione delle
due sottosequenze ordinate
Rispetto al MergeSort, divide complesso ed impera semplice
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Codifica di QuickSort
(ci sono piccole differenze rispetto a quella in [Dem])
Quicksort (A, p, q) {p ≤ q+1}
if p < q then
{p < q}
r ← Partition (A, p, q)
{A[p..r-1] ≤ A[r] ≤ A [r+1..q]}
{p ≤ r (ovvero: p ≤ (r-1)+1 }
Quicksort (A, p, r -1)
{A[p..r-1] ordinato & A[p..r-1] ≤ A[r] ≤ A [r+1..q]}
{r ≤ q (ovvero: r+1 ≤ q+1) }
Quicksort (A, r+1, q)
{A[p..r-1] ordinato & A[r+1..q] ordinato &
& A[p..r-1] ≤ A[r] ≤ A [r+1..q] }
{A[p..q] e` ordinato}
F. Damiani - Alg. & Lab. 04/05
Partizione in loco
• Scorri l’array “in parallelo” da sinistra verso
destra e da destra verso sinistra
ƒ da sinistra verso destra, ci si ferma su un elemento
maggiore del perno
ƒ da destra verso sinistra, ci si ferma su un elemento
minore del perno
• Scambia gli elementi e riprendi la scansione
Tempo di esecuzione: O(n)
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Partizione in loco, un esempio (attenzione: esecuzione del codice
presentato in [Deme], in cui il pivot viene mantenuto in
posizione 1, e alla fine si effettua poi uno scambio):
45 12 93 3
67 43 85 29 24 92 63 3
21
45 12 21 3
67 43 85 29 24 92 63 3
93
45 12 21 3
3 43 85 29 24 92 63 67 93
45 12 21 3
3 43 24 29 85 92 63 67 93
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Codifica di Partition
(ci sono piccole differenze rispetto a quella in [Dem])
Partition (A, p, q) {p < q}
x ← A[p]
i ← p-1
j ← q+1
{A[p..i] ≤ x & A[j..q] ≥ x}
while true do
do j ← j-1 while A[j] > x
do i ← i+1 while A[i] < x
{A[p..i-1] ≤ x & A[j+1..q] ≥ x & A[i] ≥ x & A[j] ≤ x}
if i < j
then {A[p..i-1] ≤ x & A[j+1..q] ≥ x & A[i] ≥ x & A[j] ≤ x
& i < j} “scambia A[i] con A[j]”
else {A[p..j-1] ≤ A[j] ≤ A[j+1..q]}
return j
F. Damiani - Alg. & Lab. 04/05
Qucksort, esempio di esecuzione:
5 5 6 7 1 2 3 4
2 5 4 3 1 5 7 6
1 2 3 4 5 5 6 7
2 5 4 3 1
1 2 4 3 5
1 2 3 4 5
1
1
1
dopo partition
output
7 6
6 7
6 7
4 3 5
3 4 5
3 4 5
3
3
3
input
6
6
6
L’albero delle
chiamate
ricorsive può
essere
sbilanciato
5
5
5
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Analisi di complessita’ in tempo
Siano T(n) e P(n) il numero di confronti tra elementi di A eseguiti
rispettivamente da Quicksort e Partition.
Poiche’ P(n) = n e le due porzioni di A sulle quali avviene la
chiamata ricorsiva hanno j - 1 e n - j elementi otteniamo:
T(n) = 0
T(n) = n + T(j - 1) + T(n - j)
per n < 2
per n ≥ 2
F. Damiani - Alg. & Lab. 04/05
Caso peggiore
Nel caso peggiore, il perno scelto ad ogni passo è
il minimo (o il massimo) degli elementi nell’array.
Se A[p] e’ il minimo:
la porzione di A con elementi minori di A[p] e’ vuota,
mentre la porzione di A con elementi maggiori o uguali
contiene n-1 elementi.
L’equazione di ricorrenza assume la forma:
T(n) = 0
T(n) = T(n - 1) + n
per n < 2
per n ≥ 2
F. Damiani - Alg. & Lab. 04/05
n
n-1
Albero della ricorsione
di Quicksort
nel caso peggiore
n-2
T(n) = Θ(n2)
2
F. Damiani - Alg. & Lab. 04/05
Caso migliore
A e’ sempre suddiviso a meta’: la porzione di A con elementi
minori di A[p] e quella con elementi maggiori o uguali contiene
(n-1)/2 elementi.
L’equazione di ricorrenza assume la forma:
T(n) = 0
T(n) = 2 T((n - 1)/2 ) + n
per n < 2
per n ≥ 2
T(n) = Θ(n lgn)
F. Damiani - Alg. & Lab. 04/05
Randomizzazione
• Possiamo evitare il caso peggiore scegliendo come perno un
elemento a caso
• Poiché ogni elemento ha la stessa probabilità, pari a 1/n, di essere
scelto come perno, il numero di confronti nel caso atteso è:
n
n
T(n) = n + 1/n Σ (T(j - 1) + T(n - j)) = n + 2/n Σ T(j)
j=1
j=1
dove j-1 e n-j sono le dimensioni dei problemi risolti
ricorsivamente.
(In [Dem] si considera invece la relazione di ricorrenza:
n-1
T(n)=
∑
a=0
n-1
2 T(a)
1 (n-1+T(a)+T(n-a-1)) = n-1+
∑
—
—
n
n
a=0
dove a e (n-a-1) sono le dimensioni dei sottoproblemi risolti
ricorsivamente.) F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Numero di confronti nell’algoritmo randomizzato
(analogo a quello nel caso medio, nell’ipotesi che i possibili input,
ugualmente probabili, siano tutte le permutazioni di n elementi distinti)
Partition produce un misto di suddivisioni “buone” e “cattive”,
che sono distribuite lungo l’albero in modo casuale.
n
T(n) = n + 1/n Σ (T(j - 1) + T(n - j))
j=1
n-1
= n + 2/n Σ T(j)
j=1
La soluzione dell’equazione richiede qualche manipolazione
algebrica:
F. Damiani - Alg. & Lab. 04/05
T(n) = n + 2/n
n-1
Σ T(j)
j=1
n-2
T(n-1) = n -1 + 2/(n-1)
Σ T(j)
j=1
(n-1) (n-1)
(n-1) T(n-1)
=
n
n
(n-1) T(n-1)
T(n) n
n-2
+ 2/n
Σ T(j)
j=1
2n-1
=
n + 2/n T(n-1)
2n-1
n+1
T(n) = n + n T(n-1)
n+1
≤ 2 + n T(n-1)
F. Damiani - Alg. & Lab. 04/05
n+1
T(n) ≤ 2 + n T(n-1)
n+1
n
≤ 2 + n (2 +
n-1
≤ 2+2
n+1
n +
T(n-2))
n+1 T(n-2)
n-1
≤ 2 + 2 n+1 + 2 n+1 + 2 n+1 . . . + (n+1) T(1)
n
n-1
n-2
2
n+1
1
1 +. . . + 1 )
= 2
+
2(n+1)
(
+
n
3
n-1
n+1
n+1
= 2 (n+1) Σ 1/k
k=3
T(n) = O(n log n)
F. Damiani - Alg. & Lab. 04/05
Analisi di complessita’ in spazio
In base alle considerazioni fatte in precedenza sulle possibili
forme dell’albero delle chiamate ricorsive di QuickSort abbiamo
che (dato che Partition opera in loco) lo spazio richiesto e’:
S(n) = O(log n)
nel caso ottimo
S(n) = O(n)
nel caso peggiore
S(n) = O(log n)
nel caso medio
(ovvero, nel caso atteso dell’algoritmo
randomizzato)
.
F. Damiani - Alg. & Lab. 04/05
Osservazioni
• Possiamo ridurre ulteriormente la probabilita’ che si verifichi il
caso peggiore scegliendo come perno il mediano di 3 (o 5 o7)
elementi scelti a caso.
• Possiamo migliorare ulteriormente le prestazioni gestendo i
sottoproblemi di dimensione piccola (ad es. ≤ 10) con un insertion
sort.
• Il quicksort non e’ stabile (la nozione di stabilita’ e’ introdotta piu’
avanti, in questi lucidi): controllate il funzionamento sia della
procedura partition presentata in questi lucidi che di quella
presentata in [Deme].
F. Damiani - Alg. & Lab. 04/05
Esercizio
• Se si modifica la procedura partition presentata in questi lucidi
sostituendo le righe:
do j ← j-1 while A[j] > x
do i ← i+1 while A[i] < x
con le righe:
do j ← j-1 while A[j] ≥ x
do i ← i+1 while A[i] ≤ x
si ottiene una procedura partition che e’ ancora corretta, ma che
tuttavia presenta un inconveniente. Quale?
(Suggerimento: considerate la situazione si verifica in cui tutti gli
elementi da ordinare hanno la stessa chiave.)
F. Damiani - Alg. & Lab. 04/05
Ordinamenti lineari
(per dati di input con proprietà particolari)
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
IntegerSort: fase 1
Per ordinare n interi con valori in [1,k]
Mantiene un array Y di k contatori tale che Y[x] = numero
di volte che il valore x compare nell’array di input X
Esempio di esecuzione:
X
5
1
6
8
6
Y
0
1
0
2
0
3
0
4
1
5
X
5
1
6
8
6
Y
1
1
0
2
0
3
0
4
1
5
0
6
1
6
0
7
0
7
0
8
1
8
5
1
6
8
6
1
1
0
2
0
3
0
4
1
5
5
1
6
8
6
1
1
0
2
0
3
0
4
1
5
0
6
0
7
0
8
2
6
0
7
1
8
5
1
6
8
6
1
1
0
2
0
3
0
4
1
5
1
6
0
7
(a) Calcolo di Y
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
0
8
IntegerSort: fase 2
Scorre Y da sinistra verso destra e, se Y[x]=k, scrive in
X il valore x per k volte
X
1
1
Y
1
1
0
2
0
3
0
4
X
1 5
6
6
Y
0
1
0
3
0
4
0
2
1
5
0
5
2
6
2
6
0
7
0
7
1
8
1
8
0
1
0
2
0
3
0
4
1 5
6
6
0
1
0
3
0
4
0
2
1
5
0
5
2
6
0
6
0
7
0
7
1
8
1
8
1
5
0
1
0
2
0
3
0
4
1
5
1
5
6
6
8
0
1
0
2
0
3
0
4
0
5
2
6
0
7
1
8
0
6
0
7
1
8
(b) Ricostruzione di X
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
IntegerSort: analisi
• Tempo O(k) per inizializzare Y a 0
• Tempo O(n) per calcolare i valori dei contatori
• Tempo O(n+k) per ricostruire X
O(n+k)
Tempo lineare se k=O(n)
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
BucketSort
Per ordinare n record con chiavi intere in [1,k]
• Basta mantenere un array di liste, anziché di
contatori, ed operare come per IntegerSort
• La lista Y[x] conterrà gli elementi con chiave
uguale a x
• Concatenare poi le liste
Tempo O(n+k) come per IntegerSort
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
La stabilità
• Un algoritmo è stabile se preserva l’ordine
iniziale tra elementi con la stessa chiave
• Il BucketSort può essere reso stabile
appendendo gli elementi di X in coda alla
opportuna lista Y[i]
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
RadixSort
• Cosa fare se il massimo valore k e’ > di n, ad
esempio se k = Θ(nc)?
• Rappresentiamo gli elementi in base b, ed
eseguiamo una serie di BucketSort
• Partiamo dalla cifra meno significativa verso
quella più significativa
Per
b=10
2397
4368
5924
5924
2397
4368
5924
4368
2397
4368
2397
5924
2397
4368
5924
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Correttezza
• Se x e y hanno una diversa t-esima cifra, la
t-esima passata di BucketSort li ordina
• Se x e y hanno la stessa t-esima cifra, la
proprietà di stabilità del BucketSort li
mantiene ordinati correttamente
Dopo la t-esima passata di BucketSort, i
numeri sono correttamente ordinati rispetto
alle t cifre meno significative
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Tempo di esecuzione
• O(logb k) passate di bucketsort
• Ciascuna passata richiede tempo O(n+b)
O( (n+b) logb k)
(
)
logk
Se b = Θ(n), si ha O(n logn k)=O n ——
logn
Tempo lineare se k=O(nc), c costante
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Riepilogo
• Nuove tecniche:
ƒ Divide et impera (MergeSort, QuickSort)
ƒ Randomizzazione (QuickSort)
ƒ Strutture dati efficienti (HeapSort)
• Proprietà particolari dei dati in ingresso possono
aiutare a progettare algoritmi più efficienti:
algoritmi lineari
• Alberi di decisione per la dimostrazione di
delimitazioni inferiori
F. Damiani - Alg. & Lab. 04/05 (da C. Demetrescu et al - McGraw-Hill)
Domande…
IntegerSort, BucketSort, RadixSort
non ordinano in loco…
Se qualcuno vi dicesse che ha trovato un
algoritmo di ordinamento che permettere di
ordinare l’insieme {1,…,n} in loco con
complessita’ in tempo O(n) nel caso
pessimo, che cosa gli rispondereste?
F. Damiani - Alg. & Lab. 04/05
Esercizio
• Dimostrare, con il metodo delle asserzioni, la
correttezza delle codifiche degli algoritmi
selectionSort, insertionSort,
bubleSort, mergeSort (e merge),
quickSort (e partition),
integerSort, bucketSort, radixSort
contenute in [Dem]. ANNOTATE (SCRIVENDO
A MATITA SUL LIBRO) IL CODICE CON LE
ASSERZIONI CHE NE DIMOSTRANO LA
CORRETTEZZA.
F. Damiani - Alg. & Lab. 04/05