PERCORSI ABILITANTI SPECIALI
Università di Pisa
Algoritmi di ordinamento
Classe A042
Informatica
Algoritmica e problem solving per l’insegnamento
Linguaggi di programmazione per l’insegnamento
Laboratorio didattico-pedagogico integrato per l'insegnamento dell'informatica 1
a.a. 2013-14
Anastasia Scalioti
Indice:
1. Ipotesi di lavoro ............................................................................................................................... 3
1.1 Prerequisiti................................................................................................................................. 3
1.2 Obiettivi ...................................................................................................................................... 3
1.3 Metodologie didattiche .............................................................................................................. 3
2. Algoritmi di Ordinamento ................................................................................................................ 4
2.1.Introduzione ............................................................................................................................... 4
2.2- Ordinamento ............................................................................................................................. 5
2.3. Ordinamento per selezione ....................................................................................................... 6
2.4. Insertion sort ............................................................................................................................. 7
2.5.Mergesort ................................................................................................................................... 8
2.6.Quicksort .................................................................................................................................... 9
Allegato1 ............................................................................................................................................ 12
2
1. Ipotesi di lavoro
In base alle Linee Guida ministeriali vigenti si suppone che l’ unità didattica sugli “Algoritmi di
ordinamento” venga trattata nella classe 4a di un Istituto Tecnico settore Informatico. Si suppone
che la classe sia costituita da 24 studenti. L’unità didattica si articola nei seguenti argomenti:
1. selection sort;
2. insertion sort;
3. mergesort;
4. insertionsort.
1.1 Prerequisiti

Concetto di algoritmo;

Basi di programmazione nel linguaggio C;

Concetto di iterazione, array, funzione complessità computazionale;

Paradigma del “divide et impera” e ricorsione;

Saper codificare la scansione, il confronto e lo scambio di elementi di un array;
1.2 Obiettivi

Classificare gli algoritmi di ordinamento;

Comprendere tecniche avanzate di codifica;

Scegliere l’algoritmo adeguato alla situazione;

Apprendere strategie non banali;
1.3 Metodologie didattiche
Nello svolgere l’argomento dell’ordinamento si adotta la metodologia del problem posing, del
problem solving e del cooperative learning, in modo da indirizzare i ragazzi verso una tipologia di
apprendimento attivo, che li coinvolga, li entusiasmi e li diverta anche; al fine di aumentare la
fiducia in se stessi verranno indicati traguardi raggiungibili e di compiti realizzabili. I ragazzi
vengono suddivisi in gruppi di 4:

inizialmente viene proposto loro il problema di ordinare dei cubetti, dei libri numerati, delle
carte da gioco o dei cd, di contare il numero di passi effettuati e confrontare quindi la
velocità delle strategie provate dai singoli gruppi per effettuare l’ordinamento;

viene chiesto loro di pensare a “come” velocizzare il metodo (brainstorming);

in un secondo tempo, vengono presentati gli argomenti dell’unità didattica con l’ausilio di
cubetti numerati che aiutino a capire i singoli passi dell’algoritmo, visto che i ragazzi
3
amano poco le trattazioni teoriche; si partirà dall’argomento più semplice, il selection sort,
per arrivare a quelli che richiedono la ricorsione, di non facile comprensione;

prima ancora di passare a scrivere il codice, vengono invitati a rifare con i cubetti i passi del
singolo algoritmo trattato, in modo da verificare che abbiano recepito la strategia;

per divertirli (è fondamentale nell’apprendimento quanto l’aspetto motivazionale) si
presenterà anche un apposito video al link https://www.youtube.com/user/AlgoRythmics;

nel passare a scrivere il codice vengono usate metodologie interattive, quali quelle proposte
dai Koans o da Code Hunt, che oltre a stimolare i ragazzi grazie alla suddivisione in livelli
di difficoltà crescente, costituiscono strumenti divertenti per apprendere i concetti della
programmazione; verranno proposti sistemi on-line di esercizi con valutazione e testing dei
programmi sottoposti: http://cms.di.unipi.it/.

verrà adottato il linguaggio C;

l’apprendimento dei ragazzi sarà valutato in base all’acquisizione dei concetti e del metodo,
alla capacità di scrivere correttamente le funzioni (di confronto, di scambio, ricorsive),
usare un appropriato linguaggio tecnico, usare con dimestichezza il computer; sarà
apprezzata la capacità di essere autonomi, sia nello svolgimento degli esercizi, sia nel
riuscire a capire gli errori fatti in fase di compilazione; sarà valutato positivamente
l’impegno e l’interesse, esplicitati attraverso l’attenzione e le domande.
2. Algoritmi di Ordinamento
2.1.Introduzione
L’importanza dello studio e dell’analisi degli algoritmi si basa sull’evidente constatazione che per
risolvere uno stesso problema o per eseguire un medesimo compito esistono in generale svariati
procedimenti che si diversificano per comodità, velocità e sicurezza. La necessità di ordinare un
insieme di dati viene riscontrata in molte situazioni della vita quotidiana: fornire un elenco di nomi
in ordine alfabetico, ordinare per autori dei CD, delle canzoni , dei film e così via.
Supponiamo di avere un insieme di oggetti e di voler stabilire se un dato oggetto è presente o meno
nell’elenco e quale posizione occupa. Il procedimento più immediato è quello di esaminare uno per
uno gli oggetti dell’elenco fino a trovare il dato, o comunque fino ad esaurire il campo di ricerca.
Questo è un algoritmo di ricerca sequenziale. Ogni oggetto esaminato ci obbliga a leggere tutto
l’elenco quando il nostro oggetto non vi è incluso; laddove è presente richiede la lettura di circa
metà elenco in media. Se esiste una relazione di ordine totale la ricerca diventa più rapida: è
decisamente più semplice cercare un elemento in un insieme ordinato, anziché in uno disordinato.
Ad esempio, se si deve cercare un nominativo su una lista non ordinata si impiega un tempo
4
maggiore rispetto alla ricerca in una rubrica telefonica. Il problema dell’ordinamento, pertanto,
riveste una fondamentale importanza anche per il problema della ricerca: i due aspetti sono
strettamente connessi.
2.2- Ordinamento
Nell’ambito degli algoritmi non numerici fanno parte da leone per vastità di letteratura quelli di
ordinamento (sorting) rivolti a disporre una fila di oggetti secondo un certo ordine rispetto a
qualche caratteristica; tipicamente si tratta di sistemare numeri in un certo ordine o parole in ordine
alfabetico. Ordinare un array significa confrontare tra di loro i suoi elementi ed effettuare
opportuni scambi in modo tale da disporne il contenuto in ordine crescente o decrescente a
seconda del tipo di ordinamento voluto.
Esistono numerose tipologie di algoritmi di ordinamento più o meno complessi e più o meno veloci.
Le diverse strategie, generalmente, possono differire anche di molto tra di loro. In prima
approssimazione si potrebbe affermare che la «complessità» dell’algoritmo è direttamente
proporzionale alla velocità di ordinamento del medesimo. I moderni elaboratori sono così veloci che
su piccoli array diventa praticamente ininfluente la scelta del tipo di algoritmo, per cui le
prestazioni di un buon sort diventano significativamente apprezzabili solo se questo viene applicato
a un vettore con un numero di elementi dell’ordine dei milioni. In ogni caso, visto che le due
operazioni fondamentali su cui questi tipi di algoritmi si basano sono il confronto e lo scambio di
elementi, risulta abbastanza chiaro che adottando una strategia che minimizzi il numero
complessivo di queste si ottiene un ordinamento più veloce. Ovviamente, la velocità del sort
dipende anche dalla distribuzione iniziale dei valori nell’array, e questo non è tanto legato al
numero di confronti quanto a quello degli scambi che saranno effettuati. Si tenga infatti presente
che, in termini di velocità, un’operazione di scambio è più «costosa» di un’operazione di confronto.
C’è da precisare che alcuni algoritmi operano direttamente sulla struttura dati che contiene gli
elementi da ordinare , mentre altri utilizzano una seconda struttura di “appoggio”. Si definiscono
pertanto “in sito” (in place) gli algoritmi che operano direttamente sul vettore originale, mentre si
dicono “non in sito”quelli che producono un nuovo vettore ordinato, lasciando inalterato il vettore
di origine. I primi consentono una minore occupazione dello spazio di memoria, in quanto non
generano vettori duplicati.
Analizzeremo i seguenti algoritmi di ordinamento: selection sort, insertiion sort, merge sort e
quicksort. I primi due appartengono alla cosiddetta categoria dei “metodi ingenui”perché applicano
metodi elementari, mentre gli altri due appartengono alla categoria dei “metodi avanzati”, in quanto
5
consentono di migliorare le prestazioni dei metodi ingenui, a scapito, però, della leggibilità del
codice; sono di difficile comprensione e sfruttano tecniche ricorsive.
2.3. Ordinamento per selezione
L’ordinamento per selezione, detto selection sort, consiste nell’uso ripetuto di una routine di ricerca
del minimo:
al passo generico i = 0, 1,…,n-1 viene selezionato l’elemento che occuperà la posizione i

della sequenza ordinata;
al termine del passo i gli i + 1 elementi selezionati fino a quel momento coincidono con i

primi i + 1 elementi della sequenza ordinata, cioè sono parte della soluzione.
Ad esempio, per ordinare il vettore [ 21, 34, 18, 9, 14, 41, 3, 38] si dovrà :
1. cercare il più piccolo elemento dell’array
21
34
18
9
14
41
3
38
21
38
2. scambiare l'elemento più piccolo con l'elemento in prima posizione;
3
34
18
9
14
41
3. incrementare l’indice e ricominciare il ciclo: si ripete la stessa operazione nel sottovettore
che inizia per 34; si trova il minimo, 9, e lo si scambia con il 34:
3
34
18
9
14
41
21
38
3
9
18
34
14
41
21
38
21
38
4. incrementare l’indice e ripetere il ciclo nel sottovettore che inizia per 18:
3
9
14
34
18
41
5. incrementare l’indice e ripetere il ciclo nel sottovettore che inizia dal 34:
3
9
14
18
34
41
21
38
3
9
14
18
21
41
34
38
3
9
14
18
21
34
41
38
3
9
14
18
21
34
38
41
E così via:
6
Possiamo notare che sono stati effettuati molti scambi necessari solo a liberare lo spazio per il
numero trovato, mentre il numero che in origine occupava tale posizione non viene analizzato, ma
soltanto spostato. L’algoritmo ha complessità n2, cioè ha complessità quadratica rispetto al numero
di elementi da ordinare. Tale complessità è sempre raggiunta a livello di tempo, qualunque sia la
sequenza iniziale.
2.4. Insertion sort
L’algoritmo per inserimento, relativamente semplice, funziona con lo stesso meccanismo usato da
molte persone per ordinare una mano di bridge o ramino: si inizia con la mano sinistra vuota, si
prende una carta alla volta e la si inserisce nella corretta posizione dopo averla confrontata con ogni
altra carta, da destra a sinistra. I passi sono i seguenti:
1. individuare la posizione che deve occupare rispetto a quelli già presenti;
2. predisporre un posto libero;
3. effettuare l’inserimento.
Supponiamo, ad esempio, di voler disporre in un vettore in ordine crescente i seguenti numeri: 21,
34, 18, 9, 14, 41, 3, 38. Il primo valore (21) viene inserito nella prima cella
21
Inserendo il secondo numero, 34, non c’è da effettuare altre operazioni. Quando si va ad inserire il
numero 18, invece, occorre scalare i numeri precedenti per lasciare il posto al 18, che dovrà essere
posizionato in cima. Avremo quindi i seguenti passi:
21
18
34
21
34
21
34
Per il numero 9 si procede come per il 18:
9
18
21
34
Il 14 va inserito tra il 9 ed il 18, lasciando uno spazio tra i due, il 41 non richiede spostamenti,
mentre il 3 richiede lo spostamento di tutti gli altri valori ; infine il 38 va posizionato prima del 41.
9
14
18
21
34
9
14
18
21
34
41
3
9
14
18
21
34
41
3
9
14
18
21
34
38
41
7
L’algoritmo possiamo descriverlo nei seguenti passi:

confrontare l’elemento da inserire con quello presente nell’ultima posizione;

spostare gli elementi verso destra finchè non si trova la posizione in cui inserire il nuovo
elemento (viene creato un “buco” che viene spostato da destra a sinistra);

inserire il nuovo elemento in quella posizione-
Tale metodo si presta per ordinare numeri non ancora presenti in memoria. Nel caso in cui si
volesse ordinare un vettore di numeri già presenti, si dovrà provvedere a

prelevare il numero da ordinare dall’ultima posizione, per liberare un “buco” e consentire un
eventuale shift a destra;

oppure si usa un ordinamento “esterno”usando un vettore nuovo per poter posizionare i
numeri ordinati.
Nell’Allegato 1 è presentata una versione in place dell’algoritmo. Tale algoritmo risulta efficiente
solo nel caso in cui si debba ordinare un piccolo numero di elementi. Anche l’insertion sort è un
algoritmo di ordinamento con complessità quadratica rispetto al numero degli elementi da ordinare,
come il selection sort, ma si differenzia da quest’ultimo in quanto può richiedere un tempo
significativamente inferiore per certe sequenze di elementi; se la sequenza iniziale è già in ordine o
non è ordinato solo un numero trascurabile di elementi, l’insetion sort ha complessità dell’ordine di
n, mentre la complessità del selection sort rimane sempre quadratica.
2.5.Mergesort
Gli algoritmi precedenti, facilmente codificabili, che usano strategie vicine a quelle della vita
quotidiana, presentano un’elevata complessità di calcolo risultando, pertanto, poco efficienti nel
caso di problemi di dimensioni notevoli. Il mergesort (ed anche il quicksort) risultano più efficienti,
ma la strategia adottata e la codifica non sono di facile comprensione. 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, che consiste nella suddivisione del problema in
sottoproblemi via via più piccoli. Il merge sort, inventato da John von Neumann nel 1945,
concettualmente opera nel seguente modo:
1. la sequenza viene divisa (divide) in due metà (se la sequenza contiene un numero dispari di
elementi, viene divisa in due sottosequenze di cui la prima ha un elemento in più della
seconda);
2. ognuna di queste sottosequenze viene ordinata, applicando ricorsivamente l'algoritmo
(impera);
8
3. le due sottosequenze ordinate vengono fuse (ricombina - merge). Per fare questo, si estrae
ripetutamente il minimo delle due sottosequenze e lo si pone nella sequenza in uscita, che
risulterà ordinata;
Lo pseudocodice è il seguente:
Mergesort (a, sinistra, destra):
IF (sinistra<destra){
Per implementare l’algoritmo è necessario specificare come
fondere le due sotto-sequenze ordinate; il metodo che opera in loco
risulta piuttosto complicato; l’uso di un array addizionale, invece,
centro=(sinistra+destra):2;
Mergesort(a,
sinistra,
centro);
sicuramente meno complicato, si basa sulla stessa strategia che si
adotta per fondere due mazzi di carte ordinati in modo crescente:
ad ogni passo per stabilire la carta di valore minimo nei due mazzi
Mergesort (a, centro+1,
destra);
Fusione (a, sinistra, centro,
basta confrontare le due carte in cima ai mazzi stessi ed inserirla in
fondo al nuovo mazzo.
destra);
Esempio di funzionamento
Supponendo di dover ordinare la sequenza [11 3 16 2 1 5 9 0], l'algoritmo procede ricorsivamente
dividendola in metà successive, fino ad arrivare alle coppie
[11 3] [16 2] [1 5] [9 0]
A questo punto si fondono (merge) in maniera ordinata gli elementi, riunendo le metà:
[3 11] [2 16] [1 5] [0 9]
Al passo successivo, si fondono le coppie di array di due elementi:
[2 3 11 16] [0 1 5 9]
Infine, fondendo le due sequenze di quattro elementi, si ottiene la sequenza ordinata:
[0 1 2 3 5 9 11 16]
L'esecuzione ricorsiva all'interno del calcolatore non avviene nell'ordine descritto sopra. Tuttavia, si
è formulato l'esempio in questo modo per renderlo più comprensibile. La complessità è dell’ordine
di n logn e non dipende dalla disposizione iniziale dei valori negli elementi dell’array.
2.6.Quicksort
9
Quicksort, l’algoritmo di ordinamento per distribuzione (letteralmente “ordinamento rapido”), è
un ottimo algoritmo ricorsivo "in place" che si basa sul paradigma "divide et impera" come il
merge sort. Si differenzia da quest’ultimo in quanto la fase di decomposizione è più evoluta,
mentre quella di ricombinazione è più immediata. I pregi di tale algoritmo sono:

facilità di implementazione;

è un algoritmo general purpose, che ha un buon comportamento in un’ampia varietà di
situazioni ed in molti casi richiede meno risorse di qualsiasi altro algoritmo;

è un ordinamento in sito, quindi non richiede vettori temporanei di appoggio;

ha una buona efficienza (dipendente dall’elemento scelto come pivot); le sue prestazioni
sono le migliori tra quelle degli algoritmi basati sul confronto.
Il principio di funzionamento prevede di
1.
definire un elemento, chiamato pivot o perno, nel caso che la sequenza abbia almeno
due elementi; gli elementi di valore inferiore al valore di tale perno vengono posti a
sinistra e gli elementi di valore superiore vengono posti a destra: la posizione di
pivot determina due sottovettori (decomposizione);
2.
le sotto-sequenze vengono ordinate ricorsivamente nello stesso modo, individuando
un nuovo perno, spostando gli elementi e ricercando un nuovo punto di pivot; il
procedimento viene ripetuto fino ad ottenere sottovettori di dimensione unitaria
(ricorsione);
3.
i sottovettori di dimensione unitaria, che risultano ordinati, vengono concatenati
(implicitamente) in un’unica sequenza ordinata (ricombinazione).
La realizzazione ricosiva è la seguente:
Quicksort ( a, sinistra, destra):
0 ≤ sinistra, destra≤ n-1
se (inizio < fine) {
scegli pivot nell’intervallo [sinistra…destra];
perno = Distribuzione (a, sinistra, pivot, destra);
quicksort (a, sinistra, pivot – 1);
quicksort (a, pivot+1, destra);
}
E’ fondamentale la scelta della partizione per individuare il nuovo punto di pivot. Se si considera il
vettore [42, 69, 12, 54, 88, 24, 95, 67] e consideriamo come pivot corrente la posizione 3, il valore
della cella 3, cioè 54, sarà il perno:
10

si cercano gli elementi maggiori del perno che si trovano alla sua sinistra;

si cercano gli elementi minori del perno (54) che si trovano nella sezione di destra;

si esegue lo “scambio a coppie”finchè si individua una posizione tale per cui tutti gli
elementi a sinistra sono inferiori a perno e tutti gli elementi a destra sono superiori a perno.
Operativamente con due indici scorriamo il vettore da sinistra verso destra e poi da destra verso
sinistra, in modo da effettuare le ricerche e gli scambi suddetti. Avremo:
[42, 24, 12, 54, 88, 69, 95, 67]
Si divide il vettore in due sottovettori:
[42, 24, 12, 54] e [88, 69, 95, 67]
Prendiamo come valore del perno il 23 nel primo sottovettore ed il 69 nel secondo sottovettore,
quindi avremo i seguenti scambi:
[12 , 24, 42, 54] e [67, 69, 95, 88 ]
Poiché non ci sono altri scambi da fare, si individua i nuovi pivot futuro (le vecchie posizioni 1 e5):
[ 12, 24]
[42, 54]
[ 67, 69]
[95, 88]
Prendiamo come perno il primo elemento dei quattro minivettori; solo nell’ultimo c’è da fare lo
scambio, quindi si avranno 8 vettori costituiti da un solo elemento: tutti gli elementi si trovano nella
posizione corretta e l’algoritmo termina:
[ 12 ]
[ 24 ]
[ 42 ] [ 54 ]
[ 67 ] [ 69 ] [ 88 ]
[ 95 ]
[ 12 24 42 54 67 69 88 95]
L’algoritmo è tanto più efficiente quanto più l’elemento scelto partiziona perfettamente il vettore:
occorrerebbe trovare l’elemento mediano, ma l’algoritmo si complicherebbe. Si potrebbe anche
pensare di generare a caso l’elemento perno, ma la scelta più semplice è quella del primo elemento
del sottovettore oppure dell’elemento centrale: anche se non viene individuato l’elemento ottimale ,
tali operazioni non introducono elaborazioni aggiuntive e richiedono solo [N log N] passi. Gli
svantaggi sono dati dal fatto che non è stabile; nel caso peggiore (il pivot coincide con l’elemento
massimo) ha un comportamento quadratico ed è particolarmente fragile: un semplice errore nella
sua implementazione può passare inosservato,
ma causare in certe situazioni un drastico
peggioramento delle prestazioni. Una possibile implementazione nel linguaggio C è proposta
nell’Allegato 1.
11
Allegato1
Esempio1:Selection sort
#include <stdio.h>
#include <stdlib.h>
void selection_sort(int x[], int n)
{
int min=0;
int i=0;
int j ;
int app;
for (j=0; j<n-1; j++)
{
min=j;
for (i=j+1; i<n; i++)
{
if( x[i]<x[min])
min= i;
}
app=x[j];
x[j]=x[min];
x[min]=app;
}
}
void stampa_array (int x[], int n)
{
int i;
for (i=0;i<n; i++)
{
printf("%d ", x[i]);
}
printf("\n");
}
int main()
{
int a[8]={35,17,28,54,13,19,75,8};
stampa_array(a,8);
selection_sort(a,8);
stampa_array(a,8);
}
Esempio 2 : Insertion sort
#include <stdio.h>
#include <stdlib.h>
void insertion_sort(int x[], int n)
{
int i, j, app;
for (i=1; i<n; i++)
{
app = x[i];
j = i-1;
12
while ((j>=0) && (x[j]>app))
{
x[j+1] = x[j];
j--;
}
x[j+1] = app;
}
}
void stampa_array (int x[], int n)
{
int i;
for (i=0;i<n; i++)
{
printf("%d ", x[i]);
}
printf("\n");
}
int main()
{
int a[8]={35,17,28,54,13,19,75,8};
stampa_array(a,8);
insertion_sort(a,8);
stampa_array(a,8);
}
Esempio3:Mergesort
#include <stdio.h>
#include <stdlib.h>
void merge(int a[], int left, int center, int right)
{
int i, j, k;
int b[10];
i = left; j = center+1; k = 0;
while ((i<=center) && (j<=right))
{
if (a[i] <= a[j]) { b[k] = a[i]; i++; }
else { b[k] = a[j]; j++;}
k++;
}
while (i<=center)
{
b[k] = a[i]; i++; k++;
}
while (j<=right)
{
b[k] = a[j]; j++; k++;
}
for (k=left; k<=right; k++)
a[k] = b[k-left];
13
}
void merge_sort(int a[], int left, int right)
{
int center;
if (left<right)
{
center = (left+right)/2;
merge_sort(a, left, center);
merge_sort(a, center+1, right);
merge(a, left, center, right);
}
}
void stampa_array (int x[], int n)
{
int i;
for (i=0;i<n; i++)
{
printf("%d ", x[i]);
}
printf("\n");
}
int main()
{
int n=8;
int a[8]={35,17,28,54,13,19,75,8};
stampa_array(a,8);
merge_sort(a,0,n-1);
stampa_array(a,8);
}
Esempio4: Quicksort
#include <stdio.h>
#include <stdlib.h>
void scambia(int *a, int *b)
{
int c;
c=*a;
*a=*b;
*b=c;
}
void quick_sort(int v[],int sin, int des)
{
int i, j, media;
media = (v[sin]+v[des])/2;
i=sin;
j=des;
do {
14
while(v[i]<media) i = i + 1;
while(media<v[j]) j = j - 1;
if(i<=j)
{
scambia(&v[i], &v[j]);
i = i + 1;
j = j - 1;
}
}
while (j>=i);
if (sin<j) quick_sort(v,sin, j);
if (i<des) quick_sort(v,i, des);
}
void stampa_array (int x[], int n)
{
int i;
for (i=0;i<n; i++)
{
printf("%d ", x[i]);
}
printf("\n");
}
int main()
{
int n=8;
int a[8]={35,17,28,54,13,19,75,8};
stampa_array(a,8);
quick_sort(a,0,n-1);
stampa_array(a,8);
}
15