Fondamenti d’Informatica 1
A.A. 2016/2017
Corsi di Laurea in Ing. Civile e Ing. per l’Ambiente e il Territorio
Docente: Giorgio Fumera
Università degli Studi di Cagliari
Formulazione di algoritmi
Sommario
I
Approccio alla scrittura di programmi
I
Esempi di formulazione di algoritmi
I
Proprietà degli algoritmi: correttezza ed efficienza
Algoritmi notevoli: ricerca e ordinamento
I
–
–
–
–
–
ricerca sequenziale
ricerca binaria su sequenze ordinate
ordinamento per selezione (selection sort)
ordinamento “a bolle” (bubble sort)
ordinamento per inserimento (insertion sort)
Algoritmi e programmi
Algoritmo: descrizione del procedimento per l’esecuzione di una
data operazione, espressa in modo non ambiguo, in un linguaggio
comprensibile da un dato esecutore, in termini di una sequenza
finita di azioni, ciascuna delle quali sia eseguibile dall’esecutore.
Programma: rappresentazione di un algoritmo in un linguaggio (di
programmazione) comprensibile da un calcolatore (che ne sarà
l’esecutore).
Approccio alla scrittura di programmi
1. Comprendere il problema da risolvere: quali sono i dati da
elaborare e il risultato desiderato?
2. Definire un procedimento risolutivo (algoritmo), espresso
inizialmente anche in modo informale, per esempio in
linguaggio naturale, ma senza ambiguità
3. Riformulare l’algoritmo in termini delle operazioni esprimibili
nel linguaggio di programmazione scelto
4. Tradurre l’algoritmo in un programma codificato in tale
linguaggio
Formulazione di algoritmi
Quando si formula un algoritmo, o se ne verifica la correttezza, o si
vuole comprenderne il funzionamento, è sempre utile provare ad
eseguirlo (a mente, o con carta e matita) per alcuni possibili valori
dei dati di ingresso.
Di seguito si presentano alcuni esempi di formulazione di algoritmi
(passo 2 dell’approccio descritto sopra).
Se l’esecutore di riferimento è un calcolatore, nella formulazione
dell’algoritmo si deve prestare attenzione ai risultati intermedi di
cui si dovrà “tener traccia” durante l’elaborazione. Tali risultati
dovranno infatti essere memorizzati in opportune celle di memoria.
Formulazione di algoritmi: esempi
1. Calcolare la somma di una sequenza di numeri
2. Calcolare il massimo di una sequenza di numeri
3. Calcolare il massimo comun divisore (MCD) di due numeri
naturali
4. Calcolare il minimo comune multiplo (MCM) di due numeri
naturali
Formulazione di algoritmi: problemi e istanze
Si noti che negli esempi precedenti si richiede non la risoluzione di
una specifica istanza di un problema (per esempio, sommare i
numeri 5, −7, 4, 9, −2), ma la definizione di un procedimento in
grado di risolvere tutte le possibili istanze di tale problema (per
esempio, sommare una qualsiasi sequenza di numeri).
Più precisamente, per istanza s’intende uno specifico valore dei
dati d’ingresso di un problema (per esempio, la sequenza
5, −7, 4, 9, −2, nel caso della somma di una sequenza di
numeri).
Questa è una caratteristica generale degli algoritmi: un algoritmo
deve essere in grado di risolvere tutte le possibili istanze (o un dato
sottoinsieme delle istanze) di un problema.
Formulazione di algoritmi: livello di dettaglio
Negli esempi che seguono gli algoritmi saranno descritti a un livello
di dettaglio più alto (cioè, in termini di operazioni molto
elementari) rispetto a quanto normalmente si farebbe per
descriverli a beneficio di un esecutore umano.
Questo è dovuto al fatto che i linguaggi di programmazione
consentono di esprimere solo operazioni molto elementari, come si
vedrà più avanti.
1. Somma di una sequenza di numeri
Dati da elaborare: una sequenza di numeri.
Risultato desiderato: la somma di tali numeri.
Un possibile algoritmo, espresso in modo informale: sommare il
secondo numero al primo, poi sommare il terzo al risultato
precedente, e così via fino all’ultimo numero, tenendo traccia di
volta in volta solo dell’ultima somma parziale calcolata.
Notare che i numeri possono essere scanditi in un qualsiasi ordine
(l’operazione di somma è commutativa); dal punto di vista della
descrizione e dell’esecuzione dell’algoritmo è però conveniente
considerare lo stesso ordine nel quale i numeri si trovano nella
sequenza.
1. Somma di un insieme di numeri: esempio
Per comprendere meglio l’algoritmo appena descritto, si consideri la sua
applicazione a una specifica istanza: la sequenza 5, −7, 4, 9, −2.
Somma iniziale: 0
I
primo numero: 5
nuova somma parziale: 0 + 5 = 5
I
secondo numero: −7
nuova somma parziale: 5 + (−7) = −2
I
terzo numero: 4
nuova somma parziale: −2 + 4 = 2
I
quarto numero: 9
nuova somma parziale: 2 + 9 = 11
I
quinto numero: −2
nuova somma parziale: 11 + (−2) = 9
Risultato: 9.
2. Massimo di una sequenza di numeri
Dati da elaborare: una sequenza di numeri.
Risultato desiderato: il più grande di tali numeri.
Un possibile algoritmo:
I
assumere inizialmente che il numero maggiore sia il primo
della sequenza
I
scorrere quindi la sequenza dal secondo all’ultimo elemento,
confrontare ciascun elemento con il valore più grande
osservato fino a quel momento, e aggiornare quest’ultimo se
risultasse inferiore all’elemento in esame
Notare che durante l’esecuzione dell’algoritmo l’esecutore dovrà
tener traccia solo del valore più grande tra quelli già osservati.
2. Massimo di una sequenza di numeri
Esempio: calcolare il massimo tra i valori 5, −7, 4, 9, −2.
I
si assume che il valore maggiore sia 5 (il primo numero della
sequenza)
I
secondo numero: −7
il valore maggiore tra 5 e −7 è 5
I
terzo numero: 4
il valore maggiore tra 5 e 4 è 5
I
quarto numero: 9
il valore maggiore tra 5 e 9 è 9
I
quinto numero: −2
il valore maggiore tra 9 e −2 è 9
Risultato: 9.
3. Massimo comun divisore
Dati da elaborare: due numeri naturali m e n.
Risultato desiderato: il più grande numero naturale che sia divisore di
entrambi, indicato con MCD(m, n).
Un possibile algoritmo, espresso a un livello di dettaglio adeguato per un
esecutore umano:
1. scomporre m e n nei loro fattori primi
2. moltiplicare tra loro i fattori comuni, ciascuno con il minimo
esponente con cui compare nei due numeri
Se l’esecutore non fosse in grado di eseguire alcune delle operazioni
indicate, come accade in particolare per i calcolatori (per esempio, la
scomposizione in fattori primi di un numero), sarebbe necessario
descrivere anche il procedimento per la loro esecuzione in termini di
operazioni più elementari che l’esecutore sia in grado di eseguire (per
esempio, somme, sottrazioni, confronti tra numeri, ecc.).
3. Massimo comun divisore
Il MCD può anche essere anche ottenuto con un procedimento più
semplice da descrivere, e composto solo da operazioni molto
elementari, tenendo conto che:
I
MCD(m, n) ∈ {1, . . . , min{m, n}}
I
per definizione, MCD(m, n) è il più grande divisore comune di
m e n nell’insieme indicato sopra
L’algoritmo è allora il seguente: scandire i valori 1, 2, . . .,
min{m, n} dal più grande al più piccolo, e in ogni passo
verificare se il valore in esame sia un divisore sia di m che di n: in
caso affermativo si è trovato il MCD, altrimenti si procede
analizzando il valore successivo.
3. Massimo comun divisore
Esempio: calcolare il MCD tra 27 e 18.
MCD(27, 18) ∈ {1, . . . , 18}, quindi si considerano i valori
18, 17, 16, . . . :
I
18 è un divisore sia di 27 che di 18? no
I
17 è un divisore sia di 27 che di 18? no
I
16 è un divisore sia di 27 che di 18? no
I
...
I
10 è un divisore sia di 27 che di 18? no
I
9 è un divisore sia di 27 che di 18? SÌ
Risultato: MCD(27, 18) = 9.
3. Massimo comun divisore
Un altro possibile algoritmo si può ricavare da un teorema dimostrato dal
matematico greco Euclide (c. 300 a.C.).
Dati due numeri naturali m e n, con m 6= n:
MCD(m, n) = MCD(min{m, n}, max{m, n} − min{m, n})
Dimostrazione. Se m > n (la dimostrazione per m < n è analoga):
I
se k è un divisore comune di m e n, allora è anche divisore di m − n;
infatti, per ipotesi esistono due numeri naturali a e b tali che
m = k × a e n = k × b, e quindi m − n = k × (a − b)
I
se k è un divisore comune di m − n e n, allora è anche divisore di m;
infatti, per ipotesi esistono due numeri naturali c e d tali che
m − n = k × c e n = k × d, e quindi m = k × (c − d)
I
questo implica che i divisori comuni di (m, n) coincidono con quelli
di (m − n, n), e quindi anche il loro MCD sarà identico
3. Massimo comun divisore
Applicando ripetutamente il teorema di Euclide a partire dai valori
di interesse, m e n, è facile convincersi che dopo un numero finito
di passi si otterranno due valori identici, il cui MCD
(corrispondente per definizione agli stessi valori) sarà anche uguale
al MCD tra m e n.
Questo consente di formulare il seguente algoritmo (noto come
algoritmo di Euclide): dati due numeri naturali m e n, calcolare
ripetutamente una nuova coppia di numeri ottenuti dalla coppia
precedente considerando il numero più piccolo e la differenza tra
il maggiore e il minore, fino a ottenere due valori identici, che
coincideranno con MCD(m, n).
3. Massimo comun divisore
Esempio: calcolo di MCD(36, 28) con l’algoritmo di Euclide:
MCD(36, 28)
I
= MCD (28, 8)
I
= MCD (20, 8)
I
= MCD (12, 8)
I
= MCD (8, 4)
I
= MCD (4, 4) = 4
4. Minimo comune multiplo
Dati da elaborare: due numeri naturali m e n.
Risultato desiderato: il più piccolo numero naturale che sia
multiplo di entrambi, indicato con MCM(m, n).
Anche in questo caso si possono formulare almeno due possibili
algoritmi:
I
scomporre m e n nei loro fattori primi, e moltiplicare tra loro
tutti i fattori ottenuti, ciascuno elevato al massimo esponente
con cui compare nei due numeri
I
poiché MCM(m, n) ∈ {max{m, n}, . . . , m × n}, scandire tali
valori dal più piccolo al più grande e in ogni passo verificare
se il valore in esame sia un multiplo sia di m che di n: in caso
affermativo si è trovato il MCM, altrimenti si procede
analizzando il valore successivo
Osservazioni
Alcune osservazioni generali sulla formulazione di algoritmi, che
possono essere ricavate dagli esempi precedenti:
I
possono esistere diversi algoritmi in grado di risolvere uno
stesso problema
I
algoritmi diversi per risolvere uno stesso problema possono
essere più o meno difficili da formulare e da descrivere (si veda
il caso del MCD), e possono richiedere l’esecuzione di un
numero diverso di operazioni
I
in alcuni casi problemi diversi possono essere risolti con
algoritmi simili (si veda il caso degli algoritmi per la somma e
il calcolo del massimo di una sequenza di numeri, oppure per
il calcolo del MCD e del MCM)
(cont.)
Osservazioni
(cont.)
I
I
ogni algoritmo può essere espresso a diversi livelli di dettaglio,
ovvero in termini di operazioni più o meno elementari; il livello
di dettaglio adeguato corrisponde alle operazioni che
l’esecutore è in grado di eseguire
nella maggior parte degli algoritmi (compresi gli esempi visti in
precedenza) ricorrono due schemi esecutivi, che per questo
motivo corrispondono a istruzioni presenti in tutti i linguaggi
di programmazione:
– scelta tra più alternative: se si verifica una certa condizione,
allora si esegua una certa sequenza di operazioni, altrimenti
se ne esegua un’altra (istruzione condizionale)
– ripetizione di una sequenza di operazioni per un certo numero
di volte, o su tutti gli elementi di un certo insieme, o finché
una certa condizione è vera (istruzione iterativa)
Proprietà degli algoritmi: correttezza
Un algoritmo è corretto se produce il risultato desiderato per ogni
possibile istanza del problema.
Il metodo più “semplice” per verificare la correttezza di un dato
algoritmo consiste nell’eseguirlo per tutte le istanze del problema
I
in pratica questo è impossibile, se il numero d’istanze è molto
grande (in teoria può anche essere infinito)
I
inoltre, un algoritmo non corretto potrebbe non terminare mai
per qualche istanza del problema
Di norma, la correttezza deve quindi essere dimostrata con
procedimenti analoghi alle dimostrazioni dei teoremi matematici.
La non correttezza può invece essere dimostrata individuando
anche un singolo controesempio, cioè un’istanza per la quale
l’algoritmo non produce il risultato desiderato.
Proprietà degli algoritmi: efficienza
Un algoritmo A è più efficiente di un algoritmo B, se risolve lo stesso
problema usando meno risorse.
Questa proprietà è anche indicata come complessità computazionale.
Risorse necessarie a un calcolatore per l’esecuzione di un algoritmo:
I
quantità di memoria (complessità spaziale)
I
tempo d’esecuzione (complessità temporale)
Il tempo d’esecuzione dipende però dalle caratteristiche dell’esecutore
(calcolatore). Per valutare la complessità temporale indipendentemente
dal calcolatore, si considera il numero di operazioni richieste
dall’algoritmo.
La valutazione dell’efficienza viene di norma svolta nel caso peggiore,
cioè considerando l’istanza che richiede la maggior quantità di memoria o
il numero maggiore di operazioni.
Algoritmi di ricerca e ordinamento
Si considerino i seguenti problemi:
I
determinare se un certo oggetto è presente in una data
sequenza di oggetti (ricerca)
I
determinare se un certo oggetto è presente in una data
sequenza, i cui elementi siano ordinati secondo un certo
criterio (ricerca in sequenze ordinate)
I
ordinare gli elementi di una data sequenza, secondo un certo
criterio (ordinamento)
Per semplicità, di seguito si considerano sequenze di numeri. Gli
algoritmi presentati sono però applicabili a dati di qualsiasi natura.
Algoritmo di ricerca sequenziale
Problema: determinare se un certo numero è presente in una data
sequenza di numeri.
Dati da elaborare: una sequenza di numeri, e il numero da cercare.
Risultato desiderato: una risposta affermativa (“sì”) o negativa
(“no”).
Un possibile algoritmo: scandire la sequenza dal primo all’ultimo
elemento, e confrontare in ogni passo l’elemento in esame con il
valore cercato; se i due valori fossero identici, interrompere la
scansione della sequenza e rispondere “sì”, concludendo il
procedimento; se si è terminata la scansione della sequenza (e
quindi nessuno dei suoi elementi corrisponde al valore cercato),
rispondere “no”.
Esempio
Il numero 31 è presente nella sequenza (52, −48, −34, 75, −80)?
I
52 è diverso da 31
I
−48 è diverso da 31
I
−34 è diverso da 31
I
75 è diverso da 31
I
−80 è diverso da 31, e non ci sono altri elementi
Risultato: no (31 non è presente nella sequenza considerata).
Esempio
Il numero −34 è presente nella sequenza (52, −48, −34, 75, −80)?
I
52 è diverso a −34
I
−48 è diverso da −34
I
−34 è uguale a −34: si trascurano gli elementi successivi
della sequenza, e il risultato è sì (−34 è presente nella
sequenza considerata)
Algoritmo di ricerca sequenziale: efficienza
L’algoritmo di ricerca sequenziale può essere applicato a qualsiasi
sequenza, indipendentemente dall’ordine degli elementi. La sua
efficienza in termini del numero di operazioni richieste (complessità
temporale) può essere facilmente valutata, nel caso peggiore,
tenendo conto che:
I
l’algoritmo consiste in una serie di confronti tra coppie di
elementi (l’oggetto da cercare e un elemento della sequenza)
I
il caso peggiore corrisponde all’istanza che richiede il maggior
numero di confronti, a parità della lunghezza della sequenza
È facile convincersi che tra tutte le istanze in cui la sequenza ha
una data lunghezza N, il caso peggiore è quello in cui l’oggetto
cercato si trova nell’ultima posizione della sequenza, oppure non è
presente in essa: in questi casi il numero di confronti è pari a N.
Algoritmo di ricerca binaria
Un algoritmo più efficiente può essere formulato per il caso particolare in cui la sequenza sia ordinata secondo un certo criterio,
usando un approccio analogo a quello che si segue nella ricerca di
un nome in un elenco telefonico.
Informalmente: si apre l’elenco in corrispondenza delle due pagine
centrali (approsimativamente); se il nome cercato si trova in tali
pagine, la ricerca termina; altrimenti:
I
se il nome cercato precede (in ordine alfabetico) i nomi nelle
pagine considerate, la ricerca prosegue nelle pagine
precedenti, nello stesso modo (aprendo l’elenco più o meno
a metà di tali pagine, ecc.)
I
se il nome cercato segue quelli nelle pagine considerate, la
ricerca procede nelle pagine successive
Algoritmo di ricerca binaria
In modo più formale: si confronta l’elemento da cercare (indicato
con x ) con quello al centro della sequenza (indicato con c); se x è
uguale a c la ricerca termina (la risposta è “sì”); in caso contrario:
I
se x precede c, la ricerca procede nello stesso modo tra gli
elementi che precedono c
I
se x segue c, la ricerca procede nello stesso modo tra gli
elementi che seguono c
L’algoritmo termina:
I
non appena si trova il valore cercato: il risultato è “sì”
I
quando la sottosequenza da considerare non contiene nessun
elemento: il risultato è “no”
Nota: se il numero N di elementi della sequenza (o sottosequenza)
considerata è pari, si considera come elemento centrale quello in
posizione N2 o N2 + 1.
Esempio
Determinare se il numero 31 sia presente nella sequenza:
1, 6, 8, 10, 18, 25, 32, 34, 37, 52, 60.
Di seguito si mostra la sequenza considerata in ogni passo
dell’algoritmo, e, in rosso, l’elemento centrale:
I
primo passo: 1, 6, 8, 10, 18, 25, 32, 34, 37, 52, 60
25 < 31: si prosegue con la sottosequenza a destra
I
secondo passo: 32, 34, 37, 52, 60
37 > 31: si prosegue con la sottosequenza a sinistra
I
terzo passo: 32, 34
32 > 31: si prosegue con la sottosequenza a sinistra
I
quarto passo: la sottosequenza è vuota
Risultato: no (31 non è presente nella sequenza considerata).
Esempio
Determinare se il numero 8 sia presente nella sequenza:
1, 6, 8, 10, 18, 25, 32, 34, 37, 52, 60
I
primo passo: 1, 6, 8, 10, 18, 25, 32, 34, 37, 52, 60
25 > 8: si prosegue con la sottosequenza a sinistra
I
secondo passo: 1, 6, 8, 10, 18
8 = 8: l’algoritmo termina e il risultato è sì (8 è presente
nella sequenza considerata)
Algoritmo di ricerca binaria: efficienza
Il numero maggiore di confronti (caso peggiore) si ha quando
l’oggetto da cercare non è presente nella sequenza considerata,
oppure quando si trova nell’ultima sottosequenza considerata, e
questa risulta composta da un solo elemento.
Il corrispondente numero di confronti può essere approssimato
tenendo conto che:
I
dopo ogni confronto la lunghezza della successiva
sottosequenza si riduce (circa) della metà
I
l’ultima sottosequenza sarà sempre composta da un solo
elemento
Algoritmo di ricerca binaria: efficienza
Per una sequenza iniziale di N elementi, il numero di confronti nel
caso peggiore (indicato con k), può essere approssimato come
segue (assumendo per semplicità che anche una sequenza di
lunghezza dispari si riduca esattamente della metà):
numero di confronti
1
2
3
...
k
lunghezza della sequenza
N
= N/20
N/2 = N/21
N/4 = N/22
...
...
1
= N/2k−1
Da cui si ottiene facilmente (trascurando il fatto che k deve essere
un intero):
k = log2 N + 1
.
Confronto tra ricerca sequenziale e binaria
Il numero di confronti richiesto nel caso peggiore, per sequenze
ordinate di N elementi, è pari a
I
N, per la ricerca sequenziale
I
≈ log2 N + 1, per la ricerca binaria
Poiché N ≥ log2 N + 1 per qualsiasi valore intero di N, la ricerca
binaria è più efficiente di quella sequenziale. Il vantaggio diventa
evidente al crescere di N, come mostra il seguente esempio (nel
quale si approssima log2 N + 1 a un intero per troncamento):
N
10
100
1.000
1.000.000
109
...
numero di confronti (caso peggiore)
ricerca sequenziale: N ricerca binaria: log2 N
10
100
1.000
1.000.000
109
...
+1
4
7
10
20
30
...
Algoritmi di ordinamento
Problema: ordinare secondo un certo criterio una data sequenza di
oggetti.
Dati di partenza: una sequenza di oggetti, e un criterio di
ordinamento.
Risultato desiderato: gli stessi elementi della sequenza iniziale,
ordinati secondo il criterio richiesto.
Esistono diversi algoritmi di ordinamento che si differenziano per la
loro efficienza. Di seguito si descrivono tre algoritmi (i più semplici
da formulare, ma anche i meno efficienti):
I
ordinamento per selezione (selection sort)
I
ordinamento “a bolle” (bubble sort)
I
ordinamento per inserimento (insertion sort)
Ordinamento per selezione
Idea di base: disporre l’elemento più piccolo nella prima posizione
della sequenza, quello immediatamente successivo nella seconda
posizione, e così via.
L’algoritmo di ordinamento per selezione ottiene questo risultato
scandendo le posizioni della sequenza dalla prima alla penultima;
per ognuna di tali posizioni:
1. cercare la posizione dell’elemento più piccolo a partire da
quello nella posizione considerata (si può usare un algoritmo
analogo a quello per la ricerca del massimo)
2. se la posizione di tale elemento non coincide con la posizione
considerata, si scambiano gli elementi in tali posizioni
Una variante dell’algoritmo consiste nel disporre nell’ultima
posizione l’elemento più grande, nella penultima posizione quello
immediatamente precedente, e così via.
Esempio
Ordinare in senso crescente i seguenti numeri, con l’algoritmo di
ordinamento per selezione: 52, −34, −48, 58, 75, −80, 22.
In rosso si mostra la posizione considerata in ogni passo dell’algoritmo, in
grassetto l’elemento più piccolo da tale posizione in avanti; se i due
elementi coincidono, non si esegue nessuno scambio.
I
52, −34, −48, 58, 75, −80, 22: si scambiano 52 e −80
I
−80, −34, −48, 58, 75, 52, 22: si scambiano −34 e −48
I
−80, −48, −34, 58, 75, 52, 22: nessuno scambio
I
−80, −48, −34, 58, 75, 52, 22: si scambiano 58 e 22
I
−80, −48, −34, 22, 75, 52, 58: si scambiano 75 e 52
I
−80, −48, −34, 22, 52, 75, 58: si scambiano 75 e 58
I
−80, −48, −34, 22, 52, 58, 75
Risultato: −80, −48, −34, 22, 52, 58, 75.
Ordinamento “a bolle”
L’idea di base è la stessa dell’ordinamento per selezione: disporre
in prima posizione l’elemento più piccolo, in seconda posizione
quello immediatamente successivo, e così via (oppure: disporre
nell’ultima posizione l’elemento più grande, nella penultima
posizione quello immediatamente precedente, e così via).
L’algoritmo di ordinamento “a bolle” ottiene questo risultato non
ricercando in ogni passo il corrispondente elemento più piccolo, ma
attraverso un’opportuna serie di scambi tra coppie di elementi.
Ordinamento “a bolle”
Detta N la lunghezza della sequenza, si considerano in successione:
I
la sequenza iniziale
I
la sottosequenza nelle ultime N − 1 posizioni
I
la sottosequenza nelle ultime N − 2 posizioni
I
...
I
la sottosequenza nelle ultime due posizioni
Per ciascuna sottosequenza, detta M la sua lunghezza, l’elemento
più piccolo viene portato in prima posizione confrontando in
successione:
I
gli elementi in posizione M − 1 e M
I
gli elementi in posizione M − 2 e M − 1
I
...
I
gli elementi nelle prime due posizioni
e scambiando dopo ogni confronto i due elementi considerati, se
non si trovano nell’ordine desiderato.
Esempio
Ordinare in senso crescente i seguenti N = 7 numeri, con l’algoritmo di
ordinamento “a bolle”: 52, −34, −48, 58, 75, −80, 22.
In rosso si mostra la sottosequenza considerata in ogni passo dell’algoritmo, in grassetto i due elementi confrontati; se i due elementi non si
trovano nell’ordine desiderato, vengono scambiati.
I
primo passo: si considera l’intera sequenza di N elementi
52, −34, −48, 58, 75, −80, 22: nessuno scambio
52, −34, −48, 58, 75, −80, 22: scambio
52, −34, −48, 58, −80, 75, 22: scambio
52, −34, −48, −80, 58, 75, 22: scambio
52, −34, −80, −48, 58, 75, 22: scambio
52, −80, −34, −48, 58, 75, 22: scambio
−80, 52, −34, −48, 58, 75, 22
(cont.)
Esempio
(cont.)
I
secondo passo: si considerano gli ultimi N − 1 elementi
−80, 52, −34, −48, 58, 75, 22: scambio
−80, 52, −34, −48, 58, 22, 75: scambio
−80, 52, −34, −48, 22, 58, 75: nessuno scambio
−80, 52, −34, −48, 22, 58, 75: scambio
−80, 52, −48, −34, 22, 58, 75: scambio
−80, −48, 52, −34, 22, 58, 75
I
terzo
−80,
−80,
−80,
−80,
−80,
(cont.)
passo: si considerano gli ultimi N − 2 elementi
−48, 52, −34, 22, 58, 75: nessuno scambio
−48, 52, −34, 22, 58, 75: nessuno scambio
−48, 52, −34, 22, 58, 75: nessuno scambio
−48, 52, −34, 22, 58, 75: scambio
−48, −34, 52, 22, 58, 75
Esempio
(cont.)
I
quarto passo: si considerano gli ultimi N − 3 elementi
−80, −48, −34, 52, 22, 58, 75: nessuno scambio
−80, −48, −34, 52, 22, 58, 75: nessuno scambio
−80, −48, −34, 52, 22, 58, 75: scambio
−80, −48, −34, 22, 52, 58, 75
I
quinto passo: si considerano gli ultimi N − 4 elementi
−80, −48, −34, 22, 52, 58, 75: nessuno scambio
−80, −48, −34, 22, 52, 58, 75: nessuno scambio
−80, −48, −34, 22, 52, 58, 75
I
sesto passo: si considerano gli ultimi N − 5 (due) elementi
−80, −48, −34, 22, 52, 58, 75: nessuno scambio
Risultato: −80, −48, −34, 22, 52, 58, 75.
Ordinamento per inserimento
L’idea è la seguente:
I
si assuma che gli elementi nelle prime M posizioni (per un
qualunque M ∈ {1, 2, . . . , N − 1}) formino una sottosequenza già
ordinata (anche se questa non fosse la loro posizione corretta
nell’intera sequenza ordinata)
I
si può allora inserire nella posizione corretta di tale sottosequenza
l’elemento in posizione M + 1 (indicato con x ), attraverso una serie
di confronti con gli elementi in posizione M, M − 1, M − 2, . . . :
– se x è minore o uguale all’elemento con cui viene confrontato,
quest’ultimo deve essere spostato nella posizione successiva
della sequenza, e si procede poi con un altro confronto
– se x è maggiore dell’elemento con cui viene confrontato, x
deve essere inserito nella posizione successiva della sequenza,
e i confronti terminano
I
si procede quindi nello stesso modo con gli elementi in posizione
M + 2, M + 3, . . . , N
Ordinamento per inserimento
Per rendere tale algoritmo applicabile a qualsiasi sequenza, si può
sempre considerare come sottosequenza ordinata iniziale quella
composta dal solo primo elemento (M = 1).
Anche in questo caso si può considerare una variante che consiste
nell’inserire l’elemento in posizione N − 1 nella sottosequenza
composta dal solo ultimo elemento; poi l’elemento in posizione
N − 2 nella sottosequenza (ora ordinata) degli ultimi due elementi,
e così via.
Esempio
Ordinare in senso crescente i seguenti numeri, con l’algoritmo di
ordinamento per inserimento: 52, −34, −48, 58, 75, −80, 22.
Per ogni passo, in rosso si mostra la sottosequenza già ordinata, e
in grassetto l’elemento da inserire in tale sottosequenza:
I
52, −34, −48, 58, 75, −80, 22
l’elemento 52 viene spostato di una posizione verso destra, e
−34 viene inserito nella prima posizione
I
−34, 52, −48, 58, 75, −80, 22
gli elementi −34 e 52 vengono spostati di una posizione verso
destra, −48 viene inserito nella prima posizione
I
−48, −34, 52, 58, 75, −80, 22
nessuno spostamento
I
−48, −34, 52, 58, 75, −80, 22
nessuno spostamento
(cont.)
Esempio
(cont.)
I
−48, −34, 52, 58, 75, −80, 22
gli elementi −48, . . . , 75 vengono spostati di una posizione
verso destra, −80 viene inserito nella prima posizione
I
−80, −48, −34, 52, 58, 75, 22
gli elementi 52, 58, 75 vengono spostati di una posizione
verso destra, 22 viene inserito nella quarta posizione
I
−80, −48, −34, 22, 52, 58, 75
Risultato: −80, −48, −34, 22, 52, 58, 75.
Algoritmi di ordinamento: efficienza
La complessità temporale può essere valutata in termini del
numero di confronti tra coppie di elementi; dopo ogni confronto,
nel caso peggiore ci sarà anche uno scambio.
Non è difficile rendersi conto che per l’ordinamento per selezione e
l’ordinamento “a bolle” il numero di confronti non dipende dalla
sequenza di partenza.
Nel caso dell’ordinamento per inserimento, il numero di confronti
dipende invece dalla particolare sequenza di partenza, e il caso
peggiore è quello in cui tale sequenza sia ordinata nel senso
opposto a quello desiderato.
Algoritmi di ordinamento: efficienza
Esempio: ordinamento per selezione di una sequenza di N elementi
I
passo 1: per trovare l’elemento più piccolo dell’intera
sequenza sono necessari N − 1 confronti (assumendo
inizialmente che il più piccolo sia il primo)
I
passo 2: N − 2 confronti
I
...
I
passo N − 1: un confronto
Numero totale di confronti:
(N − 1) + (N − 2) + . . . + 1 =
N−1
X
k=1
k
=
N(N − 1)
2
Con ragionamenti analoghi si può facilmente ricavare che
l’ordinamento per inserimento (nel caso peggiore) e l’ordinamento
“a bolle” richiedono lo stesso numero di confronti.
Algoritmi di ordinamento: efficienza
Per una sequenza di N elementi, il numero di operazioni richieste
nel caso peggiore dai tre algoritmi di ordinamento considerati è
quindi proporzionale a N 2 .
Esistono altri algoritmi di ordinamento più complessi, alcuni dei
quali hanno un’efficienza maggiore:
I
Quick Sort (numero di confronti proporzionale a N 2 )
I
Merge Sort (numero di confronti proporzionale a N log2 N)
I
Heap Sort (numero di confronti proporzionale a N log2 N)