Ordinamento
Continuiamo a discutere il problema dell’ordinamento:
Ordinamento (Sorting)
Input: Sequenza di n numeri <a1,a2, ……,an>
Output: Permutazione
π<a1,a2, ……,an> = <ai1,ai2, ……,ain>
tale che:
ai1  ai2  ……  ain
Algoritmo di Insertion-Sort
problema dell’ordinamento
 risolve il
La complessità temporale (tempo di esecuzione
dell’algoritmo) dell’ Insertion-Sort verifica:
Tworst(n) = Tavg(n)= (n2)
Tbest(n) = (n)
La complessità spaziale (spazio di memoria
necessario per ospitare le strutture di dati
utilizzate dall’algoritmo) dell’Insertion Sort è (n).
1
Prima di continuare ……
Quando forniamo limiti asintotici superiori/inferiori alla
complessità di un algoritmo senza specificare le
proprietà dell’input, ci riferiamo implicitamente al caso
peggiore:
Un algoritmo ha delimitazione superiore alla complessità (o
più semplicemente complessità) O(f(n)) (upper bound) se per
tutte le istanze di input
T(n) = O(f(n))
 Tbest(n) = O(f(n)), ma anche Tworst(n) = O(f(n))
Un algoritmo ha delimitazione inferiore alla complessità
(f(n)) (lower bound) se esiste almeno una istanza di input
per cui T(n) = (f(n))
 Tworst (n) = (f(n)), ma non è detto che Tbest(n) = Ω(f(n))
Quindi:
1. Se scrivo che un algoritmo ha complessità T(n) =
O(f(n)), intendo semplicemente dire che per tutte le
istanze di input l’algoritmo termina in O(f(n)).
2.
Se invece scrivo che un algoritmo ha complessità T(n)
= Θ(f(n)), intendo dire che T(n)=O(f(n)), ma anche
che esiste almeno una istanza di input per cui T(n) =
(f(n)).
2
L’algoritmo di Insertion Sort è l’ unico algoritmo
possibile per risolvere il problema dell’ordinamento?
Ovviamente NO!
L’algoritmo di Selection Sort è definito dalla
seguente strategia:
• Al primo passo, si seleziona l’elemento più
piccolo tra gli n elementi della sequenza, e lo si
pone nella prima posizione (scambiandolo con
l’elemento A[1] );
• Al secondo passo, si seleziona l’elemento più
piccolo tra i rimanenti n-1 elementi di A, e lo si
pone nella seconda posizione (scambiandolo con
A[2] );
.
.
.
• Al k-esimo passo, si seleziona l’elemento più
piccolo tra i rimanenti n-(k-1) elementi di A, e lo
si pone nella k-esima posizione (scambiandolo
con A[k]);
.
.
.
• All’(n-1)-esimo passo, si seleziona l’elemento
più piccolo tra gli ultimi 2 elementi di A, e lo si
pone nella penultima posizione (scambiandolo 3
con A[n-1]).
Notare
La strategia descritta prevede ad ogni step la
risoluzione dello stesso problema (ricerca del
minimo di una sequenza).
 Abbiamo risolto il problema dell’ordinamento
riconducendo tale problema al più semplice
problema di ricerca del minimo.
Algoritmo per la ricerca dell’elemento minimo di
una sequenza
Min_elemento(A)
ind_min  1
For i 2 to length(A)
do if (A[i] < A[ind_min ])
then ind_min  i
Return ind_min
Qual è la complessità spaziale di questo algoritmo?
S(n) = (n)
Qual è la complessità temporale di questo algoritmo?
T(n) = (n)
per tutti gli input
4
Algoritmo Selection Sort
Selection-Sort(A)
For j1 to (length(A)-1)
do ind_min  j
For i j+1 to length(A)
do if (A[i] < A[ind_min ])
then ind_min  i
k  A[ind_min]
A[ind_min]  A[j]
A[j]  k
Notiamo che l’algoritmo, oltre a terminare sempre, è
corretto. Infatti, ragionando per induzione, devo
mostrare che la seguente affermazione è vera per
j=1,2,3,……n-1:
“Al j-esimo step l’algoritmo produce una sequenza
ordinata non decrescente A[1],……,A[j], dove A[1] è il più
piccolo elemento, A[2] è il secondo elemento piu’ piccolo ,
…………, A[j] è il j-esimo elemento più piccolo della
sequenza iniziale.”
 Verifico la validità dell’affermazione per j=1;
 Assumo che la affermazione sia valida per un generico
valore di j=h;
 Mostro che da ciò segue che la affermazione è valida
per j=h+1.
5
Analisi del Selection Sort
Selection-Sort(A)
For j1 to (length(A)-1)
do ind_min  j
For i j+1 to length(A)
do if (A[i] < A[ind_min ])
then ind_min  i
k  A[ind_min]
A[ind_min]  A[j]
A[j]  k
(*)
Complessità temporale del Selection Sort
- Qual è la linea (o il blocco di linee di codice) che
viene eseguito più volte (operazione dominante)?
if (A[i] < A[ind_min ]) - operazione di confronto
- Quante volte viene eseguita?
n2
n  j   (n2 )

2
j1
n 1
- Il numero di volte che questa linea viene eseguita
dipende dall’ input?
No!
La complessità temporale del Selection Sort è
T(n)=(n2) (indip. dall’input).
6
Complessità intrinseca di un problema
Abbiamo introdotto 2 algoritmi (IS, SS) che
risolvono il problema dell’ordinamento:
SS – T(n) = Θ(n2)
IS - T(n) = Θ(n2)
Ci chiediamo – Si può fare di meglio?
Più precisamente – E’ possibile risolvere il problema
dell’ ordinamento mediante algoritmi aventi un
comportamento asintotico migliore?
Notare – Per rispondere alla domanda, dobbiamo
“alzare” il livello della nostra analisi. Dobbiamo
discutere le proprietà generali del problema,
indipendentemente dagli algoritmi specifici
utilizzati per risolverlo.
7
Complessità intrinseca di un problema
vs
Complessità computazionale di un algoritmo
Un problema computazionale ha delimitazione superiore
alla complessità O(f(n)) (upper bound) se esiste un
algoritmo per la sua risoluzione con delimitazione
superiore O(f(n)).
Un problema computazionale ha delimitazione inferiore
alla complessità (f(n)) (lower bound) se tutti gli
algoritmi per la sua risoluzione hanno delimitazione
inferiore (f(n)).
Se dimostro che un problema ha delimitazione inferiore
(f(n)) e trovo un algoritmo risolutivo avente complessità
O(f(n)) allora…
…a meno di costanti, ho un algoritmo ottimale per
risolvere il problema!!!
Esempio di algoritmo ottimale  Algoritmo per la
ricerca del minimo in un insieme non ordinato, avente
complessità O(n),
Infatti, ogni algoritmo dovrà almeno leggere l’input, e
quindi avrà complessità (n).
8
Problema dell’ordinamento
Sappiamo per ora che:
Lower bound - (n)
Upper bound – O(n2)
(banale, dimensione dell’input)
IS, SS
Abbiamo un gap lineare tra upper bound e lower bound.
Forse possiamo fare meglio ….……
9
Lower Bound per il problema dell’ordinamento
Ordinamento per confronti
Dati due elementi ai ed aj, per determinarne
l’ordinamento relativo effettuiamo una delle seguenti
operazioni di confronto:
ai  aj ; ai  aj ; ai  aj ; ai  aj ; ai  a j
Non si possono esaminare i valori degli elementi o
ottenere informazioni sul loro ordine in altro modo.
Notare – Tutti gli algoritmi di ordinamento considerati
fino ad ora sono algoritmi di ordinamento per confronto.
10
Gli algoritmi di ordinamento per confronto possono
essere descritti in modo astratto in termini di ALBERI
DI DECISIONE.
Un generico algoritmo di ordinamento per confronto
lavora nel modo seguente:
-Confronta due elementi ai ed aj (ad esempio effettua
il test ai  aj);
- A seconda del risultato – riordina e/o decide il
confronto successivo da eseguire.
Albero di decisione - Descrive i confronti che l’algoritmo
esegue quando opera su un input di una determinata
dimensione. I movimenti dei dati e tutti gli altri aspetti
dell’algoritmo vengono ignorati
Albero di decisione dell’algoritmo Insertion Sort
a1:a2


a2:a3
a1:a3


a1:a3
<a1,a2,a3>

<a1,a3,a2>


<a2,a1,a3>

<a3,a1,a2>
a2:a3

<a2,a3,a1>

<a3,a2,a1>
11
Prima di discutere i dettagli, alcune definizioni ……
radice
a1:a2
Sotto-albero
sinistro

Sotto-albero
destro

a2:a3
a1:a3



<a1,a3,a2>


a1:a3
<a1,a2,a3>
nodo
<a2,a1,a3>
a2:a3

cammino

<a3,a1,a2>
<a2,a3,a1>
<a3,a2,a1>
foglia
Profondità di un nodo = lunghezza del cammino che lo
congiunge alla radice.
Altezza di un albero = valore massimo della profondità
dei nodi.
Notare -
(Numero di Foglie)  2h
h = altezza dell’albero
12
Torniamo al problema dell’ordinamento
Insertion-Sort
a1:a2


a2:a3
a1:a3


a1:a3
<a1,a2,a3>

<a1,a3,a2>


<a2,a1,a3>

<a3,a1,a2>
a2:a3

<a2,a3,a1>

<a3,a2,a1>
- Ogni foglia è etichettata con una permutazione della
sequenza iniziale;
- L’esecuzione dell’ algoritmo corrisponde a tracciare un
cammino dalla radice ad una foglia;
- L’algoritmo segue un cammino diverso a seconda delle
caratteristiche dell’input
caso migliore: cammino più breve
caso peggiore: cammino più lungo
- L’altezza dell’albero fornisce il numero di confronti che
l’algoritmo esegue nel caso peggiore.
13
Limite inferiore al problema dell’ordinamento per confronti
Insertion-Sort
a1:a2


a2:a3
a1:a3


a1:a3
<a1,a2,a3>

<a1,a3,a2>


<a2,a1,a3>

<a3,a1,a2>
a2:a3


<a2,a3,a1>
<a3,a2,a1>
Notiamo che:
- il numero di foglie dell’albero di decisione deve essere
almeno pari al numero di possibili permutazioni della
sequenza iniziale:
(# foglie)  n!
- Come già detto, sussiste la seguente relazione tra il
numero di foglie e l’altezza di un albero binario:
(# foglie)  2h
Ciò implica:
2h  n!
Da ciò segue h  log2(n!), e per l’approssimazione di
Stirling su n!, che impone n!>(n/e)n, ne segue che
h > log2((n/e)n) = n(log2n-log2e) = (n log2n)
14
In conclusione:
Ricordando che l’altezza dell’albero di decisione indica il
numero di confronti che un generico algoritmo effettua
nel caso peggiore, otteniamo:
 Algoritmo
T(n) = (n log2 n)
Quindi:
Lower Bound = (n log2 n)
(problema dell ’ordinamento per confronti)
Esercizi –
1) Albero di decisione per SS (3 elementi)
2) Ho scritto T(n) = (n log2 n).
Implica T(n) = (n ln (n)) ?
15