18 Ordinamento
18 Ordinamento
• Ipotesi:
gli elementi siano memorizzati in un array.
ALGORITMI DI ORDINAMENTO
Principali algoritmi di ordinamento:
• naïve sort (semplice, intuitivo, poco efficiente)
• bubble sort (semplice, un po’ più efficiente)
• insert sort (intuitivo, abbastanza efficiente)
• merge sort (non intuitivo, molto efficiente)
• quick sort (non intuitivo, alquanto efficiente)
NAÏVE SORT
Codifica
void naiveSort(int v[], int n){
int p; La dimensione dell’array
while (n>1) { cala di 1 a ogni iterazione
p = trovaPosMax(v,n);
if (p<n-1) scambia(&v[p],&v[n-1]);
n--;
}
}
NAÏVE SORT
Codifica
int trovaPosMax(int v[], int n){
int i, posMax=0; All’inizio si assume v[0]
come max di tentativo.
for (i=1; i<n; i++)
if (v[posMax]<v[i]) posMax=i;
return posMax;
}
Si scandisce l’array e, se si trova un elemento maggiore
del max attuale, lo si assume come nuovo max,
memorizzandone la posizione.
NAÏVE SORT
Valutazione di complessità
• Il numero di confronti necessari vale sempre:
(N-1) + (N-2) + (N-3) + … + 2 + 1 =
= N*(N-1)/2 = O(N2/2)
• Nel caso peggiore, questo è anche il numero di
scambi necessari (in generale saranno meno)
• Importante: la complessità non dipende dai
particolari dati di ingresso
– l’algoritmo fa gli stessi confronti sia per un
array disordinato, sia per un array già ordinato!!
BUBBLE SORT (ordinamento a bolle)
• Corregge il difetto principale del naïve sort:
quello di non accorgersi se l’array, a un
certo punto, è già ordinato.
• Opera per “passate successive” sull’array:
– a ogni iterazione, considera una ad una tutte le
possibili coppie di elementi adiacenti,
scambiandoli se risultano nell’ordine errato
– così, dopo ogni iterazione, l’elemento massimo
è in fondo alla parte di array considerata
• Quando non si verificano scambi, l’array è
ordinato, e l’algoritmo termina.
BUBBLE SORT
Codifica
void bubbleSort(int v[], int n){
int i; int ordinato = 0;
while (n>1 && ordinato==0){
ordinato
di t = 1 1;
for (i=0; i<n-1; i++)
if (v[i]>v[i+1]) {
scambia(&v[i],&v[i+1]);
ordinato = 0; }
n--;
}
}
BUBBLE SORT
Esempio
Iª passata (dim. = 4)
al termine, 7 è a posto.
array ordinato
BUBBLE SORT
Valutazione di complessità
• Caso peggiore: numero di confronti identico al
precedente → O(N2/2)
• Nel caso migliore, però, basta una sola
passata,
t con N-1 f ti → O(N)
N 1 confronti
• Nel caso medio, i confronti saranno compresi
fra N-1 e N2/2, a seconda dei dati di ingresso.
INSERT SORT
• Per ottenere un array ordinato basta
costruirlo ordinato, inserendo gli elementi al
posto giusto fin dall’inizio.
• Idealmente, il metodo costruisce un nuovo
array, contenente
t t gli
li stessi
t i elementi
l ti del
d l
primo, ma ordinato.
• In pratica, non è necessario costruire un
secondo array, in quanto le stesse
operazioni possono essere svolte
direttamente sull’array originale: così, alla
fine esso risulterà ordinato.
INSERT SORT
Scelta di progetto
• “vecchio” e “nuovo” array condividono lo
stesso array fisico di N celle (da 0 a N-1)
• in ogni istante, le prime K celle (numerate
da 0 a K
K-1)
1) costituiscono il nuovo array
• le successive N-K celle costituiscono la
parte residua dell’array originale
0 1 K-1 K N-1
INSERT SORT
• Come conseguenza della scelta di progetto
fatta, in ogni istante il nuovo elemento da
inserire si trova nella cella successiva alla
fine del nuovo array, cioè la (K+1)-esima
(il cuii iindice
di è K)
Elemento da inse-
Nuovo array (dim = K) rire (indice K)
0 1 K-1 K
INSERT SORT
Specifica
for (k=1; k<n; k++)
<inserisci alla posizione k-esima del nuovo
array l’elemento minore fra quelli rimasti
nell’array
nell array originale> All’inizio
All’i i i (k=1)
(k 1) il nuovo array è
Codifica la sola prima cella
INSERT SORT
Specifica di insMinore()
void insMinore(int v[], int pos){
<determina la posizione in cui va inserito il
nuovo elemento>
<crea lo spazio spostando gli altri elementi
in avanti di una posizione>
<inserisci il nuovo elemento alla posizione
prevista>
}
INSERT SORT
Codifica di insMinore()
void insMinore(int v[], int pos){
int i = pos-1, x = v[pos];
Determina la
while (i>=0 && x<v[i]) posizione a cui
{ inserire x
v[i+1]= v[i]; /* crea lo spazio */
i--;
}
v[i+1]=x; /* inserisce l’elemento */
}
INSERT SORT
Esempio
INSERT SORT
Valutazione di complessità
• Nel caso peggiore (array ordinato al contrario),
richiede 1+2+3+…+(N-1) confronti e
spostamenti → O(N2/2)
• Nel
N l caso migliore
i li (
(array già
ià ordinato),
di t ) b bastano
t
solo N-1 confronti (senza spostamenti)
• Nel caso medio, a ogni ciclo il nuovo elemento
viene inserito nella posizione centrale dell’array
→ 1/2+2/2+…+(N-1)/2 confronti e spostamenti
Morale: O(N2/4)
QUICK SORT
• Idea base: ordinare un array corto è molto meno
costoso che ordinarne uno lungo.
• Conseguenza: può essere utile partizionare l’array
in due parti, ordinarle separatamente, e infine
fonderle insieme.
• In pratica:
– si suddivide il vettore in due “sub-array”, delimitati da un
elemento “sentinella” (pivot)
– il primo array deve contenere solo elementi mi-nori o
uguali al pivot, il secondo solo elementi maggiori del
pivot.
• Alla fine di questo blocco di lucidi c’e’ il codice ma
non lo vedremo a lezione e non fa parte del
programma.
QUICK SORT
• Si può dimostrare che O(N log2 N) è un
limite inferiore alla complessità del
problema dell’ordinamento di un array.
• Dunque, nessun algoritmo, presente o
futuro potrà far meglio di O(N log2 N)
futuro,
• Però, il quicksort raggiunge questo risultato
solo se il pivot è scelto bene
– per fortuna, la suddivisione in sub-array uguali
è la cosa più probabile nel caso medio
– l’ideale sarebbe però che tale risultato fosse
raggiunto sempre: a ciò provvede il Merge Sort.
MERGE SORT
• È una variante del quick sort che produce
sempre due sub-array di egual ampiezza
– così, ottiene sempre il caso ottimo O(N*log2 N)
• In pratica:
si spezza l’array in due parti di ugual dimensione
si ordinano separatamente queste due parti
(chiamata ricorsiva)
si fondono i due sub-array ordinati così ottenuti
in modo da ottenere un unico array ordinato.
• Il punto cruciale è l’algoritmo di fusione
(merge) dei due array
MERGE SORT
Esempio
1 8 7 6 5 4 3 2 1 0 -1-2
2 11
8 7 6 5 4 3 2 1 0 -1-2
3 6 12 17
8 7 6 5 4 3 2 1 0-1 -2
7 8 13 14 18 19
4 5
8 7 6 5 4 3 2 1 0 -1 -
10 15 16 20 21
9
5 4 2 1 -1 -2
MERGE SORT
Specifica
void mergeSort(int v[], int iniz, int fine,
int vout[]) {
if (<array non vuoto>){
<partiziona l’array
l array in due metà>
<richiama mergeSort ricorsivamente sui due sub-array,
se non sono vuoti>
<fondi in vout i due sub-array ordinati>
}
}
MERGE SORT
Codifica
void mergeSort(int v[], int iniz, int fine,
int vout[]) {
int mid;
if ( iniz < fine ) {
mid = (fine + iniz) / 2;
mergeSort(v, iniz, mid, vout);
mergeSort(v, mid+1, fine, vout);
merge(v, iniz, mid+1, fine, vout);
}
}
mergeSort() si limita a suddividere l’array: è
merge() che svolge il lavoro
MERGE SORT
Codifica di merge()
void merge(int v[], int i1, int i2,
int fine, int vout[]){
int i=i1, j=i2, k=i1;
while ( i <= i2-1
i2 1 && j <= fine ) {
if (v[i] < v[j]) {vout[k] = v[i]; i++;}
else {vout[k] = v[j]; j++;}
k++;
}
while (i<=i2-1){vout[k]=v[i]; i++; k++;}
while (j<=fine){vout[k]=v[j]; j++; k++;}
for (i=i1; i<=fine; i++) v[i] = vout[i];
}
QUICK SORT
• Idea base: ordinare un array corto è molto
meno costoso che ordinarne uno lungo.
• Conseguenza: può essere utile partizionare
l’array in due parti, ordinarle separatamente,
e infine
i fi fonderle
f d l insieme.
i i
• In pratica:
– si suddivide il vettore in due “sub-array”,
delimitati da un elemento “sentinella” (pivot)
– il primo array deve contenere solo elementi mi-
nori o uguali al pivot, il secondo solo elementi
maggiori del pivot.
QUICK SORT
Algoritmo ricorsivo:
• i due sub-array ripropongono un problema
di ordinamento in un caso più semplice
(array più corti)
• a forza di scomporre un array in sub-array,
si giunge a un array di un solo elemento,
che è già ordinato (caso banale).
QUICK SORT
Struttura dell’algoritmo
• scegliere un elemento come pivot
• partizionare l’array nei due sub-array
• ordinarli separatamente (ricorsione)
L’operazione-base è il partizionamento
dell’array nei due sub-array. Per farla:
• se il primo sub-array ha un elemento > pivot, e
il secondo array un elemento < pivot, questi
due elementi vengono scambiati
Poi si riapplica quicksort ai due sub-array.
QUICK SORT
Esempio: legenda
pivot
20 4 12 14 10 16 2
freccia nera (iniz): indica l’inizio freccia nera (fine): indica la fine
dell’array (e del I° sub-array) dell’array (e del II° sub-array)
QUICK SORT
Esempio (ipotesi: si sceglie 14 come pivot)
pivot 20>2 → scambio
20 4 12 14 10 16 2
14>10 → scambio
2 4 12 14 10 16 20
QUICK SORT
Esempio (passo 2: ricorsione sul I° sub-array)
pivot
2 4 12 10
II° array: solo
I° array: solo elementi > pivot
elementi ≤ pivot (dimensione 2)
(dimensione 1) 2 4 12 10
QUICK SORT
Esempio (passo 4: ricorsione sul II° sub-array)
pivot
14 16 20
restano solo due
micro- array singoli
→ caso banale
14 16 20
QUICK SORT
Codifica
void quickSort(int v[],int iniz,int fine){
int i, j, pivot;
if (iniz<fine) {
i = iniz, j = fine;
pivot = v[(iniz + fine)/2];
<isola nella prima metà array gli elementi minori o
uguali al pivot e nella seconda metà quelli maggiori >
<richiama quicksort ricorsivamente sui due sub-array,
se non sono vuoti >
}
QUICK SORT
Codifica
void quickSort(int v[],int iniz,int fine){
int i, j, pivot;
if (iniz<fine) {
i = iniz, j = fine;
pivot = v[(iniz + fine)/2];
<isola nella prima metà array gli elementi minori o
uguali al pivot e nella seconda metà quelli maggiori >
if (iniz < j) quickSort(v, iniz, j);
if (i < fine) quickSort(v, i, fine);
}
QUICK SORT
Codifica
<isola nella prima metà array gli elementi minori o
uguali al pivot e nella seconda metà quelli maggiori >
do {
while (v[i] < pivot) i++;
while (v[j] > pivot) j--;
if (i < j) scambia(&v[i], &v[j]);
if (i <= j) i++, j--;
} while (i <= j);
<invariante: qui j<i, quindi i due sub-array su cui
applicare la ricorsione sono (iniz,j) e (i,fine) >
QUICK SORT
La complessità dipende dalla scelta del pivot:
• se il pivot è scelto male (uno dei due sub-array
ha lunghezza zero), i confronti sono O(N2)
• se però il pivot è scelto bene (in modo da
avere due sub-array di egual dimensione):
• si hanno log2 N attivazioni di quicksort
• al passo k si opera su 2k array, ciascuno
di lunghezza L = N/2k
• il numero di confronti ad ogni livello è sempre N
(L confronti per ciascuno dei 2k array)
• Numero globale di confronti: O(N log2 N)
QUICK SORT
• Si può dimostrare che O(N log2 N) è un
limite inferiore alla complessità del
problema dell’ordinamento di un array.
• Dunque, nessun algoritmo, presente o
futuro potrà far meglio di O(N log2 N)
futuro,
• Però, il quicksort raggiunge questo risultato
solo se il pivot è scelto bene
– per fortuna, la suddivisione in sub-array uguali
è la cosa più probabile nel caso medio
– l’ideale sarebbe però che tale risultato fosse
raggiunto sempre: a ciò provvede il Merge Sort.
ESPERIMENTI
• Verificare le valutazioni di complessità che
abbiamo dato non è difficile
– basta predisporre un programma che “conti” le
istruzioni di confronto, incrementando ogni volta
un’apposita
’ it variabile
i bil intera
i t ...
– ... e farlo funzionare con diverse quantità di dati
di ingresso
• Farlo può essere molto significativo.
ESPERIMENTI
• Risultati: