Algoritmi
Fondamentali
Nicola Di Mauro
[email protected]
2
Ordinamento

Disporre un gruppo di elementi secondo una prefissata
relazione d’ordine

Dipendente dal tipo di informazione



2 possibilità




Crescente
Decrescente
Altri criteri
Una delle attività di elaborazione più importanti


Numerica (ordinamento numerico)
Alfanumerica (ordinamento lessicografico)
Stima: 30% del tempo di calcolo di un elaboratore
Obiettivo: efficienza

Sfruttare “al meglio” i confronti ed i conseguenti spostamenti
degli elementi

Piazzare gli elementi prima possibile più vicino alla loro posizione
finale nella sequenza ordinata
3
Ordinamento
 Possibilità di avere elementi costituiti da
più componenti
 Associazione a ciascuno di una chiave



Identifica univocamente
Stabilisce la sua posizione
Unica componente rilevante per l’ordinamento
 esempio: record / oggetti
 Supponiamo, nel seguito, di richiedere un
ordinamento numerico crescente
 elaborazione sulla sola chiave
4
Ordinamento
 Gran varietà di algoritmi
 Basati su confronti e scambi fra gli elementi

Relazione d’ordine, criterio
 Non esiste uno migliore in assoluto

La bontà dipende da fattori connessi ai dati su cui
deve essere applicato
 Dimensione dell’insieme di dati


Numerosità
Grado di pre-ordinamento dell’insieme di dati

Già ordinato, parzialmente, ordine opposto, casuale
5
Ordinamento
 Algoritmi esterni

Usano un array di appoggio


Occupazione di memoria doppia
Necessità di copiare il risultato nell’array originale
 Algoritmi interni

Eseguono l’ordinamento lavorando sullo
stesso array da ordinare

Basati su scambi di posizione degli elementi
6
Ordinamento Esterno
 Enumerativo

Ciascun elemento confrontato con tutti gli altri
per determinare il numero degli elementi
dell’insieme che sono più piccoli in modo da
stabilire la sua posizione finale
 Non verranno trattati nel seguito
7
Ordinamento Interno

Per Selezione


A bolle


Elemento più piccolo localizzato e separato dagli altri,
quindi selezione del successivo elemento più piccolo,
e così via
Coppie di elementi adiacenti fuori ordine scambiate,
finché non è più necessario effettuare alcuno scambio
Per Inserzione

Elementi considerati uno alla volta e inseriti al posto
che gli compete all’interno degli altri già ordinati
Ordinamento per Selezione
8
 Minimi successivi

Trovare il più piccolo elemento dell’insieme e
porlo in prima posizione




Scambio con l’elemento in prima posizione
Trovare il più piccolo dei rimanenti (n – 1)
elementi e sistemarlo in seconda posizione
…
Finché si trovi e collochi il penultimo elemento

Ultimo sistemato automaticamente
Ordinamento per 9
Selezione: Esempio
Inizio
I
II
III
IV
V
array(1)
44
44
11
11
11
11
array(2)
33
33
33
22
22
22
array(3)
66
66
66
66
33
33
array(4)
11
11
44
44
44
44
array(5)
55
55
55
55
55
55
array(6)
22
22
22
33
66
66
Ordinamento per 10
Selezione: Algoritmo
i←0
fintantoché i < n-1 esegui
trova il minimo di lista (i … n-1)
scambia la posizione del minimo con lista(i)
i←i+1
 Algoritmi già noti
 Ricerca del minimo
 Scambio
Ordinamento per 11
Selezione: in Java
...
for (int i = 0; i<n-1; i++) {
min = a[i];
p = i;
for (int j = i+1; j<n; j++)
if (a[j] < min) {
min = a[j];
p = j;
}
a[p] = a[i];
a[i] = min;
}
...
Ordinamento per 12
Selezione: Complessità
 Confronti
 Sempre (n – 1) * n / 2 ~ O(n2)




n – 1 al I passo di scansione
n – 2 al II passo di scansione
…
1 all’(n – 1)-mo passo di scansione
 Scambi
 Al più (n – 1)

1 per ogni passo
Ordinamento per 13
Selezione: Considerazioni
 Ogni ciclo scorre tutta la parte non
ordinata
 Numero fisso di confronti

Non trae vantaggio dall’eventuale
preordinamento
 Pochi scambi
Ordinamento a Bolle

Far “affiorare” ad ogni passo l’elemento più
piccolo fra quelli in esame



Confronto fra coppie di elementi adiacenti e,
se sono fuori ordine, scambio,
ripetendo il tutto fino ad ottenere la sequenza
ordinata
Simile alle bolle di gas in un bicchiere
Il passo di esecuzione coincide con:


Il numero di elementi già ordinati
Elemento a cui fermarsi ad ogni passo
14
Ordinamento a Bolle

Se in una passata non viene effettuato nessuno
scambio, l’insieme è già ordinato

L’algoritmo può già terminare


15
Meno di n – 1 passi
Miglioramento:

Usare un indicatore di scambi effettuati



Impostato a vero all’inizio di ogni passata
Impostato a falso non appena si effettua uno scambio
Si termina se alla fine di un passo è rimasto inalterato
Ordinamento a Bolle 16
Esempio
Inizio
I/1
I/2
I/3
I/4
II
array(1)
4
4
4
4
4
1
array(2)
3
3
3
3
1
4
array(3)
6
6
6
1
3
3
array(4)
1
1
1
6
6
6
array(5)
5
2
2
2
2
2
array(6)
2
5
5
5
5
5
Ordinamento a Bolle 17
Esempio
II
II/1
II/2
II/3
III
array(1)
1
1
1
1
1
array(2)
4
4
4
4
2
array(3)
3
3
3
2
4
array(4)
6
6
2
3
3
array(5)
2
2
6
6
6
array(6)
5
5
5
5
5
Ordinamento a Bolle 18
Esempio
III
III/1
III/2
IV
IV/1
Fine
array(1)
1
1
1
1
1
1
array(2)
2
2
2
2
2
2
array(3)
4
4
4
3
3
3
array(4)
3
3
3
4
4
4
array(5)
6
5
5
5
5
5
array(6)
5
6
6
6
6
6
Ordinamento a Bolle 19
Algoritmo
passo ← 0
(* passo di esecuzione *)
ordinato ← falso
fintantoché passo < n e non ordinato esegui
passo ← passo + 1
ordinato ← vero
i←n
fintantoché i > passo esegui
se vettore(i) < vettore(i – 1) allora
scambia vettore(i) con vettore(i– 1)
ordinato ← falso
i←i–1
Ordinamento a Bolle 20
Codifica
. . .
p = 0;
ordinato = false;
while ((p < n) && !ordinato) {
p++;
ordinato = true;
for (int i = n-1; i >= p; i--)
if (a[i] < a[i-1]) {
t = a[i];
a[i] = a[i-1];
a[i-1] = t;
ordinato = false;
}
}
. . .
Ordinamento a Bolle 21
Complessità

Caso migliore (lista già ordinata): 1 passo


n – 1 confronti, 0 scambi
Caso peggiore (ordine opposto): n – 1 passi

All’i-mo passo



n – i + 1 confronti → in tutto (n – 1) * n / 2 ~ O(n2)
 Come per Selezione
n – i + 1 scambi → in tutto (n – 1) * n / 2 ~ O(n2)
 Molto maggiore della Selezione
Caso medio

Scambi pari alla metà dei confronti → O(n2)
Ordinamento a Bolle 22
Considerazioni
 Ogni ciclo scorre tutta la parte non
ordinata
 Prestazioni medie inferiori agli altri
metodi


Nel caso peggiore, numero di confronti uguale
all’ordinamento per selezione, ma numero di
scambi molto maggiore
Molto veloce per insiemi con alto grado di
preordinamento
Ordinamento per 23
Inserzione
 Ricerca la giusta posizione d’ordine di ogni
elemento rispetto alla parte già ordinata
 Inizialmente già ordinato solo il primo
elemento
 Elementi da ordinare considerati uno per
volta

Necessari n – 1 passi
 Metodo intuitivo
 Simile all’ordinamento eseguito sulle carte da
gioco
Ordinamento per 24
Inserzione

Determinare la posizione in cui inserire la chiave
nella sequenza ordinata, facendo scalare le altre


Scansione sequenziale
Soluzione completa costruita inserendo un
elemento della parte non ordinata nella parte
ordinata, estendendola di un elemento
parte ordinata
≤x
|x|
| x | parte non ordinata
>x
parte non ordinata
→
Ordinamento per 25
Inserzione: Note
 Strategia di scelta del prossimo elemento
da inserire nella parte ordinata

Primo elemento della parte non ordinata
 Variante [da Dromey]

Inserire subito il più piccolo in prima
posizione

Evita di dover effettuare appositi controlli
sull’indice per evitare che esca fuori dall’array
Ordinamento per 26
Inserzione: Esempio
Inizio
I
II
III
IV
V
array(1)
40
30
30
10
10
10
array(2)
30
40
40
30
30
20
array(3)
60
60
60
40
40
30
array(4)
10
10
10
60
50
40
array(5)
50
50
50
50
60
50
array(6)
20
20
20
20
20
60
Ordinamento per 27
Inserzione: Algoritmo
per ogni elemento dal secondo fino all’ultimo esegui
inserito ← falso
fintantoché non è stato inserito esegui
se è minore del precedente allora
fai scalare il precedente
se sei arrivato in prima posizione allora
piazzalo; inserito ← vero
fine-se (necessario per evitare ambiguità)
altrimenti
piazzalo; inserito ← vero
fine-se
fine-fintantoché
fine-perogni
Ordinamento per 28
Inserzione: Codifica Java
for (int i=1; i <= n-1; ++i) {
x = a[i];
j = i - 1;
inserito = false;
while (!inserito)
if (x < a[j]) {
a[j+1] = a[j];
j--;
if (j < 0) {
a[0] = x;
inserito = true;
}
} else {
a[j + 1] = x;
inserito = true;
}
}
Ordinamento per 29
Inserzione: Algoritmo
Cerca il minimo
Prima posizione ← minimo
Fintantoché c’è una parte non ordinata
Considera il primo elemento di tale parte
Confrontalo a ritroso con i precedenti, facendoli via via
scalare finché sono maggiori
 Gli elementi via via incontrati scandendo a
ritroso la parte ordinata scalano per far
spazio all’elemento da inserire
Ordinamento per 30
Inserzione: in Java
. . .
min = a[0];
p = 0;
for (int i=1; i <= n-1; ++i)
if (a[i] < min) {
min = a[i];
p = i;
}
a[p] = a[0];
a[0] = min; // a[0] e a[1] ordinati
for (int i=2; i <= n-1; ++i) {
x = a[i];
j = i;
while (x < a[j-1]) { a[j] = a[j-1]; j--; }
a[j] = x;
}
. . .
Ordinamento per 31
Inserzione: Complessità
 Sempre n – 1 passi
 Uno scambio per ogni confronto, salvo
(eventualmente) l’ultimo
 Caso ottimo (lista già ordinata)

n – 1 confronti, 0 scambi
 Come il metodo a bolle
 Caso pessimo (ordine opposto)

i-mo passo
 i – 1 confronti e scambi -> (n – 1) * n / 2 ~
O(n2)
 Caso medio: metà confronti e scambi
Ordinamento per 32
Inserzione: Considerazioni

Per sequenze con distribuzione casuale



Molti confronti
Molti scambi
Caso migliore come l’ordinamento a bolle

Valido per



Piccole sequenze (n ≤ 25)
Sequenze note a priori essere parzialmente ordinate
Ogni ciclo scorre una porzione della parte
ordinata
Ordinamento 33
Considerazioni
 Scambio più costoso del confronto


Confronto operazione base del processore
Scambio composto da tre assegnamenti

Un assegnamento richiede due accessi alla memoria
 Ad ogni passo


La porzione ordinata cresce di una unità
La porzione disordinata decresce di una unità
Ordinamento 34
Tecniche Avanzate

Si può dimostrare che, in media, per dati casuali,
gli elementi devono essere spostati di una
distanza pari a n/3 rispetto alla loro posizione
originaria

Gli algoritmi semplici tendono a spostare solo gli
elementi vicini e quindi necessitano di molti
spostamenti in più per ordinare la lista


Meno efficienza
Algoritmi migliori in grado di scambiare, nelle prime
fasi di ordinamento, valori che occupano posizioni
molto distanti nella lista
Ordinamento di Shell 35
(a diminuzione di incremento)
 Si può ricavare vantaggio dai confronti
iniziali piazzando gli elementi più vicini
alla loro posizione finale

Muovere, inizialmente, elementi su lunghe
distanze


Sequenza finale ordinata raggiunta più velocemente
Ridurre progressivamente le distanze

Raffinamento dell’ordine degli elementi
36
Ordinamento di Shell
 Scelta della distanza
 Distanza massima possibile su n elementi: n/2

Possibilità che un elemento resti disaccoppiato
 Primo con l’(n/2 + 1)-mo
 Secondo con l’(n/2 + 2)-mo
 …
 Scelta della riduzione di distanza
 Dimezzamento progressivo

Numero logaritmico di riduzioni
 n/2; (n/2)/2 = n/4; n/8; n/16; …; n/2log2n
Ordinamento di Shell 37
Esempio

n=8
| 20 | 35 | 18 | 8 | 14 | 41 | 3 | 39 |
Distanza n/2 = 4
4 catene da 2 elementi
| 14 | 35 | 3 | 8 | 20 | 41 | 18 | 39 |
Distanza n/4 = 2
2 catene da 4 elementi
| 3 | 8 | 14 | 35 | 18 | 39 | 20 | 41 |
Distanza n/8 = 1
1 catena da 8 elementi
| 3 | 8 | 14 | 18 | 20 | 35 | 39 | 41 |
38
Ordinamento di Shell
 Scelta di un algoritmo per ordinare le
catene
 Disordine nella lista relativo

Algoritmo efficiente per strutture parzialmente
ordinate
 Catene corte

Algoritmo efficiente per piccoli insiemi di valori
 Metodi candidati
 A bolle
 Per inserzione

Meno scambi
39
Ordinamento di Shell
 Realizzazione del meccanismo per
diminuire le distanze
inc ← n
fintantoché inc > 1
inc ← inc / 2
Ordina le catene con incremento inc per inserzione
40
Ordinamento di Shell
 Numero di catene da ordinare per ogni
definito incremento
 Pari all’incremento
inc ← n
fintantoché inc > 1
inc ← inc / 2
per j che varia da 1 a inc
Ordina la j-ma catena con incremento inc per inserzione
41
Ordinamento di Shell

Accesso ad ogni singola catena per applicare
l’ordinamento

Nell’ordinamento per inserzione si inizia
considerando il secondo elemento



Elementi successivi a distanza inc (invece che 1)


Posizione del primo elemento di ogni catena: j
Posizione del secondo elemento di ogni catena: j + inc
posizione ← posizione precedente + inc
Fino a quando la posizione non supera la dimensione
dell’array (n)

k>n
42
Ordinamento di Shell
inc ← n
fintantoché inc > 1
inc ← inc / 2
per j che varia da 1 a inc
k ← j + inc
fintantoché k ≤ n
x ← a(k)
Trova la posizione corrente per x
a(corrente) ← x
k ← k + inc
43
Ordinamento di Shell

Individuazione della posizione corretta per
inserire l’elemento x via via considerato

Inizialmente x è in posizione k nella lista


Il primo elemento da confrontare con x è nella
posizione precedente (distanza inc invece che 1)


corrente ← k
precedente ← corrente – incremento
Scorrere in seguito gli altri elementi nella catena


corrente ← precedente
precedente ← precedente – incremento
fintantoché x < a(precedente) esegui …
44
Ordinamento di Shell
 Necessità di un metodo per garantire la
terminazione
 Nell’ordinamento per inserzione esisteva una
sentinella

Minimo in prima posizione
 Metodo basato solo sugli indici

Se precedente è ripetutamente decrementato di inc
potrà finire sotto j che segna l’inizio della catena
corrente
 Accertarsi che precedente ≥ j
45
Ordinamento di Shell
fintantoché (precedente ≥ j) e (x < a(precedente)) esegui
…

Problemi con l’implementazione

Valutazione della condizione di ciclo non
cortocircuitata


Non si ferma appena è trovata falsa la prima condizione
Necessità di definire un indicatore che ricordi se
x < a(precedente) è vero o falso
while (precedente ≥ j) and (not inserito) do …
Ordinamento di Shell 46
Algoritmo
Definire il vettore di n elementi
Inizializzare l’incremento inc a n
Fintantoché l’incremento è maggiore di 1
Dimezza l’incremento
Per ciascuna delle inc catene a intervalli inc da ordinare
Individua il secondo elemento della catena
Fintantoché non è raggiunta la fine della catena
Usa il meccanismo di inserzione per l’elemento corrente
Passa all’elemento successivo a distanza inc
Ordinamento di Shell 47
Programma Java
inc = n;
while (inc > 1) {
inc = inc / 2;
for (int j = 0 ; j < inc; ++j) {
k = j + inc; // indice successivo nella catena
while (k < n) {
inserito = false;
x = a[k];
corr = k; prec = corr-inc;
while ((prec >= j) && !inserito ) {
if (x < a[prec]) {
a[corr] = a[prec];
corr = prec; prec = prec-inc;
}
else inserito = true;
} // while ((prec >= j) && !inserito )
a[corr] = x;
k = k + inc;
} // while (k < n)
} // for
} // while (inc > 1)
48
Ordinamento di Shell
| 20 | 35 | 18 |


Risultato finale:
Penultimo passo:



n=7
3 |
| 3 | 8 | 14 | 18 | 20 | 35 | 41 |
| 8 | 14 | 18 | 20 | 35 | 41 | 3 |
Per arrivarci con Shell:

8 | 14 | 41 |
3 confronti, 2 scambi
1 solo passo: n/2 = 3 catene di lunghezza 2
Al passo successivo: n/4 = 1 catena pari a tutto l’array
Per arrivarci per inserzione: 11 confronti, 8 scambi

| 20 | 35 | 18 | 8 | 14 | 41 | 3 | → 1 confronto,
0 scambi

| 20 | 35 | 18 | 8 | 14 | 41 | 3 | → 2 confronti,
2 scambi

| 18 | 20 | 35 | 8 | 14 | 41 | 3 | → 3 confronti,
3 scambi

| 8 | 18 | 20 | 35 | 14 | 41 | 3 | → 4 confronti,
3 scambi

| 8 | 14 | 18 | 20 | 35 | 41 | 3 | → 1 confronto,
0 scambi
Ordinamento di Shell 49
Considerazioni

Numero di passi esterni logaritmico


L’ultimo passo coincide con il normale
ordinamento per inserzione/selezione


Numero di volte che si può continuare a dimezzare n
Vantaggio del preordinamento risultante superiore
alla perdita per le ripetizioni dell’ordinamento
Scelte migliori per la sequenza di decrementi

2p–1, …, 31, 15, 7, 3, 1

Numero di confronti e scambi proporzionale a n1.2
50
Partizionamento
 Dato un array non ordinato di n elementi,
e un valore x dello stesso tipo degli
elementi, partizionare gli elementi in due
sottoinsiemi tali che
 Gli elementi ≤ x siano in un sottoinsieme
 Gli elementi > x siano nell’altro sottoinsieme
 Esempio
| 4 | 13 | 28 | 18 | 7 | 36 | 11 | 24 |
x = 20
51
Partizionamento

Si potrebbe ordinare l’array e trovare l’indice
dell’elemento che separa i due sottoinsiemi



| 4 | 7 | 11 | 13 | 18 | 24 | 28 | 36 |
≤ 20
> 20
Non richiesto
Non necessario
Il problema sarebbe risolto anche con la
seguente configurazione finale
| 4 | 13 | 11 | 18 | 7 | 36 | 28 | 24 |
≤ 20
> 20
52
Partizionamento

Nota la posizione finale p che divide i due
sottoinsiemi

Alcuni elementi non vanno spostati



Alcuni elementi sono fuori posto


Scorrendo l’array da sinistra verso p, i valori ≤ x
Scorrendo l’array da destra verso p, i valori > x
Per ognuno da una parte, uno dall’altra
 Scambio
La posizione p può essere ricavata

Contare gli elementi ≤ x
53
Partizionamento

Occorrono 2 scansioni dell’array


Determinazione della posizione p
Individuazione e scambio degli elementi fuori posto


Si termina quando si arriva a p
È possibile scandire l’array una sola volta
Mentre le due partizioni non si incontrano
Estendere le partizioni sinistra e destra scambiando le coppie
piazzate male

La posizione p sarà individuata automaticamente al
termine
54
Partizionamento
 Scansione effettuata tramite 2 indici

i per la partizione sinistra



Parte da 1
Incrementato
j per la partizione destra


Parte da n
Decrementato
Partizionamento 55
Algoritmo

Individuazione dei primi valori fuori posto

Mentre l’i-mo elemento è ≤ x e i < j


Mentre il j-mo elemento è > x e i < j


Decrementare j
Mentre le partizioni non si sono incontrate



Incrementare i
Scambiare i valori nelle posizioni individuate
Cercare i successivi valori fuori posto
Al termine, j è la posizione p

Limite inferiore per i valori > x nella partizione
Partizionamento 56
Note Implementative
 Gestione del caso in cui x è fuori
dall’intervallo dei valori assunti dall’array
 Inserire un controllo supplementare dopo i
cicli preliminari

se il j-mo elemento è > x allora decrementa j
Ciò assicura che ∀k ∈ [1..j]: a[k] ≤ x
 Se x è maggiore di tutti gli elementi dell’array,
al termine dei cicli si ha i = j = n e il controllo
non scatta
 Se x è minore di tutti gli elementi dell’array,
al termine dei cicli si ha i = j = 1 e, dopo il
controllo, j = 0
Partizionamento 57
Codice Java
int partiziona(Tipo[] a, int p1, int p2, Tipo x)
i = p1; j = p2;
while ((i < j) && (a[i] <= x)) i++;
while ((i < j) && (a[j] > x)) j--;
if (a[j] > x) j--;
while (i < j) {
t = a[i];
a[i] = a[j];
a[j] = t;
i++; j--;
while (a[i] <= x) i++;
while (a[j] > x) j--;
}
return j;
}
58
Fusione
 Fondere due array, già ordinati secondo il
medesimo criterio, in un unico array
ordinato secondo lo stesso criterio
2 | 5 | 9 | 13 | 24
3 | 4 | 11 | 15 | 22
2 | 3 | 4 | 5 | 9 | 11 | 13 | 15 | 22 | 24
 Numero di elementi dell’array risultante pari
alla somma degli elementi dei due array dati
 Necessità di esaminare tutti gli elementi dei
due array dati
59
Fusione
 I due array possono considerarsi come
costituiti da sottosequenze da concatenare
opportunamente nell’array finale
 Suddividere ciascuno dei due array dati in una
parte già fusa e una ancora da fondere

In ogni istante, esaminare il primo elemento della
parte da fondere dei due array
 Inserire il minore nella prima posizione
dell’array fuso
 Avanzare al prossimo elemento dell’array da cui
è stato preso
60
Fusione
 Uno dei due array finirà per primo


Copiare fino ad esaurimento i suoi elementi
restanti nell’array finale
Individuabile dall’inizio confrontando gli
ultimi elementi dei due array

Se l’ultimo elemento di un array è minore del primo
elemento dell’altro, la fusione si riduce alla
copiatura degli elementi dell’uno seguita dalla
copiatura degli elementi dell’altro
Fusione 61
Algoritmo
Mentre non è terminato il numero di elementi da
considerare in nessuno degli array:
Confronta i loro primi elementi degli array da fondere e
metti il più piccolo nell’array finale
Aggiorna l’indice dell’array appropriato
Se è finito il primo array, allora
Copia il resto del secondo nell’array finale
Altrimenti
Copia il resto del primo nell’array finale
Fusione 62
Modularizzazione

algoritmo merge


algoritmo mergecopy


Fonde l’array che finisce prima con l’altro
algoritmo shortmerge


Decide quale array finisce prima, quindi esegue la
fusione di conseguenza
Fonde le parti degli array ricadenti nello stesso
intervallo
algoritmo copy

Copia gli elementi di un array dalla posizione attuale
fino alla fine
Fusione 63
Algoritmo merge
Definire gli array a[1..m] e b[1..n]
 Se l’ultimo elemento di a è ≤ all’ultimo elemento
di b allora




Altrimenti



Fondi tutto a con b
Copia il resto di b
Fondi tutto b con a
Copia il resto di a
Dà il risultato c[1..n+m]
Fusione 64
Algoritmo mergecopy

Definire gli array a[1..m] e b[1..n]
con a[m] ≤ b[n]

Se l’ultimo elemento di a risulta ≤ rispetto al
primo elemento di b allora



Copia tutto a nei primi m elementi di c
Copia tutto b in c, partendo dalla posizione m+1
Altrimenti


Fondi tutto a con b in c
Copia il resto di b in c partendo dalla posizione in cui
è finita la fusione
Fusione 65
Algoritmo shortmerge
Definire gli array a[1..m] e b[1..n]
con a[m] ≤ b[n]
 Mentre tutto a non è ancora fuso esegui


Se il corrente a ≤ b allora



Altrimenti



Copia il corrente a nella corrente posizione di c
Avanza l’indice di a
Copia il corrente b nella corrente posizione di c
Avanza l’indice di b
Avanza l’indice di c
Fusione 66
Algoritmo copy
 Definire gli array b[1..n] e c[1..n+m] e
definire dove cominciare
in b (al corrente j) e
in c (al corrente k)
 Mentre non è ancora finito b esegui



Copia l’elemento dalla corrente posizione in b
nella corrente posizione in c
Avanza l’indice j di b
Avanza l’indice k di c
67
Ordinamento per Fusione

Algoritmo di MergeSort

L’idea alla base è che l’ordinamento di una lista di n
elementi può essere ottenuto


Dividendo la lista in due sequenze di n/2 elementi ciascuna
 Dimensione inferiore (dimezzata)
Ordinando singolarmente ogni sequenza
 Problema di ordine inferiore
 Risolubile secondo la stessa tecnica


Procedura ricorsiva
Fondendo le due metà ordinate in un’unica sequenza
 Lista risultante ordinata
Ordinamento per Fusione 68
Esempio
Insieme iniziale
| 33 | 21 | 7 | 48 | 28 | 13 | 65 | 17 |
 Suddivisione in 2 sottoinsiemi
| 33 | 21 | 7 | 48 |
| 28 | 13 | 65 | 17 |
 Ordinamento di ogni singolo sottoinsieme
| 7 | 21 | 33 | 48 |
| 13 | 17 | 28 | 65 |
 Combinazione (fusione) dei sottoinsiemi ordinati
| 7 | 13 | 17 | 21 | 28 | 33 | 48 | 65 |

Ordinamento per Fusione 69
Esempio
85 24 63 45
17
31 96 50
decomposizioni
85 24 63 45
ordinamenti
(semplici)
fusioni
17
31 96 50
85 24
63 45
17
31
96 50
24 85
45 63
17
31
50 96
24 45 63 85
17
24
31
17
31 50 96
45 50 63 85 96
70
Ordinamento per Fusione
void ordinamentoPerFusione(int[] a) {
n = a.length;
int[] b = new int[n] = {. . .};
mergesort(a, 0, n-1)
}
void mergeSort(int[] a, int p1, int p2){
if (p1 < p2) {
int q = (p1 + p2) / 2;
mergeSort(a, p1, q);
mergeSort(a, q+1, p2);
merge(a, p1, q, p2)
}
}
Ordinamento per Fusione 71
Merge
 La procedura di fusione deve lavorare su
segmenti diversi di uno stesso vettore
 Non può essere la stessa definita per la fusione
di due vettori distinti
 Occorre definire una nuova procedura che
fonda segmenti adiacenti di uno stesso
vettore
Ordinamento per Fusione 72
merge()
public static void merge
(int[] a, int from,
int mid, int to) {
int n = to - from + 1;
int[] b = new int[n];
int i = from;
int k = mid + 1;
int j = 0;
while (i<=mid && k<=to) {
if (a[i] < a[k])
{ b[j] = a[i]; i++;}
else
{ b[j] = a[k]; k++;}
j++;
}
while (i <= mid) {
b[j] = a[i];
i++;
j++;
}
while (k <= to) {
b[j] = a[k];
k++;
j++;
}
for (j = 0; j < n; j++)
a[from + j] = b[j];
}
Ordinamento per Fusione 73
Considerazioni
un certo numero di livelli di decomposizioni e
fusioni
 in ciascun livello vengono fusi tutti gli elementi

il costo asintotico di ciascun livello di fusioni è N
 il numero di livelli è log2 N

TmergeSort(N) = N log2 N
74
Quicksort [Hoare]
 Detto anche ordinamento per
partizionamento-scambio

Basato sull’idea che nei primi passi di
ordinamento la distanza fra i dati da muovere
deve essere grande


Analogamente all’ordinamento di Shell
2 versioni


Iterativa
Ricorsiva
75
Quicksort & Shellsort

L’ordinamento di Shell confronta al primo passo
elementi a distanza n/2


Equità nelle distanze di confronto fra i vari elementi
Distanza massima riferita al singolo elemento



Primo con l’ultimo
Secondo col penultimo
…
Quicksort 76
Partizionamento
 Poco beneficio

Alcuni confronti non generano scambi


Al più n/2 scambi
Ogni scambio sistema al più una coppia

Andrebbe bene se l’array avesse un ordine inverso
 Meglio un metodo di partizionamento


elementi più piccoli | elementi più grandi
Aumenta il preordinamento
Riduce la dimensione del problema

Ogni partizione può essere affrontata separatamente
Quicksort 77
Scelta Valore Centrale
 Il partizionamento richiede un valore in
base al quale distinguere gli elementi
 Scelto a caso

Potrebbe essere esterno ai valori della lista
 Prenderne uno della lista stessa

Indifferente quale
 Elemento mediano


Elemento in posizione media
Posizione media

Limite inferiore + limite superiore DIV 2
Quicksort 78
Dopo il Partizionamento

Situazione:

Parte sinistra


Parte destra


Elementi minori dell’elemento mediano
Elementi maggiori dell’elemento mediano
Escluse interazioni fra partizioni

Nessun elemento della parte sinistra potrà mai,
nell’ordinamento, finire nella parte destra


Ciascuna può essere trattata (ordinata) separatamente
Ciascuna contiene meno elementi della lista completa
Quicksort 79
Ciclo

Per ciascuna delle 2 partizioni ottenute valgono
le stesse considerazioni precedenti



Riapplichiamo su ciascuna i concetti già esposti



Problemi analoghi a quello iniziale (ordinamento)
Dimensione minore
Si ottengono, per ciascuna, altre 2 partizioni
Situazione dopo un certo numero di passi:
elementi I partizione < elementi II partizione <
… < elementi n-ma partizione

Criterio di stop: partizioni di un solo elemento


Non ulteriormente partizionabili
Già ordinate
Quicksort 80
Algoritmo di Base
 Mentre tutte le partizioni non risultano
ancora ridotte a dimensione 1 esegui



Scegli la prossima partizione da elaborare
Individua un nuovo valore di partizionamento
per la partizione scelta (il mediano)
Partiziona la partizione attuale in due insiemi
più piccoli parzialmente ordinati rispetto al
mediano attuale
Quicksort 81
Iterativo

Ogni passo genera 2 nuove partizioni

Si può agire su una sola partizione alla volta



Una si elabora
L’elaborazione dell’altra resta in sospeso
Possibile esistenza, in un certo momento, di partizioni
create in attesa di elaborazione


Necessità di ricordarne i limiti destro e sinistro
 Dove iniziano
 Dove finiscono
Salvare l’informazione sui limiti di ciascuna
 Riferita alle posizioni (indice) dell’array originale
Quicksort 82
Ricordare i Compiti Futuri
 Memorizzazione dei limiti delle partizioni
lasciate in sospeso
 Ogni partizione richiede la memorizzazione di
2 valori che la individuano (gli estremi)


Immagazzinati in un array di appoggio
 Relativi agli indici dell’array di partenza
 2 elementi dell’array di appoggio per ogni
partizione
Scelta della prossima
 Simulazione di una struttura a pila

Last In, First Out
Quicksort 83
Fabbisogno di Memoria

Man mano che il partizionamento prosegue



Il numero delle partizioni di cui salvare l’informazione
sui limiti aumenta (di 1)
Le partizioni diventano sempre più piccole
Dimensione dell’array di servizio

Caso peggiore: 2(n–1) elementi



Si memorizzano n – 1 partizioni lasciate in sospeso
 Ogni volta si elabora la partizione più grande
 Ogni volta la partizione più piccola contiene un solo
elemento
| partizione da elaborare per prima | x |
Ciascuna richiede 2 elementi
Troppo Spazio!
Quicksort 84
Ottimizzazione
 Elaborare per prima la partizione più
piccola


Minor numero di partizioni da tenere in
sospeso
Partizioni di dimensione 1 non generano altre
partizioni


Individuate e scartate subito dall’elaborazione
In ogni istante esiste una sola partizione in sospeso
di cui vanno salvati i limiti (la più grande)
Quicksort 85
Ottimizzazione/2
 Ad ogni passo si elabora la più grande
delle partizioni più piccole


Nel precedente caso peggiore: 2 elementi
Nuovo caso peggiore: 2 log2 n


Partizioni divise sempre a metà
 Dimensione dimezzata
Accettabile
Quicksort 86
Salvataggio Limiti con Pila

Test per decidere qual è la partizione più grande

Se le partizioni cadono a sinistra del medio



Altrimenti



Elabora la partizione sinistra (che è la più grande)
Salva i limiti di quella destra
Elabora la partizione destra (che è la più grande)
Salva i limiti di quella sinistra
La partizione più grande è sempre messa in
attesa di elaborazione

Gestione a pila
Quicksort 87
Partizionamento e Accodamento
1
a
2
a
|
e f
|
cd
|
c
3
b
a..c < d..b
f..c < a..e
| ||
f ghc
f..g < h..c
Top dello stack
Vettori di servizio
d .. b
1
a .. e
d .. b
2
h
a
d
3
..
..
..
c
e
b
Quicksort 88
Fine Ciclo

Quando si raggiunge una partizione di
dimensione 1 si ricomincia il processo di
partizionamento sulla partizione più
recentemente memorizzata


Limiti della partizione rimossi dalla cima della pila
Quando tutte le partizioni sono state ridotte a
dimensione 1 non ci saranno più limiti nella pila


Pila vuota
Terminazione del processo di partizionamento
Quicksort 89
Algoritmo




Acquisire l’array a[1..n] da ordinare
Inizializzare il puntatore al top dello stack al valore 2
Impostare i limiti sup e inf dell’array nello stack a 1..n
(si inizia con una sola partizione coincidente con l’intero array)
Mentre lo stack non è vuoto esegui
 Estrai dal top dello stack la prossima partizione
(limiti sup e inf del segmento di array corrispondente)
 Mentre il corrente segmento non è ridotto a dimensione 1 esegui
 Scegli l’elemento mediano del segmento di array
 Partiziona il segmento in 2 rispetto al suo valore mediano
 Salva nello stack i limiti della partizione più grande
 Segmento corrente ← partizione più piccola
Quicksort 90
Codice Java
top = 0;
stack[top++] = 0; stack[top] = n - 1;
while (top > 0) {
right = stack[top--]; left = stack[top--];
while (left < right) {
medio = (left + right) / 2;
mediano = a[medio];
pos = partiziona(a,left,right,mediano);
if (pos < medio) { // salva dx e lavora su sx
stack[++top] = pos+1;
stack[++top] = right;
right = pos; // nuovo inizio sx
} else { // salva sx e lavora sulla dx
stack[++top] = left;
stack[++top] = pos;
left = pos+1; // nuovo inizio dx
}
} // while (left < right)
}
Quicksort 91
Versione Ricorsiva

Algoritmo per sua natura ricorsivo

Implementazione ricorsiva molto semplice:
Strategia basata sull’applicazione dello stesso
processo a problemi via via più piccoli



Divide l’insieme di dati in 2 partizioni in modo tale che tutti
gli elementi della partizione sinistra (prime pos posizioni)
siano ≤ a tutti gli elementi della parte destra (posizioni da
pos+1 fino ad n)
Riapplica lo stesso meccanismo di partizionamento ad
entrambe le partizioni (che sono di dimensioni via via più
piccole) a[1..pos] e a[pos+1..n] fino ad ottenere segmenti di
dimensione 1
A questo punto l’array sarà completamente ordinato
Quicksort 92
Ricorsivo
Situazione iniziale:
dati originali
inf = 1
sup = n
Dopo il primo partizionamento:
partizione sinistra
inf
|
pos
partizione destra
sup
Passi fondamentali dell’algoritmo ricorsivo

Se nell’insieme corrente c’è più di 1 elemento



Partiziona i dati in partizione sinistra e destra
Ripeti il processo di partizionamento per la partizione
sinistra
Ripeti il processo di partizionamento per la partizione destra
Quicksort 93
Ricorsivo/2
 Indicatori di indice necessari:
inf

|
pos
sup
partition(a, inf, sup, mediano, pos)
 Ricorsione


quicksort2(a, inf, pos)
quicksort2(a, pos+1, sup)
 Terminazione della ricorsione

Chiamata ad un segmento di 1 unico elemento
 Test: se inf < sup allora continua
Quicksort 94
Algoritmo Ricorsivo
Se il segmento corrente da ordinare contiene più di 1 elemento allora
Partizionalo in 2 segmenti più piccoli tali che tutti gli elementi del
segmento di sinistra siano ≤ di tutti gli elementi del segmento di
destra
Se il segmento di sinistra è più piccolo del segmento di destra
allora
Quicksort il segmento di sinistra
Quicksort il segmento di destra
Altrimenti
Quicksort il segmento di destra
Quicksort il segmento di sinistra
Quicksort 95
Metodo Java
void quicksort2(double[] a, int inf, int sup) {
int medio = -1;
double mediano;
if (inf < sup) {
medio = (inf + sup) / 2;
mediano = a[medio];
partition(a, inf, sup, mediano, medio);
if ((medio – inf) < (sup – medio-1)) {
quicksort2(a, inf, medio);
quicksort2(a, medio+1, sup);
}
else {
quicksort2(a, medio+1, sup);
quicksort2(a, inf, medio);
}
} // if (inf < sup)
}
Quicksort 96
Considerazioni
 Suddivisione successiva delle partizioni


Struttura risultante ad albero binario
Caso peggiore: suddivisione a metà



Profondità dell’albero: log2 n
 Numero di volte che si può spaccare
successivamente a metà una lista di n elementi
Ciascun livello dell’albero contiene n elementi
Complessità: n log2 n

Algoritmo ottimo!