Algoritmi di ordinamento in C

annuncio pubblicitario
Algoritmi di ordinamento in C
Giovanni Cintolo ([email protected])
15 luglio 2007 - versione 1
Per semplicità in questi algoritmi ci riferiremo solamente all'ordinamento di
un array di numeri interi.
Ovviamente, gli algoritmi elencati potranno essere usati per ordinare dati di un
qualsiasi tipo su cui sia denita una relazione d'ordine.
1 Merge sort
Il merge sort è un algoritmo di ordinamento abbastanza rapido, che utilizza un
processo di risoluzione ricorsivo. L'idea alla base del merge sort è il procedimento Divide et Impera : si suddivide il problema in sottoproblemi via via più
piccoli, sino a che questi ultimi diventino di semplice risoluzione e poi si combinano le soluzioni al ne di risolvere il problema dato.
Il merge sort opera quindi dividendo l'insieme da ordinare in due metà e procedendo all'ordinamento delle medesime ricorsivamente. Quando si sono divise
tutte le metà si procede alla loro fusione (merge appunto) costruendo un insieme
ordinato.
1.1
Simulazione
1 Presa
1
da http://en.wikipedia.org/wiki/merge_sort.
1
1.2
Spiegazione
Esaminiamo il caso dell'ordinamento crescente.
Consente di dividere l'array in tanti sottoarray, per poi
operarci in ognuno tramite la funzione merge.
Funzione mergesort
In input vengono forniti due sottoarray ordinati. Il compito
di questa funzione è ordinarli tra di loro.
Funzione merge
• Si crea un vettore di appoggio (buf ) di dimensione DIM (è una costante
simbolica, che rappresenta la dimensione del vettore di partenza) dove
posizioneremo gli elementi in ordine corretto, una variabile primo che
punterà all'indice del primo sottoarray (v[iniziale..med] ), cioè iniziale, e
una variabile secondo che punterà all'indice del secondo sottoarray (v[med
+ 1..nale] ), cioè med + 1. L'utilità della variabile appoggio è di indicare
in quale indice del vettore buf posizionare il valore.
• Il ciclo while permette di scorrere i due sottoarray, prenderne il minore e
salvarlo nel vettore buf. Se l'elemento viene preso in v[primo], bisognerà
incrementare solo primo, viceversa per v[secondo]. In ogni caso bisogna
incrementare anche appoggio, visto che abbiamo appena inserito l'elemento
minore in buf[appoggio].
• Alla ne del ciclo while ci saranno due casi:
1. E' nito prima il primo sottoarray. In questo caso non bisogna fare
niente, poiché signica che nella porzione v[secondo..nale] gli elementi sono già nella loro giusta collocazione. La prima porzione
ordinata sarà invece nel vettore buf[iniziale..appoggio - 1] e verrà
copiata in v tramite il ciclo for.
2. E' nito prima il secondo sottoarray, quindi si copiano da v in v stesso
tutti gli elementi, no a med, cominciando dal fondo. A questo punto
risulterà ordinata solo la porzione nale di v, mentre la prima parte
ordinata sarà presente nel vettore buf[iniziale..appoggio - 1], e, grazie
ad un ciclo for, la si copia in v.
1.3
Algoritmo
Ordinamento crescente
void merge(int v[], int iniziale, int med, int finale){
int buf[DIM];
int primo, secondo, appoggio, i;
primo = iniziale;
secondo = med + 1;
2
}
appoggio = iniziale;
while(primo <= med && secondo <= finale){
if(v[primo] <= v[secondo]){
buf[appoggio] = v[primo];
primo++;
}
else{
buf[appoggio] = v[secondo];
secondo++;
}
appoggio++;
}
if(secondo > finale)
for(i = med; i >= primo; i--) {
v[finale] = v[i];
finale--;
}
for(i = iniziale; i < appoggio; i++)
v[i] = buf[i];
void mergesort(int v[], int iniziale, int finale){
int med;
}
if(iniziale < finale){
med = (iniziale + finale) / 2;
mergesort(v, iniziale, med);
mergesort(v, med + 1, finale);
merge(v, iniziale, med, finale);
}
L'unica dierenza sta nel cambiare l'operatore
di confronto tra v[primo] e v[secondo].
Ordinamento decrescente
void merge(int v[], int iniziale, int med, int finale){
int buf[DIM];
int primo, secondo, appoggio, i;
primo = iniziale;
secondo = med + 1;
appoggio = iniziale;
while(primo <= med && secondo <= finale){
if(v[primo] >= v[secondo]){
buf[appoggio] = v[primo];
primo++;
3
}
else{
buf[appoggio] = v[secondo];
secondo++;
}
appoggio++;
}
}
if(secondo > finale)
for(i= med; i>=primo; i--) {
v[finale] = v[i];
finale--;
}
for(i = iniziale; i < appoggio; i++)
v[i] = buf[i];
void mergesort(int v[], int iniziale, int finale){
int med;
}
if(iniziale < finale){
med = (iniziale + finale) / 2;
mergesort(v, iniziale, med);
mergesort(v, med + 1, finale);
merge(v, iniziale, med, finale);
}
2 Insertion sort
L'insertion sort, in italiano ordinamento a inserimento, non è molto diverso dal
modo in cui un essere umano, spesso, ordina un mazzo di carte.
Partendo con la mano sinistra vuota e le carte sul tavolo, se ne preleva una alla
volta e la si inserisce in posizione corretta sulla mano sinistra.
2.1
Spiegazione
Verrà esaminato il caso dell'ordinamento crescente.
• Si utilizzano tre variabili: ind_sist che tiene traccia dell'indice del prossi-
mo elemento da sistemare, ind_contr che memorizza l'indice dell'elemento da controllare e val_sist, in cui viene salvato il valore dell'elemento da
sistemare.
• Inizialmente ind_contr punta all'indice 0 e ind_sist al successivo, mentre
val_sist memorizza il valore dell'elemento di indice ind_sist del vettore.
Se il secondo elemento è minore del primo i due vengono scambiati grazie al
4
ciclo while e al successivo assegnamento v[ind_contr + 1] = val_sist. Nel
caso i due elementi siano già ordinati, l'assegnamento appena citato viene
comunque eettuato, ma senza modicare l'array, visto che l'elemento del
vettore di indice ind_contr + 1 sarà uguale a val_sist.
• Il compito del ciclo while è quello di posizionare il valore che dobbiamo
sistemare (val_sist ) all'interno dell'array. Finché non si trova la posizione
corretta bisognerà spostare, un posto alla volta, gli elementi che sono
maggiori di val_sist.
• Ad ogni iterazione del ciclo for manteniamo ordinato il sottoarray [0..ind_sist
- 1], per poi inserirvi ogni elemento nella sua collocazione appropriata.
2.2
Algoritmo
Ordinamento crescente
void insertionsort(int v[], int dim){
int ind_sist, ind_contr, val_sist;
}
for(ind_sist = 1; ind_sist < dim; ind_sist++){
val_sist = v[ind_sist];
ind_contr = ind_sist - 1;
while(ind_contr >= 0 && v[ind_contr] > val_sist){
v[ind_contr + 1] = v[ind_contr];
ind_contr--;
}
v[ind_contr + 1] = val_sist;
}
Ordinamento decrescente
void insertionsort(int v[], int dim){
int ind_sist, ind_contr, val_sist;
}
for(ind_sist = 1; ind_sist < dim; ind_sist++){
val_sist = v[ind_sist];
ind_contr = ind_sist - 1;
while(ind_contr >= 0 && v[ind_contr] < val_sist){
v[ind_contr + 1] = v[ind_contr];
ind_contr--;
}
v[ind_contr + 1] = val_sist;
}
5
3 Selection sort
L'ordinamento per selezione (selection sort) è un algoritmo di ordinamento che
opera in modo simile all'ordinamento per inserzione; seleziona il numero minore
nella sequenza di partenza e lo sposta nella sequenza ordinata.
3.1
Spiegazione
Esaminiamo il caso dell'ordinamento crescente.
• Si scorre il vettore dal basso verso l'alto.
• Si cerca l'indice del più piccolo elemento dell'array.
• Nella prima iterazione si scambia l'elemento più piccolo con l'elemento in
prima posizione, nella seconda quello più piccolo con l'elemento in seconda
posizione e così via. Quando l'elemento di indice i dovrà essere scambiato
con l'elemento di indice i_min sarà inutile eettuare questa operazione se
i è uguale a i_min.
• Alla ne di ogni iterazione la porzione di array tra gli indici 0 e i è ordinata
e quindi dovremo operare solo nella porzione tra i + 1 e dim - 1.
3.2
Algoritmo
3.2.1
Versione iterativa
Ordinamento crescente
Si mantiene ordinata la parte bassa dell'array.
void selectionSort(int v[], int dim){
int i, j, i_min, temp;
}
for(i = 0; i < dim - 1; i++){
i_min = i;
for(j = i + 1; j < dim; j++)
if(v[j] < v[i_min])
i_min = j;
if(i != i_min){
temp = v[i];
v[i] = v[i_min];
v[i_min] = temp;
}
}
6
Ordinamento decrescente
Si mantiene ordinata la parte bassa dell'array.
void selectionSort(int v[], int dim){
int i, j, i_max, temp;
}
3.2.2
for(i = 0; i < dim - 1; i++){
i_max = i;
for(j = i + 1; j < dim; j++)
if(v[j] > v[i_max])
i_max = j;
if(i != i_max){
temp = v[i];
v[i] = v[i_max];
v[i_max] = temp;
}
}
Versione ricorsiva
Ordinamento crescente
Si mantiene ordinata la parte alta dell'array.
void selectionsort(int v[], int dim){
int i = dim - 1, j, i_max, temp;
}
if(dim > 0){
i_max = i;
for(j = i - 1; j >= 0; j--)
if(v[j] > v[i_max])
i_max = j;
if(i != i_max){
temp = v[i];
v[i] = v[i_max];
v[i_max] = temp;
}
selectionsort(v, dim - 1);
}
4 Bubblesort
Il bubblesort (letteralmente: ordinamento a bolle) è un semplice algoritmo iterativo di ordinamento, anche se non molto eciente.
Il nome dell'algoritmo è dovuto al fatto che, durante l'applicazione del procedimento, i valori vengono spostati all'interno dell'array con una dinamica che
ricorda il movimento delle bollicine in un bicchiere di champagne. In particolare,
7
alcuni elementi attraversano l'array velocemente (come bollicine che emergono
dal fondo del bicchiere), altri più lentamente (a dierenza di quanto avviene
nel caso del bicchiere di champagne, tuttavia, alcuni elementi salgono ma altri
scendono).
Ogni passaggio prevede che gli elementi dell'array siano confrontati a due a due,
procedendo in un verso stabilito a propria scelta. Quindi nel caso si voglia ordinare in senso crescente l'array si hanno due possibilità: spostare l'elemento
più grande verso l'alto o spostare il più piccolo verso il basso.
4.1
Spiegazione
Esaminiamo il caso dell'ordinamento crescente, spostando l'elemento più grande
verso l'alto (algoritmo 4.2.2).
• Saranno confrontati il primo e il secondo elemento, poi il secondo e il terzo,
poi il terzo e il quarto e così via no al confronto fra penultimo e ultimo
elemento.
• Per ogni confronto, se i due elementi adiacenti confrontati non sono ordi-
nati, vengono scambiati.
• Durante ogni iterazione l'elemento più grande raggiunge la sua collo-
cazione denitiva. Quindi, alla prima iterazione, il maggiore risiederà
nell'ultima posizione dell'array.
• Se in un passaggio non viene eettuato nessuno spostamento di elementi,
signica che l'array è ordinato.
4.2
Algoritmo
Nei casi in cui non viene proposto anche l'ordinamento decrescente, basterà
semplicemente cambiare il confronto tra i due elementi adiacenti.
4.2.1
Versione iterativa
Ordinamento crescente
verso il basso.
Ad ogni passaggio, l'elemento più piccolo si muove
void bubblesort(int v[], int dim){
int i = 0, j, ordinato;
int temp;
do{
ordinato = 1;
for(j = dim - 1; j > i; j--)
if(v[j - 1] > v[j]){
temp = v[j];
v[j] = v[j - 1];
8
v[j - 1] = temp;
ordinato = 0;
}
}
i++;
}while(i < dim - 1 && !ordinato);
Ordinamento decrescente
verso il basso.
Ad ogni passaggio, l'elemento più grande si muove
void bubblesort(int v[], int dim){
int i = 0, j, ordinato;
int temp;
}
4.2.2
do{
ordinato = 1;
for(j = dim - 1; j > i; j--)
if(v[j - 1] < v[j]){
temp = v[j];
v[j] = v[j - 1];
v[j - 1] = temp;
ordinato = 0;
}
i++;
}while(i < dim - 1 && !ordinato);
Versione iterativa - Altra soluzione
L'algoritmo precedente lo si può scrivere più semplicemente con due for. In questo caso però l'elemento più grande si sposta verso
l'alto. La condizione di terminazione del secondo for sarebbe anche potuta essere
j < dim - 1. Ci si accorge però che una volta spostato l'elemento maggiore nella
parte alta dell'array, possiamo operare in un suo sottoarray. Sarebbe, quindi,
inutile eettuare ulteriori confronti nella parte superiore dell'array, che risulta
essere già ordinata.
Ordinamento crescente
void bubblesort(int v[], int dim){
int i, j, ordinato = 0;
int temp;
for(i = 1; i < dim && !ordinato; i++){
ordinato = 1;
for(j = 0; j < dim - i; j++){
if(v[j] > v[j + 1]){
temp = v[j];
v[j] = v[j + 1];
9
}
4.2.3
}
}
}
v[j + 1] = temp;
ordinato = 0;
Versione ricorsiva
Ad ogni passaggio, l'elemento più grande si muove
verso l'alto. Come nella soluzione precedente, quella iterativa, una volta che abbiamo spostato l'elemento più grande nell'ultima posizione dell'array, possiamo
operare su un suo sottoarray, visto che la parte nale risulterà ordinata.
.
Ordinamento crescente
void bubblesort(int v[], int dim){
int j, ordinato = 0;
int temp;
}
if(dim > 1){
ordinato = 1;
for(j = 1; j < dim; j++){
if(v[j - 1] > v[j]){
temp = v[j];
v[j] = v[j - 1];
v[j - 1] = temp;
ordinato = 0;
}
}
if(!ordinato)
bubblesort(v, dim - 1);
}
5 Counting sort
Il counting sort è un'algoritmo di ordinamento per valori numerici interi e non è
basato su confronti e scambi, ma per posizionare ordinatamente ogni elemento
dell'array basterà calcolare quanti sono i suoi elementi minori. Si assume che
ogni input sia compreso tra 0 e M (rappresenta il massimo numero dell'array).
5.1
Spiegazione
Esaminiamo il caso dell'ordinamento crescente. L'array ordinato sarà salvato in
un altro array (ris ).
10
• Si prepara un nuovo array occ di dimensione M + 1, che conterrà le occor-
renze di ogni elemento all'interno dell'array e, quindi, lo si inizializza a 0.
L'indice x dell'array occ rappresenta le occorrenze del numero x all'interno
del vettore v.
• Si contano le occorrenze di ogni numero all'interno del vettore. Ovvia-
mente non necessariamente tutti i numeri compresi tra 0 e M sono contenuti al suo interno e quindi alcuni elementi del vettore occ potrebbero
rimanere a 0.
• A questo punto si ha la necessità di conoscere in che posizione bisognerà
inserire l'elemento. Per fare questo ad ogni elemento del vettore delle occorrenze gli si sommano tutte le occorrenze dei numeri precedenti. Quindi
per sapere dove collocare il numero x dell'array v, cioè sapere quanti sono
gli elementi minori di x, basterà guardare il valore all'indice x nel vettore
delle occorrenze.
• Ora nel vettore ris, che deve essere un vettore fornito in input, anch'esso
di dimensione dim, verranno posizionati in ordine gli elementi del vettore
v. Si scorrerà il vettore v dal basso verso l'alto. Per ogni suo elemento si
valuta quanto sono i suoi minori, tramite occ[v[i]], e per posizionarlo correttamente bisogna sottrarre -1, poiché gli indici dei vettori cominciano
da 0. A questo punto sappiamo la posizione precisa in cui collocare l'elemento v[i]. Non bisogna dimenticarsi di diminuire le occorrenze di quel
numero, cioè occ[v[i]], poiché se all'interno dell'array v ci sono elementi uguali, questi non potranno essere collocati nella stessa posizione, ma
dovranno essere adiacenti.
5.2
Algoritmo
Ordinamento crescente
void countingsort(int v[], int dim, int ris[]){
int i, occ[M + 1];
}
for(i = 0; i < M + 1; i++)
occ[i] = 0;
for(i = 0; i < dim; i++)
occ[v[i]]++;
for(i = 1 ; i < M + 1; i++)
occ[i] += occ[i-1];
for(i = dim - 1; i >= 0; i--){
ris[occ[v[i]] - 1] = v[i];
occ[v[i]]--;
}
11
Ordinamento decrescente
void countingsort(int v[], int dim, int ris[]){
int i, occ[M + 1];
}
for(i = 0; i < M + 1; i++)
occ[i] = 0;
for(i = 0; i < dim; i++)
occ[v[i]]++;
for(i = M - 1 ; i >= 0; i--)
occ[i] += occ[i+1];
for(i = dim - 1; i >= 0; i--){
ris[occ[v[i]] - 1] = v[i];
occ[v[i]]--;
}
Riferimenti bibliograci
[1] Prof.ssa A. Raaetà, Dispensa Laboratorio di Programmazione, Università
Ca' Foscari - Venezia
[2] Merge sort, http://it.wikipedia.org/wiki/merge_sort
[3] Insertion sort, http://it.wikipedia.org/wiki/insertion_sort
[4] Selection sort, http://it.wikipedia.org/wiki/selection_sort
[5] Bubble sort, http://it.wikipedia.org/wiki/bubble_sort
[6] Counting sort, http://it.wikipedia.org/wiki/counting_sort
[7] Deitel H. M. e Deitel P. J., Corso Completo di Programmazione, Apogeo, 2
ed.
12
Scarica