Professional Documents
Culture Documents
2
Per suggerimenti, commenti o segnalazione di errori scrivere a
acciaro@di.uniba.it
oppure a
m di leonardo@yahoo.it
Indice
1 Introduzione
1.1 Programmazione in piccolo ed in grande
1.2 Obiettivi del corso . . . . . . . . . . . .
1.3 Motivazioni . . . . . . . . . . . . . . . .
1.3.1 I limiti del calcolabile . . . . . . .
1.4 Algoritmo . . . . . . . . . . . . . . . . .
1.4.1 Specifiche del problema . . . . . .
1.4.2 Strumenti di formalizzazione . . .
1.4.3 Esempio di algoritmo . . . . . . .
1.4.4 Lalgoritmo come funzione . . . .
1.4.5 Nota storica . . . . . . . . . . . .
1.5 Definizione formale di problema. . . . . .
1.6 Programma . . . . . . . . . . . . . . . .
1.7 Costi . . . . . . . . . . . . . . . . . . . .
1.7.1 Risorse di calcolo . . . . . . . . .
1.7.2 Efficienza. . . . . . . . . . . . . .
1.7.3 Modello di calcolo . . . . . . . . .
1.7.4 Irrisolubilit`a . . . . . . . . . . . .
1.7.5 Intrattabilit`a . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
9
9
9
10
11
11
13
14
14
15
15
16
16
16
17
17
18
18
19
2 La macchina di Turing
2.1 Definizione di Macchina di Turing . . . . .
2.1.1 Lalfabeto esterno della M.d.T. . .
2.1.2 Gli stati della M.d.T. . . . . . . . .
2.1.3 Configurazione iniziale della MdT.
2.1.4 Il programma nella M.d.T. . . . . .
2.1.5 Il Programma come funzione . . . .
2.1.6 Terminazione della computazione. .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
21
21
22
22
22
22
23
23
INDICE
2.2
2.3
2.4
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
23
23
24
25
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
31
32
32
33
33
33
34
34
34
36
39
41
41
43
43
44
44
44
45
45
46
46
47
47
47
48
48
INDICE
49
. . . . . . . . . . . 49
scritti in pseudo. . . . . . . . . . . 50
. . . . . . . . . . . 51
. . . . . . . . . . . 52
6 Algoritmi ricorsivi
6.1 Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . .
6.2 Qualche mini esempio . . . . . . . . . . . . . . . . . . . . .
6.3 Linguaggi di programmazione che consentono la ricorsione
6.3.1 Visita di un albero binario . . . . . . . . . . . . . .
7 Lapproccio Divide et Impera
7.1 Introduzione . . . . . . . . . .
7.1.1 Esempio: il Mergesort
7.2 Bilanciamento . . . . . . . . .
7.3 Lalgoritmo di Strassen . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
53
54
54
55
56
.
.
.
.
57
59
60
61
62
.
.
.
.
.
.
.
65
66
68
69
70
72
74
76
77
79
11 Programmazione Dinamica
11.1 Introduzione . . . . . . . . . . . . . . . .
11.1.1 Un caso notevole . . . . . . . . .
11.1.2 Descrizione del metodo . . . . . .
11.1.3 Schema base dellalgoritmo . . . .
11.1.4 Versione definitiva dellalgoritmo
11.1.5 Un esempio svolto . . . . . . . .
81
81
81
82
83
83
84
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
INDICE
12 Dizionari
85
13 Alberi
87
89
15 Alberi AVL
91
93
17 Le heaps
17.1 Le code con priorit`a . . .
17.2 Le heaps . . . . . . . . . .
17.3 Ricerca del minimo . . . .
17.4 Inserimento . . . . . . . .
17.5 Cancellazione del minimo .
17.6 Costruzione di una heap .
17.7 Esercizi . . . . . . . . . .
18 Heapsort
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
95
95
96
97
97
98
99
101
103
19 Tecniche Hash
105
19.1 Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
19.2 Caratteristiche delle funzioni hash . . . . . . . . . . . . . . . . 108
19.3 Tecniche di scansione della tabella . . . . . . . . . . . . . . . . 109
19.3.1 Scansione uniforme . . . . . . . . . . . . . . . . . . . . 109
19.3.2 Scansione lineare . . . . . . . . . . . . . . . . . . . . . 109
19.3.3 Hashing doppio . . . . . . . . . . . . . . . . . . . . . . 110
19.3.4 Hashing quadratico . . . . . . . . . . . . . . . . . . . . 110
19.4 Hashing tramite concatenamento diretto . . . . . . . . . . . . 111
19.5 Hashing perfetto . . . . . . . . . . . . . . . . . . . . . . . . . 112
19.6 Implementazione pratica di uno schema ad indirizzamento aperto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
19.7 Analisi di complessit`a . . . . . . . . . . . . . . . . . . . . . . . 115
19.7.1 Dimostrazione per le tecniche a concatenazione . . . . 116
19.8 Esercizi di ricapitolazione . . . . . . . . . . . . . . . . . . . . 117
19.9 Esercizi avanzati . . . . . . . . . . . . . . . . . . . . . . . . . 118
INDICE
20 Il BucketSort
20.1 Descrizione dellalgoritmo . . . . . .
20.1.1 Correttezza dellalgoritmo . .
20.1.2 Complessit`a nel caso medio .
20.1.3 Complessit`a nel caso pessimo
20.2 Esercizi . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
121
. 121
. 122
. 123
. 124
. 124
21 Complessit
a del problema ordinamento
125
21.1 Alberi decisionali . . . . . . . . . . . . . . . . . . . . . . . . . 125
22 Selezione in tempo lineare
129
22.1 Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
22.2 Un algoritmo ottimale . . . . . . . . . . . . . . . . . . . . . . 130
23 Algoritmi su grafi
135
INDICE
Capitolo 1
Introduzione
1.1
1.2
10
CAPITOLO 1. INTRODUZIONE
Testi di riferimento
I testi di riferimento sono indicati in bibliografia. Come libro di testo `e
fortemente consigliato il volume di Lodi e Pacini [1].
Come libro di consultazione si consiglia il classico testo di Aho, Hopcroft
e Ullman [4]. Questo `e un must per gli addetti al settore, ovvero un volume
di cui non si pu`o fare a meno.
Infine, la trattazione matematica del problema della complessit`a degli
algoritmi `e molto rigorosa nel primo volume di Cormen, Leiserson e Rivest
[2].
1.3
Motivazioni
1.4. ALGORITMO
1.3.1
11
1.4
Algoritmo
12
CAPITOLO 1. INTRODUZIONE
1.4. ALGORITMO
13
Ordina la sequenza 3,8,6,0,2
1.4.1
14
CAPITOLO 1. INTRODUZIONE
Dati: Un insieme di n chiavi a1 , . . . , an che `e possibile confrontare
usando loperatore . Le chiavi sono numeri interi positivi pi`
u piccoli
di un limite prefissato (ad esempio 1000).
Risultato: Una sequenza b1 , . . . , bn che costituisce un riordinamento
dellinsieme dato di chiavi e che soddisfa la relazione bi bi+1 per ogni
i = 1, 2, ..., n 1.
1.4.2
Strumenti di formalizzazione
1.4.3
Esempio di algoritmo
1.4. ALGORITMO
15
1.4.4
1.4.5
Nota storica
16
CAPITOLO 1. INTRODUZIONE
` questa la caratteristica che rende talvolta preferibili, per la
E
rappresentazione e lorganizzazione delle informazioni, i modelli
algoritmici a quelli matematici tradizionali (D.E. Knuth)
1.5
1.6
Programma
1.7
Costi
1.7. COSTI
17
1.7.1
Risorse di calcolo
Definizione 2 Il costo relativo allesecuzione di un programma viene definito come la quantit`a di risorse di calcolo che il programma utilizza durante
lesecuzione.
Le risorse di calcolo a disposizione del programma sono:
1 Il Tempo utilizzato per eseguire lalgoritmo;
2 Lo Spazio di lavoro utilizzato per memorizzare i risultati intermedi.
3 Il Numero degli esecutori, se pi`
u esecutori collaborano per risolvere lo
stesso problema.
Almeno inizialmente, `e bene pensare al concetto di risorsa cercando dei
riferimenti con il mondo reale, ad esempio lorganizzazione di un ufficio.
Tale classificazione delle risorse `e totalmente indipendentemente dalla
sua interpretazione informatica. Qualora ci si riferisca al campo specifico
dellinformatica:
lo spazio di lavoro diventa la memoria del calcolatore;
il numero degli esecutori diventa il numero dei processori a nostra
disposizione, in un sistema multi-processore.
1.7.2
Efficienza.
18
CAPITOLO 1. INTRODUZIONE
1.7.3
Modello di calcolo
1.7.4
Irrisolubilit`
a
1.7. COSTI
19
1.7.5
Intrattabilit`
a
20
CAPITOLO 1. INTRODUZIONE
Capitolo 2
La macchina di Turing
In questa lezione sar`a esaminato un modello di calcolo molto generale, la
Macchina di Turing. Tale argomento `e stato probabilmente gi`a presentato
nel corso di Programmazione, per introdurre in modo assolutamente formale
la nozione di algoritmo e la Tesi di Church.
Nel contesto del nostro corso la Macchina di Turing `e un eccellente strumento didattico, poich`e ci consente di definire esattamente la nozione di
algoritmo, ma sopratutto ci consente di definire in modo semplice ed inequivocabile la nozione di risorse utilizzate da un algoritmo (spazio e tempo).
In virt`
u dellesistenza di un modello di calcolo assolutamente generale,
la nozione di irrisolubilit`a gia introdotta nelle lezioni precedenti assumer`a
nuova luce.
Al fine di agevolare la comprensione dellargomento, saranno presentate
durante la lezione alcune Macchine di Turing molto semplici.
2.1
22
2.1.1
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.2
2.2.1
Irrisolubilit`
a
24
Abbiamo visto in precedenza che esistono problemi irrisolubili algoritmicamente, e che la logica matematica si occupa (tra laltro) dei limiti della
computabilit`a, ovvero dello studio e della classificazione di tali problemi.
2.3
Esempi
1 ) Controllo di parit
a.
Siano: S = {1, b, |{z}
P , |{z}
D }
Q = {q0 , . . . qf }.
P ari Dispari
q0
q1
qf
(q1 , 1, DEST RA) (q0 , 1, DEST RA)
(qf , P, F ERM O) (qf , D, F ERM O)
S = {1, b, +}
Q = {q0 , q1 , q2 , qf }.
2.4. ESERCIZI
25
2.4
Esercizi
26
Capitolo 3
La Random Access machine
(RAM)
Questo capitolo non `e essenziale in un corso introduttivo di algoritmi e
strutture dati.
Largomento pu`o essere trattato, se il tempo a disposizione lo consente, in
appendice al corso, per introdurre il modello di costo logaritmico, e mostrare
la sua validit`a nella valutazione della complessit`a delle operazioni aritmetiche.
3.1
Definizione
28
Si assume che:
1 Il programma non pu`o modificare se stesso;
2 Tutti i calcoli avvengono utilizzando una locazione fissa di memoria
della accumulatore;
3 Ogni locazione di memoria ed ogni celletta del nastro di input ed output sono in grado di contenere un simbolo arbitrario (oppure, se si
preferisce, un numero intero di dimensione arbitraria);
4 Il programma pu`o accedere alle singole locazioni (cellette) di memoria
in ordine arbitrario.
Il punto (4) giustifica laggettivo casuale. Equivalentemente, possiamo
formulare il punto (4) come segue:
Il tempo di accesso ad una locazione (celletta) di memoria deve essere
indipendente dalla celletta.
3 Attenzione
Perche non `e possibile implementare praticamente (costruire) una RAM?
Se poniamo alcune limitazioni (in particolare la rimozione dei termini infinito ed arbitrario, ovunque essi compaiano) in modello RAM `e praticamente
implementabile, e rispecchia larchitettura della maggior parte dei calcolatori
attuali.
3.2
Complessit`
a computazionale di programmi RAM
29
30
Capitolo 4
Nozioni base di complessit`
a
Nelle lezioni precedenti sono state definite formalmente le nozioni di algoritmo, problema, e risorse di calcolo utilizzate da un algoritmo.
` stato inoltre introdotto il concetto di modello di calcolo, quale asE
trazione di un esecutore reale.
In particolare, `e stato esaminato in dettaglio un modello di calcolo, la
Macchina di Turing, al fine di formalizzare la nozione di algoritmo e fornire
al tempo stesso una immagine molto accurata delle nozioni di spazio e tempo
utilizzati da un programma, come numero di cellette utilizzate e transizioni
della Macchina.
Abbiamo visto che un uso improprio delle risorse di calcolo pu`o pregiudicare la possibilit`a di utilizzare praticamente un algoritmo. Si `e definito
inoltre efficiente un algoritmo che fa un uso parsimonioso delle risorse di
calcolo a propria disposizione.
A parte tali definizioni assolutamente generali, non `e stato mostrato alcun
esempio concreto di algoritmo efficiente o poco efficiente.
Nella presente lezione verranno ripresi brevemente tali concetti, e verr`a
definita, inizialmente in maniera informale, la nozione di complessit`a di un
algoritmo.
Quindi sar`a definita la nozione di complessit`a di un problema, e la relazione tra le due nozioni di complessit`a.
31
`
CAPITOLO 4. NOZIONI BASE DI COMPLESSITA
32
4.1
Introduzione
Abbiamo visto nelle precedenti lezioni che possiamo suddividere le problematiche riguardanti lo studio degli algoritmi in tre aree:
Sintesi (o progetto):
dato un problema P , costruire un algoritmo A per risolvere P ;
Analisi:
dato un algoritmo A ed un problema P , dimostrare che A risolve P
(correttezza) e valutare la quantit`a di risorse utilizzate da A;
Classificazione (o complessit`
a strutturale):
data una quantit`a T di risorse individuare la classe dei problemi risolubili da algoritmi che utilizzano al pi`
u tale quantit`a.
4.1.1
4.1.2
33
Pu`o accadere che dati due algoritmi per risolvere lo stesso problema P , il primo faccia un uso pi`
u efficiente della risorsa spazio, mentre il secondo privilegi
la risorsa tempo. Quale algoritmo scegliere?
Tra le due risorse spazio e tempo, il tempo va quasi sempre privilegiato. Infatti, lo spazio `e una risorsa riutilizzabile, mentre il
tempo non lo `e.
4.2
4.2.1
`
CAPITOLO 4. NOZIONI BASE DI COMPLESSITA
34
4.2.2
4.2.3
Occorre definire in anticipo come si misura la dimensione della particolare istanza del problema (input). In altre parole, non esiste un criterio
universale di misura.
Esempio 5 Nel caso di algoritmi di ordinamento, una misura possibile (ma
non lunica, attenzione!) `e data dal numero di elementi da ordinare. Quindi,
se (50, 4, 1, 9, 8) rappresenta una lista da ordinare, allora
|(50, 4, 1, 9, 8)| = 5.
Esempio 6 Nel caso di algoritmi di addizione e moltiplicazione di interi,
una misura possibile (ma non lunica, attenzione) `e data dal numero di cifre
decimali necessario ad esprimere la lunghezza degli operandi.
4.3
Complessit`
a temporale
` TEMPORALE
4.3. COMPLESSITA
35
`
CAPITOLO 4. NOZIONI BASE DI COMPLESSITA
36
4.4
Confronto di algoritmi
Di seguito elenchiamo il tempo di esecuzione, misurato in secondi, di 4 programmi, che implementano rispettivamente gli algoritmi A1 ed A2, su due
diverse architetture (computer1 e computer2)
computer
Dim. input A1
50
0.005
100
0.03
200
0.13
300
0.32
400
0.55
500
0.87
1000
3.57
1
A2
0.07
0.13
0.27
0.42
0.57
0.72
1.50
computer
A1
0.05
0.18
0.73
1.65
2.93
4.60
18.32
2
A2
0.25
0.55
1.18
1.85
2.57
3.28
7.03
37
massima del
1 min
60000
4893
244
39
15
problema
1 ora
3600000
200000
1897
153
21
massima del
1 min
600000
599900
750
80
18
problema
1 ora
360000000
3599900
5700
300
24
`
CAPITOLO 4. NOZIONI BASE DI COMPLESSITA
38
Conclusioni
39
(n + 3)(n + 2)
6=
2
1 2 5
=
n + n3
2
2
Osservazioni
Nellesempio precedente possiamo asserire che il tempo di esecuzione e al pi`
u
proporzionale al quadrato della dimensione dellinput.
La costante di proporzionalit`a non viene specificata poich`e dipende da
vari fattori (tecnologici):
bont`a del compilatore
velocit`a del computer
ecc.
Infatti, quando la dimensione dellinput cresce arbitrariamente (tende allinfinito), le costanti di proporzionalit`a non ci interessano affatto.
Esempio 7 Supponiamo che:
T1 (n) = 2n
1 3
T2 (n) =
n
2
T3 (n) = 5n2
T4 (n) = 100n
Esistono allora 5 costanti n0 , c1 , c2 , c3 , c4 tali che, per n > n0 risulta:
c1 T1 (n) > c2 T2 (n) > c3 T3 (n) > c4 T4 (n)
Per considerare esclusivamente il comportamento asintotico di un algoritmo
introduciamo la notazione O.
4.5
Definizione formale di O
Definizione 7 Diciamo che T (n) = O(f (n)) se esistono due costanti positive c ed n0 tali che per ogni n > n0 risulti T (n) < cf (n).
40
`
CAPITOLO 4. NOZIONI BASE DI COMPLESSITA
1
n2
2
3
T (n) n2
2
T (n) = O(n2 )
quadrato := 0;
For i:=1 to n do
quadrato := quadrato + n
La complessit`a rappresenter`a in questo esempio il numero di operazioni
di assegnazione e di somma effettuati nel caso pessimo. Che possiamo dire
sulla complessit`a di questo algoritmo? In particolare, possiamo dire che sia
lineare?
Certamente il tempo di esecuzione sar`a lineare in n.
Nota bene 3 Per rappresentare n in binario occorrono blognc+1 bits, dove
bxc denota la parte intera di x.
Quindi, se n e rappresentato in binario allora lalgoritmo avr`a una complessit`a
esponenziale nella dimensione dellinput!
Se il nostro input n e rappresentato in notazione unaria allora lalgoritmo
avr`a una complessit`a lineare nella dimensione dellinput.
4.6. LA NOTAZIONE
4.5.1
41
7 Attenzione
O rappresenta una delimitazione asintotica superiore alla complessit`a dellalgoritmo, e non la delimitazione asintotica superiore.
Infatti, se T (n) = (n4 ), `e anche vero che:
T (n) = O(n7 )
ecc.
4.6
La notazione
42
`
CAPITOLO 4. NOZIONI BASE DI COMPLESSITA
8 Attenzione
f (n) rappresenta una delimitazione inferiore asintotica, e non la delimitazione
inferiore asintotica, al tasso di crescita di T (n).
Esempi
Sia T (n) = 7n2 + 6. Si dimostra facilmente che T (n) = (n2 ). Attenzione:
`e anche vero che:
T (n) = (n log n)
T (n) = (n)
T (n) = (1)
Sia T (n) = 7n2 (log n)4 + 6n3 . Si dimostra facilmente che T (n) = (n3 ).
Attenzione: `e anche vero che:
T (n) = (n log n)
T (n) = (n)
T (n) = (1)
ci nt (log n)k
4.7. LA NOTAZIONE
43
dove h e il pi`
u grande tra le t che compaiono nella somma, e z il corrispondente esponente di log n.
Esempio 10 La funzione T (n) = 4n5 (log n)3 + 9n3 (logn)5 + 125n4 (log n)7 e
O(n5 (log n)3 ) ed e (n5 (log n)3 ).
4.6.1
Definizione alternativa di
Definizione 9 Diciamo che una funzione f (n) `e (g(n)) se esiste una costante
positiva c ed una sequenza infinita n1 , n2 , . . . tale che per ogni i risulti
f (ni ) > cg(ni ).
Questa seconda definizione ha molti vantaggi sulla prima ed `e diventata
oramai la definizione standard.
Ad esempio, a differenza dei numeri reali, se utilizziamo la prima definizione
di , allora non `e sempre possibile confrontare asintoticamente due funzioni.
Se utilizziamo per`o la seconda definizione di allora, date due funzioni
f (n) e g(n) asintoticamente positive risulta:
f (n) = O(g(n)); oppure
f (n) = (g(n)).
4.7
La notazione
Definizione 10 Diciamo che f (n) = (g(n)) se esistono tre costanti positive c, d, n0 tali che per ogni n > n0 risulti cg(n) < f (n) < dg(n).
In altre parole, quando una funzione f (n) e contemporaneamente O(g(n)) e (g(n)),
allora diciamo che g(n) rappresenta una delimitazione asintotica stretta al
tasso di crescita di f (n), e scriviamo f (n) = (g(n)).
Esempio 11 La funzione T (n) = 4n5 (log n)3 + 9n3 (log n)5 + 125n4 (log n)7
e (n5 (log n)3 )
`
CAPITOLO 4. NOZIONI BASE DI COMPLESSITA
44
4.8
Alcune propriet`
a di O, ,
4.8.1
4.9
Ricapitolando
` DI PROBLEMI
4.10. COMPLESSITA
45
Esempio 12 Nel caso di una Macchina di Turing, avremo che il passo elementare e dato dalla transizione, e la dimensione dellinput non e altro che
il numero di cellette utilizzate per rappresentare listanza del problema.
La complessit`a nel caso pessimo non e altro che una funzione T : N N
definita come:
T (n) := max {num. dei passi eseguiti da A su un input x
| x ha dimensione n}
Poiche `e difficile calcolare esattamente la funzione T (n), ricorriamo alla
notazione asintotica: O, , .
4.10
Complessit`
a di problemi
4.10.1
`
CAPITOLO 4. NOZIONI BASE DI COMPLESSITA
46
4.10.2
Purtroppo, nel caso di un problema P , non `e possibile definire una delimitazione asintotica inferiore alla sua complessit`a in termini degli algoritmi
noti, in quanto il migliore algoritmo per risolvere P potrebbe non essere
ancora noto!
La definizione che viene data in questo caso non `e pi`
u di natura costruttiva!
Definizione 12 Un problema P ha complessit`
a (g(n)) se qualunque algoritmo (noto o ignoto) per risolvere P richiede tempo (g(n)).
definisce quindi un limite inferiore alla complessit`a intrinseca di P .
In genere:
` relativamente semplice calcolare un limite asintotico superiore alla
1 E
complessit`a di un problema assegnato.
` molto difficile stabilire un limite asintotico inferiore, che non sia
2 E
banale.
I metodi per stabilire delimitazioni inferiori sono tratti generalmente da:
teoria dellinformazione
logica matematica
algebra
ecc.
4.11
Algoritmi ottimali
47
Nota sullottimalit`
a.
Le nozioni fin qui discusse si riferiscono alle valutazioni di complessit`a nel
` possibile definire analoghe nozioni nel caso medio.
caso pessimo. E
Esempio 15 Si dimostra che lalgoritmo quicksort ha complessit`a O(n2 ) nel
caso peggiore, e O(n log n) nel caso medio. Quindi, lalgoritmo quicksort `e
ottimale nel caso medio.
4.12
4.13
4.14
Crescita esponenziale
`
CAPITOLO 4. NOZIONI BASE DI COMPLESSITA
48
4.15
4.16
Esercizi
Capitolo 5
Linguaggi per la descrizione di
algoritmi
5.1
Introduzione
Per descrivere un algoritmo `e diventato comune adottare una variante semplificata del PASCAL o del C (pseudo-linguaggio).
Istruzioni semanticamente semplici sono scritte in linguaggio naturale.
Ad esempio:
scambia x ed y
sta per:
1.temp := x
2.x:= y
3.y:= temp
rendendo cosi lalgoritmo pi`
u facile da leggere ed analizzare.
Etichette non numeriche sono utilizzate per le operazioni di salto. Ad
esempio:
Goto uscita
Allinterno di funzioni listruzione:
Return (espressione)
`e usata per valutare lespressione, assegnare il valore alla funzione, ed
infine terminarne lesecuzione.
Allinterno di procedure listruzione:
49
5.2
51
ordina il vettore A
costa O(1) se la dimensione del vettore `e costante ci`o non `e vero se la
dimensione di A dipende dallinput.
inserisci lelemento E nella lista L
pu`o richiedere tempo costante o meno, dipendentemente dallimplementazione
della lista.
Mini esempio.
1. x := 0
2. For i:=1 to n do
3. x := x+i
Il passo 1 costa O(1). Il passo 3 costa O(1). I passi 2 e 3 costano: O(1) +
n [O(1) + O(1)] = O(1) + n O(1) = O(1) + O(n) = O(n). Lintero algoritmo
ha costo: O(1) + O(n) = O(n), ovvero lineare nella dimensione dellinput.
Esercizio
1. x := 0
2. i :=1
3. While i<=n do
4. For j:=1 to i do
5.
Begin
6.
x := x+i *j
7.
y := i+1
8.
End
9. i:=i+1
Dimostrare che il tempo richiesto `e O(n2 ).
5.3
5.3.1
Capitolo 6
Algoritmi ricorsivi
Si presuppone che lo studente abbia gia incontrato la nozione di algoritmo
ricorsivo allinterno del corso di Programmazione.
Sicuramente lo studente avr`a incontrato alcuni esempi canonici, quali
lalgoritmo per il calcolo del fattoriale di un numero e lalgoritmo per risolvere
il problema della Torre di Hanoi, ed avr`a studiato la loro implementazione
in Pascal.
Probabilmente lo studente avr`a studiato anche il quicksort o il mergesort,
dipendentemente dal contenuto del corso di Programmazione.
Si presuppone che lo studente abbia gi`a incontrato la tecnica di dimostrazione
per induzione nel corso di Programmazione e/o in uno dei corsi di Matematica del primo anno.
Agli algoritmi ricorsivi saranno dedicate due lezioni di due ore ciascuna.
Verr`a innanzitutto presentata la definizione di algoritmo ricorsivo e verranno ripresi alcuni esempi canonici gi`a incontrati nel corso di Programmazione.
Si mostrer`a poi come la ricorsione permette una migliore comprensione
di un algoritmo e semplifica lanalisi di correttezza dello stesso, mostrando
come esempio i due approcci ricorsivo ed iterativo alla visita di un albero.
Infine si accenner`a brevemente al problema di calcolare la complesit`a di
un algoritmo ricorsivo, problema che sar`a affrontato in dettaglio in un ciclo
di lezioni successivo.
53
54
6.1
Introduzione
Si definisce ricorsiva una prodedura P che invoca, direttamente o
indirettamente, se stessa.
6.2
Fattoriale(n)
int fatt;
fatt=1;
for i=2 to n
fatt := fatt * i
return( fatt )
1. Fattoriale(n)
2. if n=1
3.
then return(1)
4.
else return(n * Fattoriale(n-1))
6.3
Non tutti i linguaggi di programmazione permettono la ricorsione: ad esempio, il FORTRAN oppure il COBOL non la permettono.
Fortunatamente, il linguaggio PASCAL, che `e stato studiato nel corso di Programmazione, oppure il linguaggio C, che viene trattato nel
Laboratorio di Algoritmi e Strutture Dati, lo permettono.
Procedura attiva
Con il termine procedura attiva indichiamo la procedura in esecuzione ad
istante assegnato. Tale procedura sar`a caratterizzata da un contesto, che
`e costituito da tutte le variabili globali e locali note alla procedura in tale
istante.
Stack e record di attivazione
I linguaggi che permettono la ricorsione utilizzano in fase di esecuzione una
pila per tenere traccia della sequenza di attivazione delle varie procedure
ricorsive.
In cima a tale pila sar`a presente il record di attivazione della procedura
correntemente attiva. Tale record contiene le variabili locali alla procedura, i
56
6.3.1
Nel classico libro [4] a pp. 5657 sono riportati un algoritmo ricorsivo ed uno
non-ricorsivo per visitare un albero binario.
Si noti che la versione non-ricorsiva fa uso esplicito di una pila per tenere
traccia dei nodi visitati.
La versione ricorsiva `e molto pi`
u chiara da comprendere; la pila c`e ma
non si vede, ed `e semplicemente lo stack che contiene i record di attivazione
delle procedure.
Capitolo 7
Lapproccio Divide et Impera
Nel corso degli anni, i ricercatori nel campo dellinformatica hanno identificato diverse tecniche generali di progetto di algoritmi che consentono di
risolvere efficientemente molti problemi.
Lo studente avr`a gia avuto modo di incontrare una di tali tecniche, il
backtracking, nel corso di Programmazione, in relazione al classico problema delle Otto Regine. Tale tecnica sar`a ripresa e trattata in dettaglio nei
corsi di Intelligenza artificiale
In un corso introduttivo di Algoritmi e strutture dati ritengo essenziale
includere almeno:
Il metodo Divide et Impera;
La programmazione dinamica;
Le tecniche greedy.
Le tecniche di ricerca locale sono anchesse molto importanti e meritano
una trattazione approfondita. Ritengo possibile omettere tali tecniche dal
momento che saranno affrontate in dettaglio nei corsi di Intelligenza artificiale.
Si noti comunque che, prima di passare alla progettazione di un algoritmo,
`e necessario individuare se il problema da risolvere `e irrisolubile o intrattabile.
In tali casi nessuna tecnica di progetto ci permetter`a di ottenere, ovviamente,
algoritmi efficienti.
Il metodo Divide et Impera `e una tra le tecniche pi`
u importanti, e dal
pi`
u vasto spettro di applicazioni.
57
58
Nella prima lezione, dopo una definizione formale della tecnica, sar`a presentato un esempio notevole, il mergesort. Si mostrer`a quindi come effettuare
lanalisi di complessit`a di tale algoritmo, espandendo la relazione di ricorrenza
che ne definisce il tempo di esecuzione. Il risultato di tale analisi, O(n log n)
consentir`a allo studente di apprezzare meglio tale algoritmo, confrontandone
la complessit`a con quella di un algoritmo di ordinamento gi`a noto, il Bubblesort. Si accenner`a quindi al fatto che il problema ordinamento ha complessit`a
(n log n), e quindi lo studente avr`a incontrato un algoritmo ottimale di ordinamento. La dimostrazione della complessit`a del problema ordinamento,
attraverso alberi decisionali, sar`a comunque relegata ad un ciclo di lezioni
successivo dedicato al problema ordinamento.
Si passer`a quindi a discutere il problema del bilanciamento, e per mostrare
le implicazioni si far`a vedere che modificando opportunamente il Mergesort in
modo da avere sottoproblemi di dimensione differente, lalgoritmo risultante
sar`a decisamente meno efficiente, ed avr`a una complessit`a pari a quella del
Bubblesort.
Nella seconda lezione si mostrer`a come, tale metodo, sebbene si utilizzino
sottoproblemi di dimensione simile, non porta in genere ad algoritmi pi`
u
efficienti. In altre parole, occorre comunque effettuare sempre lanalisi della
complessit`a dellalgoritmo per poter dire se sia efficiente o meno.
A tal fine, si mostrer`a un algoritmo ricorsivo di moltiplicazione di matrici,
che richiede ad ogni passo ricorsivo 8 moltiplicazioni di matrici di dimensione
dimezzata. Dallanalisi del tempo di esecuzione di tale algoritmo lo studente potr`a evincere il fatto che la sua complessit`a, in termini di operazioni
aritmetiche elementari, `e pari a quella dellalgoritmo classico.
Si mostrer`a quindi lalgoritmo di Strassen, che `e una evoluzione della tecnica (intuitiva) ricorsiva appena vista, ma richiede ad ogni passo il calcolo di
7 prodotti di matrici di dimensione pari alla met`a della dimensione originale.
Si mostrer`a quindi come analizzare il tempo di esecuzione di tale algoritmo.
Per concludere la lezione, si accenner`a brevemente alla complessit`a del
problema Moltiplicazione di Matrici, ed al fatto che nessun algoritmo ottimale `e noto sinora per risolvere tale problema.
7.1. INTRODUZIONE
7.1
59
Introduzione
60
7.1.1
Esempio: il Mergesort
Sia S una lista di n elementi da ordinare. Assumiamo che n sia una potenza
di 2, ovvero n = 2k , in modo da poter applicare un procedimento dicotomico.
Nellalgoritmo che segue L, L1 e L2 indicano variabili locali di tipo lista.
1. Mergesort( S ):
2. Se S ha 2 elementi
3.
ordina S con un solo confronto e ritorna la lista ordinata
4. altrimenti
5.
Spezza S in due sottoliste S1 e S2 aventi la stessa cardinalit`
a
6.
Poni L1 = Mergesort( S1 )
7.
Poni L2 = Mergesort( S2 )
8.
Fondi le due liste ordinate L1 ed L2 in una unica lista L
9.
Ritorna( L )
La fusione di due liste ordinate in una unica lista si effettua molto semplicemente in n passi, vale a dire in tempo (n) Ad ogni passo occorre semplicemente confrontare i due elementi pi`
u piccoli delle liste L1 ed L2 , estrarre il
minore tra i due dalla lista a cui appartiene, ed inserirlo in coda alla nuova
lista L.
Si nota facilmente che:
il tempo richiesto per ordinare una lista di cardinalit`
a 2 `e costante;
Quindi possiamo scrivere T (2) = O(1).
per ordinare una lista di dimensione maggiore di 2 occorre:
spezzare la lista in due sottoliste
questo richiede tempo costante
ordinare ricorsivamente le 2 sottoliste di dimensione n/2
questo richiede tempo T (n/2) + T (n/2)
fondere il risultato
questo richiede tempo (n)
Quindi T (n) = O(1) + T (n/2) + T (n/2) + (n) = 2 T (n/2) + (n).
Le due formule per T (n) appena viste, che esprimono tale funzione quando
n = 2 e quando n `e una potenza di 2, insieme costituiscono ci`o che si chiama
7.2. BILANCIAMENTO
61
7.2
Bilanciamento
62
T (1) = 1
T (n) = T (1) + T (n 1) + n (n > 1)
7.3
Lalgoritmo di Strassen
A11 A12
A21 A22
B=
B11 B12
B21 B22
C=
C11 C12
C21 B22
(7.1)
(7.2)
63
T (1) = 1
T (n) = 8 T (n/2) + 4 (n/2)2
(n > 1)
La soluzione di questa equazione di ricorrenza `e data da T (n) = 0(n3 ). Pertanto tale algoritmo non presenta alcun vantaggio sullalgoritmo tradizionale.
Lidea di Strassen
Si calcolino le seguenti matrici quadrate Pi , di dimensione n/2:
P1
P2
P3
P4
P5
P6
P7
=
=
=
=
=
=
=
64
T (1) = 1
T (n) = 7 T (n/2) + 18 (n/2)2
(n > 1)
Capitolo 8
Tecniche di analisi di algoritmi
ricorsivi
Nel corso delle lezioni sul calcolo della complessit`a di un algoritmo sono state
date delle semplici regole per calcolare la complessit`a di un algoritmo iterativo
espresso utilizzando uno pseudo-linguaggio per la descrizione di algoritmi.
In particolare sono state presentate:
La regola della somma,
per calcolare la complessit`a di una sequenza costituita da un numero
costante di blocchi di codice;
La regola del prodotto,
per calcolare la complessit`a di una sezione di programma costituita da
un blocco di codice che viene eseguito iterativamente.
Per mostrare come applicare tali regole, sono stati utilizzati come esempi gli
algoritmi di bubble sort, inserion sort e moltiplicazione di matrici.
Le tecniche per analizzare la complessit`a di algoritmi ricorsivi sono molto
differenti dalle tecniche viste finora, ed utilizzano a tal fine delle relazioni di
ricorrenza. Le tecniche per risolvere tali relazioni di ricorrenza sono molto
spesso ad hoc, e ricordano piuttosto vagamente le tecniche per risolvere le
equazioni differenziali. Non `e richiesto allo studente alcuna conoscenza di
equazioni differenziali o di matematiche superiori per poter comprendere le
tecniche base che saranno trattate nelle seguenti due lezioni. Lunico prerequisito matematico `e la conoscenza della formula che esprime la somma di
una serie geometrica finita, formula che verr`a richiamata durante la lezione.
65
8.1
Introduzione
8.1. INTRODUZIONE
67
(n > 1)
(n > 1)
Problema: Data una sequenza definita attraverso una equazione di ricorrenza di ordine k vogliamo trovare una formula chiusa per il termine n-mo
di tale sequenza, ovvero una formula per esprimere ln-mo termine senza fare
riferimento ai termini precedenti.
Mini esempio: Consideriamo la seguente equazione di ricorrenza:
a0 = 1
an = c an1
(n > 0)
8.1.1
69
(0 i < n)
8.2
T (n) = 7
T (n) = 2T ( n2 ) + 8
a = 15
1
per n = 2
n
2T ( 2 ) + 2 per n > 2
Si ha allora
2T ( n2 ) + 2
2[2T ( n4 ) + 2] + 2
4T ( n4 ) + 4 + 2
4[2T ( n8 ) + 2] + 4 + 2
8T ( n8 ) + 8 + 4 + 2
..
.
n
k1
k3
= |2k1
+ 2k2 + 2{z
+ . . . + 4 + 2}
{z } T ( 2k1 ) 2
|
n
Pk1
| {z }
T (n) =
=
=
=
=
n
2
T (2)=1
p=1
2p
+ n 2 = 32 n 2
Ricordiamo per la comodit`a del lettore che la somma dei primi k + 1 termini
di una serie geometrica:
0
q + q + q + ... + q
k1
+q =
k
X
qp
p=0
`e data da
da cui
q k+1 1
q1
k1
X
2k 1
1 = 2k 2 = n 2
2 =
21
p=1
8.3
Una importante classe di equazioni di ricorrenza `e legata allanalisi di algoritmi del tipo divide et impera trattati in precedenza.
71
Ricordiamo che un algoritmo di questo tipo suddivide il generico problema originale di dimensione n in un certo numero a di sottoproblemi (che
sono istanze del medesimo problema, di dimensione inferiore) ciascuna di dimensione n/b per qualche b > 1; quindi richiama ricorsivamente se stesso su
tali istanze ridotte e poi fonde i risultati parziali ottenuti per determinare la
soluzione cercata.
Senza perdita di generalit`a, il tempo di calcolo di un algoritmo di questo
tipo pu`o essere descritto da una equazione di ricorrenza del tipo:
T (n) = a T (n/b) + d(n)
T (1) = c
(n = bk k > 0)
(8.1)
(8.2)
dove il termine d(n), noto come funzione guida, rappresenta il tempo necessario per spezzare il problema in sottoproblemi e fondere i risultati parziali
in ununica soluzione.
Si faccia ancora una volta attenzione al fatto che T (n) `e definito solo per
potenze positive di b. Si assuma inoltre che la funzione d() sia moltiplicativa,
ovvero
d(xy) = d(x) d(y)
x, y N
n>1
n>1
da cui
U (1) = c/f
U (n) = 2 U (n/2) + n
n>1
Si noti che in tale equazione la funzione guida `e d(n) = n che `e moltiplicativa. Risolviamo per U (n) ottenendo U (n) = O(n log n). Sostituiamo, infine,
ottenendo T (n) = f U (n) = f O(n log n) = O(n log n).
8.3.1
T (1) = c
T (n) = aT ( nb ) + d(n)
Si noti che
n
n
n
T ( i ) = aT ( i+1 ) + d( i )
b
b
b
da cui discende che
!
n
T (n) = aT
+ d(n)
b
"
!
!#
n
n
= a aT 2 + d
d(n)
b
b
= a2 T
=
=
=
=
73
cn
logb a
k1
X
aj d(bkj )
j=0
Soluz.Omogenea: Soluz.Particolare:
tempo per risolvere tempo per combinare
i sottoproblemi
i sottoproblemi
Effettuiamo ora delle assunzioni sulla funzione guida d() per semplificare
lultima sommatoria. Assumiamo che d() sia moltiplicativa, cio`e d(xy) =
d(x) d(y) per ogni x, y N . Ad esempio d(n) = np `e moltiplicativa, poich`e
d(xy) = (xy)p = xp y p = d(x)d(y).
Se d() `e moltiplicativa, allora ovviamente:
d(xi ) = d(x)i
8.3.2
Soluzione Particolare
k1
X
aj d(bkj )
j=0
Allora, si avr`a
P (n) = d(bk )
k1
X
aj d(bj )
j=0
= d(b )
k1
X
j=0
= d(bk )
=
a
d(b)
k
a
d(b)
a
d(b)
k
i
ak d(b)
a
1
d(b)
Pertanto:
P (n) =
ak d(b)k
a
1
d(b)
a > d(b)
P (n) = O(ak ) = O(alogb n ) = O(nlogb a )
da cui
logb a
logb a
T (n) = c| n{z
) = O(nlogb a )
} + O(n
| {z }
sol.omogenea
ovvero
sol.particolare
T (n) = O(nlogb a )
Si noti che le soluzioni particolare e omogenea sono equivalenti (nella notazione O().
Per migliorare lalgoritmo occorre diminuire logb a (ovvero, aumentare b
oppure diminuire a). Si noti che diminuire d(n) non aiuta.
75
a < d(b)
P (n) =
d(b)k ak
= O(d(b)k ) = O(d(b)logb n ) = O(nlogb d(b) )
a
1 d(b)
da cui
logb a
logb d(b)
T (n) = c| n{z
) = O(nlogb d(b) )
} + O(n
|
{z
}
sol.omogenea
ovvero
sol.particolare
d(n) = np
Si ha
d(b) = bp e logb d(b) = logb bp = p
da cui
T (n) = O(np )
La soluzione particolare eccede la soluzione omogenea. Per migliorare lalgoritmo occorre diminuire logb d(b), ovvero cercare un metodo pi`
u veloce per
fondere le soluzioni dei sottoproblemi.
CASO 3 :
a = d(b)
1=0
k1
X
a i
k
P (n) = d(b)
(
) = d(b)
1 = d(b)k k = d(b)logb n logb n = nlogb d(b) logb n
j=0 d(b)
j=0
k
k1
X
a
d(b)
da cui
T (n) = c nlogb d(b) + nlogb d(b) logb n
ovvero
T (n) = O(nlogb d(b) logb n)
Caso speciale : d(n) = np
Si ha
d(b) = np
8.3.3
Esempi
T (1)
T1 (n)
T2 (n)
T3 (n)
T4 (n)
=
=
=
=
=
c
2T ( n2 ) + n
4T ( n2 ) + n
4T ( n2 ) + n2
4T ( n2 ) + n3
a
2
4
4
4
b d(b)
2 2
2 2
2 4
2 8
=
=
=
=
O(n log. n)
O(n2 )
O(n2 log. n)
O(n3 )
Capitolo 9
Liste, Pile e Code
Si consiglia fortemente allo studente di studiare dal testo [4] le sezioni 1, 2,
3 e 4 del capitolo 2.
In alternativa e possibile studiare le sezioni 1, 2 e 3 del capitolo 11 del
testo [2].
77
78
Capitolo 10
Grafi e loro rappresentazione in
memoria
Si consiglia fortemente allo studente di studiare dal testo [4]
le sezioni 1 e 2 del capitolo 6;
la sezione 1 del capitolo 7.
Si richiede inoltre di implementare in C o C++ gli algoritmi base descritti
in tali sezioni.
79
Capitolo 11
Programmazione Dinamica
11.1
Introduzione
11.1.1
Un caso notevole
82
dove Mi e una matrice con ri1 righe e ri colonne. Lordine con cui le
matrici sono moltiplicate ha un effetto significativo sul numero totale di
moltiplicazioni richieste per calcolare M , indipendentemente dallalgoritmo
di moltiplicazione utilizzato. Si noti che il calcolo di
Mi
Mi+1
[ri1 ri ]
[ri ri+1 ]
richiede ri1 ri ri+1 moltiplicazioni, e la matrice ottenuta ha ri1 righe e ri
colonne. Disposizioni differenti delle parentesi danno luogo ad un numero di
moltiplicazioni spesso molto differenti tra loro.
Esempio 21 Consideriamo il prodotto
M =
M1
M2
M3
M4
[10 20]
[20 50]
[50 1]
[1 100]
Per calcolare
M1 (M2 (M3 M4 )) sono necessari 125000 prodotti;
(M1 (M2 M3 ) M4 ) sono necessari 2200 prodotti;
Si desidera minimizzare il numero totale di moltiplicazioni. Ci`o equivale a
tentare tutte le disposizioni valide di parentesi, che sono O(2n ), ovvero in
numero esponenziale nella dimensione del problema.
La programmazione dinamica ci permette di ottenere la soluzione cercata
in tempo O(n3 ). riassumiamo qui di seguito lidea alla base di tale metodo.
11.1.2
Sia
mij
(1 i j n)
0
se i = j
minik<j (mik + mk+1,j + ri1 rk rj ) se i < j
11.1. INTRODUZIONE
83
M 00
[ri1 rk ]
[rj rk ]
11.1.3
1. Per l che va da 0 a n 1
2.
Calcola tutti gli mij tali che |j i| = l e memorizzali
3. Restituisci m1n
11.1.4
1. Per i che va da 0 a n
2.
Poni mii := 0
3. Per l che va da 1 a n 1
4.
Per i che va da 1 a n l
5.
Poni j := i + l
6.
Poni mij := minikj (mik + mk+1,j + ri1 rk rj )
7. Restituisci m1n
Nota bene 7 Si noti che, lutilizzo di una tabella per memorizzare i valori
mij via via calcolati, e lordine in cui vengono effettuati i calcoli ci garantiscono che al punto (6) tutte le quantit`
a di cui abbiamo bisogno siano gi`
a
disponibili
84
11.1.5
Un esempio svolto
m11
m12
m13
m14
=0
m22 = 0
m33 = 0
m44 = 0
= 10000 m23 = 1000 m34 = 5000
= 1200 m24 = 3000
= 2200
2
6
9
3
7
Capitolo 12
Dizionari
Si consiglia fortemente allo studente di studiare dal testo [4] le sezioni 1, 3,
4, 5 e 6 del capitolo 4.
85
86
Capitolo 13
Alberi
Si consiglia fortemente allo studente di studiare dal testo [4] le sezioni 1, 2,
3 e 4 del capitolo 3 (omettere il paragrafo An Example: Huffman Codes).
In alternativa e possibile studiare dal testo [2] le sezioni 4 e 5 del capitolo
5.
87
88
Capitolo 14
Alberi di Ricerca Binari
Si consiglia fortemente allo studente di studiare dal testo [4] le sezioni 1 e
2 del capitolo 5.
In alternativa, e possibile studiare le sezioni 1, 2, 3 e 4 del capitolo 13 del
testo [2].
Nota bene 8 La trattazione dellanalisi di complessit
a nel testo [4] (sezione
2, p. 160) e molto pi
u accessibile.
89
90
Capitolo 15
Alberi AVL
Si consiglia fortemente allo studente di studiare dal testo [1] la sezione 6
del capitolo 13.
91
92
Capitolo 16
2-3 Alberi e B-Alberi
Si consiglia fortemente allo studente di studiare dal testo [4]
la sezione 4 del capitolo 5;
dalla sezione 4 del capitolo 11 i paragrafi Multiway Search Trees,
B-trees e Time Analysis of B-tree Operations.
In alternativa e possibile studiare il capitolo 19 del testo [3].
93
94
Capitolo 17
Le heaps
17.1
Una coda con priorit`a `e un tipo di dato astratto simile alla coda, vista in
precedenza.
Ricordiamo che la coda `e una struttura FIFO (First In First Out), vale a
dire, il primo elemento ad entrare `e anche il primo ad uscire. Il tipo di dato
astratto coda pu`o essere utilizzato per modellizzare molti processi del mondo
reale (basti pensare alla coda al semaforo, alla cassa di un supermercato,
ecc.). Vi sono molte applicazioni in cui la politica FIFO non `e adeguata.
Esempio 22 Si pensi alla coda dei processi in un sistema operativo in attesa
della risorsa CPU. La politica FIFO in tale caso non terrebbe conto delle
priorit`a dei singoli processi. In altre parole, nel momento in cui il processo
correntemente attivo termina lesecuzione, il sistema operativo deve cedere il
controllo della CPU non al prossimo processo che ne ha fatto richiesta bens`i
al processo avente priorit`
a pi`
u alta tra quelli che ne hanno fatto richiesta.
Assegnamo pertanto a ciascun processo un numero intero P , tale che ad un
valore minore di P corrisponda una priorit`a maggiore. Il sistema operativo
dovr`a di volta in volta estrarre dal pool dei processi in attesa quello avente
priorit`a maggiore, e cedere ad esso il controllo della CPU.
Per modellizzare tali situazioni introduciamo le code con priorit`a. Sia U un
insieme totalmente ordinato, come al solito. Una coda con priorit`a A `e un
sottinsieme di U su cui sono ammesse le seguenti operazioni:
95
96
17.2
Le heaps
Una heap `e un albero binario che gode delle seguenti due propriet`a:
1 Forma:
Una heap `e ottenuta da un albero binario completo eliminando 0 o pi`
u
foglie. Le eventuali foglie eliminate sono contigue e si trovano sulla
estrema destra.
2 Ordinamento relativo:
La chiave associata a ciascun nodo `e minore o uguale delle chiavi
associate ai propri figli;
Nota bene 9 Come conseguenza della 2a propriet`
a si ha che la radice contiene la chiave pi`
u piccola.
Le altre conseguenze sono dovute alla forma particolare che ha una heap.
1 Laltezza h di una heap avente n nodi `e O(log n).
2 Una heap pu`o essere memorizzata molto efficientemente in un vettore,
senza utilizzare puntatori.
La prima asserzione `e semplice da dimostrare: poich`e un albero binario
completo di altezza h ha 2h+1 1 nodi, si ha che n 2h 1, da cui
h log(n + 1) = O(log n).
97
Per quanto riguarda la rappresentazione di una heap, utilizziamo un vettore Nodi contenente in ciascuna posizione la chiave associata al nodo. La
radice della heap verr`a memorizzata nella prima posizione del vettore (ovvero,
in posizione 1). Se un nodo si trova in posizione i, allora leventuale figlio sinistro sar`a memorizzato in posizione 2i e leventuale figlio destro in posizione
2i + 1. La seguente tabella riassume quanto detto:
N ODI
P OSIZION E
Radice
1
nodo p
i
padre di p
b 2i c
figlio sinistro di p
2i
figlio destro di p
2i + 1
Come al solito, se x `e un numero reale, con bxc si intende la parte intera
inferiore di x.
Si noti che in virt`
u di tale rappresentazione la foglia che si trova pi`
ua
destra al livello h, dove h `e laltezza dellalbero, `e memorizzata nel vettore
Nodi in posizione n.
17.3
In virt`
u delle propriet`a viste precedentemente, sappiamo che il pi`
u piccolo
elemento di una heap si trova in corrispondenza della radice, che `e memorizzata nella prima posizione del vettore. Quindi, la procedura che implementa
la ricerca del minimo `e banalmente:
Procedura Minimo()
1. Ritorna ( Nodi[1] )
Ovviamente, il tempo richiesto `e O(1).
17.4
Inserimento
98
Per quanto riguarda limplementazione effettiva, lo pseudo-codice della procedura che implementa linserimento `e il seguente:
Procedura Inserisci( Chiave ):
1.
n := n + 1
2.
Nodi[ n ] := Chiave
3.
SpostaSu( n )
Procedura SpostaSu( i ):
1.
Se b 2i c 6= 0
2.
/* Nodi[ i ] ha un padre */
3.
Se Nodi[ i ] < Nodi[ b 2i c ]
4.
Scambia Nodi[ i ] e Nodi[ b 2i c ]
5.
SpostaSu(b 2i c)
Il tempo richiesto sar`a nel caso peggiore dellordine dellaltezza della heap,
ovvero O(h) = O(log n).
17.5
99
Per quanto riguarda limplementazione effettiva, lo pseudo-codice della procedura che implementa linserimento `e il seguente:
Procedura CancellaMin():
1.
Nodi[ 1 ] := Nodi[ n ]
2.
n := n - 1
3.
SpostaGiu( 1 )
Procedura SpostaGiu( i ):
0.
Se 2i n
1.
/* Nodi[ i ] ha almeno un figlio */
2.
Sia m lindice del figlio contenente la chiave pi`
u piccola
3.
Se Nodi[ m ] < Nodi[ i ]
4.
Scambia Nodi[ m ] e Nodi[ i ]
5.
SpostaGiu( m )
Poich`e il numero totale di confronti e scambi `e al pi`
u volta proporzionale
allaltezza della heap, il tempo richiesto sar`a ancora una volta O(log n).
17.6
100
17.7. ESERCIZI
101
Complessit`
a
La costruzione di una heap di n elementi richiede tempo O(n).
Dimostrazione:
Indichiamo con l(v) la massima distanza di un nodo v da
una foglia discendente di v. Indichiamo poi con nl il numero di nodi v della
heap tali che l(v) = l. Si noti che
nl 2hl =
2h
n
l
l
2
2
h
X
l=1
h
X
l nl
l
l=1
= n
h
X
l
l1
Poich`e
h
X
l
l=1
2l
<
X
l
l=0
2l
n
2l
2l
1/2
=2
(1 1/2)2
17.7
Esercizi
102
Capitolo 18
Heapsort
LHeapsort `e un algoritmo di ordinamento ottimale nel caso peggiore, che
sfrutta le propriet`a delle heaps viste in precedenza. Questo algoritmo ordina
in loco, cio`e utilizza una quantita di spazio pari esattamente alla dimensione
dellinput. Sia S un insieme di elementi da ordinare di dimensione n. Lalgoritmo heapsort costruisce dapprima una heap contenente tutti gli elementi
di S, quindi estrae ripetutamente dalla heap il piu piccolo elemento finche
la heap risulti vuota.
Heapsort( S ):
1. CostruisciHeap( S )
2. Per i che va da 1 a n
3.
A[i] = Minimo()
4.
CancellaMin()
Qual `e la complessit`a di tale algoritmo? Ricordiamo che il passo 1 richiede
tempo O(n), il passo 2 tempo costante ed il passo 4 tempo O(log n) nel caso
pessimo. Dalla regola della somma e dalla regola del prodotto si deduce che
la complessita nel caso pessimo e O(n log n).
103
104
Capitolo 19
Tecniche Hash
Alle tecniche hash saranno dedicate 2 lezioni da (circa) 2 ore ciascuna allinterno di un corso introduttivo di Algoritmi e Strutture Dati della durata
di 120 ore.
1 Nella prima lezione saranno presentati i concetti fondamentali, e la classificazione delle diverse tecniche. In tale lezione verr`a omesso completamente il problema della cancellazione negli schemi ad indirizzamento
aperto.
2 Nella seconda lezione sar`a mostrata limplementazione effettiva delle
procedure di inserimento, cancellazione e ricerca nel caso degli schemi
ad indirizzamento aperto, ricorrendo alluso di pseudo-codice laddove
necessario. In particolare verr`a posto laccento sui problemi legati alla
cancellazione di un elemento.
Quindi verr`a mostrata la complessit`a nel caso peggiore e nel caso medio
delle operazioni elementari di inserimento, ricerca e cancellazione. Per
semplicit`a lanalisi di complessit`a verr`a effettuata per i soli schemi a
concatenamento.
Lo studente dovr`a avere appreso dalle lezioni precedenti:
Il concetto di lista concatenata,
come (possibile) struttura dati per implementare il tipo di dato astratto
lista;
Il concetto di insieme,
come tipo di dato astratto;
105
106
Il concetto di dizionario,
come tipo di dato astratto, ottenuto specializzando il tipo di dato
astratto insieme;
Come implementare un dizionario utilizzando un vettore di bit.
19.1
Introduzione
Abbiamo visto nelle lezioni precedenti che il tipo di dato astratto dizionario
`e uno tra i pi`
u utilizzati nella pratica.
Gli elementi che costituiscono un dizionario sono detti chiavi , e sono
tratti da un insieme universo U . Ad ogni chiave `e associato un blocco di
informazioni.
Scopo del dizionario `e la memorizzazione di informazioni; le chiavi permettono il reperimento delle informazioni ad esse associate.
Su un dizionario vogliamo poter effettuare fondamentalmente le operazioni di inserimento, ricerca e cancellazione di elementi.
Le tabelle hash consentono di effettuare efficientemente queste tre operazioni, nel caso medio. In particolare dimostreremo che tali operazioni
richiedono tempo medio costante, e pertanto gli algoritmi che implementano
tali operazioni su una tabella hash sono ottimali nel caso medio.
Sfortunatamente, la complessit`a delle tre operazioni viste sopra `e lineare
nel numero degli elementi del dizionario, nel caso peggiore.
Le tecniche hash di organizzazione delle informazioni sono caratterizzate
dallimpiego di alcune funzioni, dette hashing (o equivalentemente scattering),
che hanno lo scopo di disperdere le chiavi appartenenti al nostro universo
U allinterno di una tabella T di dimensione finita m, generalmente molto
pi`
u piccola della cardinalit`a di U .
Una funzione hash `e una funzione definita nello spazio delle chiavi U ed
a valori nellinsieme N dei numeri naturali. Tale funzione accetta in input
una chiave e ritorna un intero in un intervallo predefinito [0, m 1].
La funzione hash deve essere progettata in modo tale da distribuire uniformamente le chiavi nellintervallo [0, m 1]. Il valore intero associato ad
una chiave `e utilizzato come indice per accedere ad un vettore di dimensione
m, detto Tabella hash, contenente i records del nostro dizionario. I records
sono inseriti nella tabella (e quindi possono essere successivamente trovati)
19.1. INTRODUZIONE
107
utilizzando la funzione hash per calcolare lindice nella tabella a partire dalla
chiave del record.
Quando la funzione hash ritorna lo stesso indice per due chiavi differenti
abbiamo una collisione. Le chiavi che collidono sono dette sinonimi. Un
algoritmo completo di hashing consiste di
1 un metodo per generare indirizzi a partire dalle chiave, ovvero una
funzione hash;
2 un metodo per trattare il problema delle collisioni, detto schema di
risoluzione delle collisioni.
Esistono due classi distinte di schemi di risoluzione delle collisioni:
1 La prima classe `e costituita dagli schemi ad indirizzamento aperto.
Gli schemi in tale classe risolvono le collisioni calcolando un nuovo
indice basato sul valore della chiave - in altre parole effettuano un
rehash nella tabella;
2 La seconda classe `e costituita dagli schemi a concatenamento.
In tali schemi tutti i record che dovrebbbero occupare la stessa posizione nella tabella hash formano una lista concatenata.
Per inserire una chiave utilizzando lindirizzamento aperto occorre scandire la
tabella secondo una politica predefinita, alla ricerca di una posizione libera.
La sequenza di posizioni scandite `e chiamata percorso.
Negli schemi ad indirizzamento aperto la chiave sar`a inserita nella prima
posizione libera lungo tale percorso, avente origine nella posizione calcolata attraverso la funzione hash. Ci sono esattamente m! possibili percorsi,
corrispondenti alle m! permutazioni dellinsieme {0, . . . , m 1}, e la maggior
parte degli schemi ad indirizzamento aperto usa un numero di percorsi di gran
lunga inferiore a m! Si noti che a diverse chiavi potrebbe essere associato lo
stesso percorso.
La porzione di un percorso interamente occupato da chiavi prende il nome
di catena. Leffetto indesiderato di avere catene pi`
u lunghe di quanto sia
atteso `e detto clustering. Esistono delle tecniche di scansione (scansione
uniforme e scansione casuale) che non soffrono del problema del clustering.
Sia m la cardinalit`a della nostra tabella, ed n il numero dei records in essa
memorizzati. La quantit`a = n/m `e detta fattore di carico della tabella.
Ovviamente nelle tecniche ad indirizzamento aperto deve essere minore
108
uguale di uno, mentre non esiste nessuna restrizione sul fattore di carico
se vengono utilizzate delle liste concatenate per gestire le collisioni.
Verr`a dimostrato al termine della lezione che lefficienza di queste tecniche
dipende fondamentalmente dal fattore di carico della tabella: in altre parole,
quanto pi`
u `e piena la tabella, tante pi`
u operazioni di scansione occorre effettuare per cercare la nostra chiave o per cercare una posizione libera in cui
inserire una nuova chiave.
19.2
kU
Chiavi alfanumeriche
Se luniverso U `e costituito da stringhe alfanumeriche, possiamo ad esempio
considerare tali stringhe come numeri in base b, dove b `e la cardinalit`a del
109
codice utilizzato (ad esempio ASCII). In tal caso, supponendo che la stringa
sia composta dai caratteri s1 , s2 , . . . , sk possiamo porre:
h(k) =
k1
X
bi ski
(mod m)
i=0
19.3
Qui di seguito elenchiamo alcune delle tecniche maggiormente usate di scansione della tabella, negli schemi ad indirizzamento aperto. Ricordiamo che la
scansione avviene sempre a partire dalla posizione h(x) calcolata applicando
la funzione hash h() alla chiave x. Lobiettivo della scansione `e quello di
trovare la chiave cercata (nel caso della ricerca) oppure una posizione libera
in cui inserire una nuova chiave (nel caso dellinserimento).
19.3.1
Scansione uniforme
19.3.2
Scansione lineare
110
19.3.3
Hashing doppio
Lhashing doppio `e una tecnica ad indirizzamento aperto che risolve il problema delle collisioni attraverso lutilizzo di una seconda funzione hash h1 ().
La seconda funzione hash `e usata per calcolare un valore compreso tra 1
ed m 1 che verr`a utilizzato come incremento per effettuare la scansione
della tabella a partire dalla posizione calcolata attraverso la prima funzione
hash h(). Ogni differente valore dellincremento da origine ad un percorso
distinto, pertanto si hanno esattamente m 1 percorsi circolari.
Lhashing doppio `e pratico ed efficiente. Inoltre, dal momento che lincremento utilizzato non `e costante ma dipende dalla chiave specifica, tale
schema non soffre del problema del clustering primario.
Esempio 24 Sia m = |T | = 10, h(x) = 3 ed h1 (x) = 2. Il percorso sar`a
3, 5, 7, 9, 1
Se si desidera garantire che ciascun percorso abbia lunghezza massima, ovvero
lunghezza pari ad m, basta prendere m primo.
Esempio 25 Sia m = |T | = 11, h(x) = 3 ed h1 (x) = 2. Il percorso sar`a
3, 5, 7, 9, 0, 2, 4, 6, 8, 10, 1
19.3.4
Hashing quadratico
111
(mod
(mod
(mod
(mod
(mod
m),
m),
m),
m),
m)
Affinch`e il metodo sia efficace occorre che la dimensione m della tabella sia un
numero primo. In tal caso ciascun percorso avr`a lunghezza pari esattamente
a met`a della dimensione della tabella.
Esempio 26 Sia m = |T | = 11 ed h(x) = 3. Il percorso sar`a
3, 4, 7, 1, 8, 6
19.4
112
19.5
Hashing perfetto
Una funzione hash perfetta `e una funzione che non genera collisioni. Pertanto
`e richiesto sempre un solo accesso alla tabella.
Tale funzione hash `e costruita a partire dallinsieme delle chiavi che costituiscono il dizionario, che deve essere noto a priori. Pertanto tale schema `e
utilizzabile solo se linsieme delle chiavi `e statico (ad esempio le parole chiave
di un linguaggio).
19.6
prime k cifre
ultime k cifre
k cifre centrali
(mod m)
t
X
i=0
zi bi h(X) =
t
X
zi B i
(mod m)
i=0
x = 235221
b = 10
B=3
m = 50
5
4
3
= h(x) = 2 3 + 3 3 + 5 3 + 2 32 + 2 31 + 1 30
(mod 50)
3 Folding
Si divide la chiave originale in vari blocchi e se ne calcola la somma
modulo m:
X = x1 |x2 |x3 |x4 h(x) = x1 + x2 + x3 + x4 (mod m)
x = 23|52|21 h(x) = 23 + 52 + 21 (mod m)
Qui di seguito presentiamo le procedure di inserimento, ricerca e cancellazione di una chiave allinterno di una tabella hash. Lunica procedura che
presenta qualche difficolt`a concettuale `e la procedura di cancellazione di un
record. In tal caso occorre modificare opportunamente la tabella per far
si che lalgoritmo di ricerca non si arresti, erroneamente, in presenza di un
record cancellato.
Assumiamo che ogni celletta della nostra tabella Hash contenga almeno
i seguenti due campi:
chiave
utilizzata per identificare un record nel dizionario;
stato
utilizzato per indicare la situazione corrente della celletta.
Chiaramente, in aggiunta a tali campi la celletta potr`a contenere tutte le
informazione che desideriamo vengano associate alla chiave.
Il campo stato `e essenziale per il funzionamento delle procedure di ricerca, inserimento e cancellazione, e pu`o assumere i seguenti valori:
114
Libero
se la celletta corrispondente `e vuota;
Occupato
se la celletta corrispondente `e occupata da un record.
Qui di seguito mostriamo una versione semplificata degli algoritmi fondamentali per gestire una tabella hash.
Per evitare di appesantire inutilmente la descrizione, abbiamo omesso il
caso in cui la scansione ci riporti alla celletta da cui siamo partiti. Tale
eccezione va ovviamente gestita opportunamente durante la procedure di
inserimento, segnalando in tal caso limpossibilit`a di inserire il nuovo record,
e durante la procedura di ricerca, segnalando in tal caso che il record cercato
non `e presente nella tabella.
Inizializzazione
Per inizializzare la tabella, occorre porre il campo stato di ciascuna celletta
uguale a libero. Ci`o chiaramente richiede tempo lineare in m, la dimensione
della tabella.
Inserimento
. Sia h(x) la funzione hash applicata alla chiave x
. Se la celletta di indice h(x) `
e libera
.
allora inserisci x in tale celletta
. altrimenti (la celletta `
e occupata)
.
scandisci la tabella alla ricerca della prima celletta vuota B
.
inserisci il record in tale celletta B
Ricerca
. Sia h(x) la funzione hash applicata alla chiave x
. Se la celletta di indice h(x) `
e vuota
.
allora ritorna non trovato
. Altrimenti
.
se la celletta contiene x
.
allora ritorna la sua posizione
.
altrimenti
`
19.7. ANALISI DI COMPLESSITA
.
.
.
.
115
Cancellazione
.
.
.
.
.
.
.
.
.
.
.
Cerca la chiave x
Sia B la celletta in cui `
e contenuta la chiave x
Cancella la celletta B
Scandisci la tabella a partire dalla celletta B
per ogni celletta B 0 incontrata
se B 0 contiene una chiave y con h(y) B
allora
copia y in B,
cancella la celletta B 0 ,
poni B := B 0
finch`
e incontri una celletta vuota.
19.7
Analisi di complessit`
a
116
concatenazione
1+
1+ 2
1
(1
2
19.7.1
Sn = 1 + 2 = O()
Un = 1 + = O()
O()
O()
117
19.8
Esercizi di ricapitolazione
118
19.9
Esercizi avanzati
(m/d) = m
119
5 Quale relazione esiste tra i risultati dellesercizio precedente e la lunghezza dei percorsi nellhashing doppio? Per semplicit`a si considerino i soli
percorsi aventi origine da una posizione prefissata, ad esempio la prima.
120
Capitolo 20
Il BucketSort
Ricordiamo che la delimitazione inferiore alla complessit`a del problema ordinamento nel caso pessimo `e (n log n). Abbiamo visto nelle lezioni precedenti un algoritmo di ordinamento che ha complessit`a O(n log n) nel caso
pessimo, il mergesort. Tale algoritmo `e dunque ottimale nel caso pessimo.
Si pu`o fare meglio nel caso medio? Ovviamente, nessun algoritmo di ordinamento potr`a effettuare un numero di passi sub-lineare, nel caso medio. In
altre parole, poich`e occorre considerare ciascun elemento da ordinare almeno
una volta allinterno dellalgoritmo, nessun algoritmo potr`a in nessun caso
avere una complessit`a inferiore a O(n). In questa lezione mostreremo un
algoritmo di ordinamente che ha complessit`a O(n) nel caso medio, il bucketsort. Tale algoritmo `e pertanto ottimale nel caso medio. Nel caso peggiore
vedremo che la sua complessit`a degenera in O(n log n), e pertanto `e la stessa
di quella dei migliori algoritmi noti.
Il bucket sort combina lefficienza nel caso medio delle tecniche hash con
lefficienza nel caso pessimo del mergesort.
20.1
Descrizione dellalgoritmo
122
x min
c
max min
(20.1)
20.1.1
Correttezza dellalgoritmo
max min
n
esclusa, nella celletta 0. Tali chiavi andranno a costituire quindi la
prima catena.
min +
max min
n
max min
n
esclusa, nella celletta 1. Tali chiavi andranno a costituire quindi la
seconda catena.
min + 2
123
...
Le chiavi comprese tra
min + i
max min
n
max min
n
esclusa, nella celletta i. Tali chiavi andranno a costituire quindi la
i-esima catena.
min + (i + 1)
...
Occorre comunque notare che alcune catene potrebbero essere totalmente
assenti.
Quindi, ogni chiave presente nella prima catena sar`a pi`
u piccola di ogni
chiave contenuta nella seconda catena, ogni chiave presente nella seconda
catena sar`a pi`
u piccola di ogni chiave contenuta nella terza catena, e cosi
via. . .
Pertanto `e sufficiente ordinare le singole catene e poi concatenarle per
ottenere la lista ordinata desiderata.
20.1.2
Complessit`
a nel caso medio
124
Occorre quindi inserire gli elementi appartenenti alle catene (precedentemente ordinate) sequenzialmente in una unica lista. Poich`e si hanno n elementi, il tempo richiesto, utilizzando ad esempio un vettore per implementare
la lista, sar`a O(n).
Dalla regola della somma si deduce quindi che la complessit`a globale nel
caso medio `e O(n), ovvero lineare.
20.1.3
Complessit`
a nel caso pessimo
x S
In tal caso si ha una sola catena di lunghezza n avente origine dalla celletta
i-esima. In tal caso, il tempo dominante sar`a costituito dal tempo necessario per ordinare le n chiavi che costituiscono lunica catena. Tale tempo
`e O(n log n) utilizzando un algoritmo ottimale nel caso pessimo, quale il
mergesort.
20.2
Esercizi
n anzich`e n.
4 Svolgere il precedente esercizio utilizzando una tabella di dimensione
log n anzich`e n.
Capitolo 21
Complessit
a del problema
ordinamento
Gli algoritmi di ordinamento considerati finora si basano sul modello dei
confronti e scambi. Le operazioni ammissibili utilizzando questo modello
sono:
Confrontare due chiavi;
Scambiare i record associati a tali chiavi.
Dimostriamo in questo capitolo che non puo esistere alcun algoritmo di ordinamento basato su confronti e scambi avente complessita nel caso pessimo
inferiore a O(n log n). In altre parole, (n log n) rappresenta una delimitazione inferiore alla complessita del problema ordinamento. Poiche il
Mergesort ha complessita O(n log n), deduciamo che:
Il Mergesort e ottimale;
La complessita del problema ordinamento e (n log n).
21.1
Alberi decisionali
n
2
!n !
2
n
n
= log
2
2
127
Capitolo 22
Selezione in tempo lineare
22.1
Introduzione
130
Tale soluzione richiede tempo O(n log n) nel caso pessimo. Esiste una soluzione
migliore? Cio equivale a chiedere se la complessita di tale problema sia inferiore a (n log n), la complessita del problema ordinamento. La risposta
e affermativa!
22.2
Un algoritmo ottimale
Lalgoritmo mostrato in tale capitolo ha complessita O(n). Poiche ovviamente nessun algoritmo di selezione potra avere mai tempo di esecuzione
sub-lineare, dovendo prendere in considerazione ciascun elemento dellinsieme
almeno una volta, lalgoritmo qui di seguito presentato e ottimale.
131
Procedura Seleziona( k , A )
0. Se |A| < 50
1. Allora
2.
Disponi gli elementi di A in un vettore V
3.
Ordina V
4.
Restituisci V [k]
5. altrimenti
6.
Dividi A in d |A|
e sequenze, di 5 elementi ciascuna
5
7.
Ordina ciascuna sequenza di 5 elementi
8.
Sia M linsieme delle mediane delle sequenze di 5 elementi
9.
Poni m = Seleziona(d |M2 | e, M )
10.
Costruisci A1 = {x A|x < m}
10.
Costruisci A2 = {x A|x = m}
10.
Costruisci A3 = {x A|x > m}
11.
Se |A1 | k
12.
Allora
13.
Poni x = Seleziona( k , A1 )
13.
Restituisci x
14.
Altrimenti
15.
Se |A1 | + |A2 | k
16.
Allora
17.
Restituisci m
18.
Altrimenti
19.
Poni x = Seleziona( k |A1 | |A2 | , A3 )
20.
Restituisci x
Analisi di correttezza
Si noti che la scelta particolare di m da noi fatta non influenza affatto la
correttezza dellalgoritmo. Un qualsiasi altro elemento di A ci garantirebbe
certamente la correttezza, ma non il tempo di esecuzione O(n).
Analisi di complessita
Immaginiamo di disporre le d |A|
e sequenze ordinate di 5 elementi ciascuna
5
in una tabellina T , una sequenza per ogni colonna, in modo tale che la terza
riga della tabellina, costituita dalle mediame delle sequenze, risulti ordinata
in maniera crescente. In base a tale disposizione, la mediama m delle mediane
132
d |A|
e
5
e
2
3
|A|
4
|A3 |
3
|A|
4
T (n) = O(1)
se n < 50
n
3
T (n) T ( 5 ) + T ( 4 n) + c n altrimenti
133
Supponiamo ora che lasserzione sia vera per tutte le dimensioni dellinput
inferiori ad n. Vogliamo dimostrare che lasserzione sia vera anche per n. Si
ha infatti:
T (n)
=
=
=
come volevasi dimostrare.
n
3
T
+T
n +c n
5
4
n
3
20 c + 20 c n + c n
5
4 !
20 20 3
+
+1 c n
5
4
80 + 300 + 20
cn
20
20 c n
134
Capitolo 23
Algoritmi su grafi
Si consiglia fortemente allo studente di studiare dal testo [4]
le pagine 212-213 (il paragrafo Transitive Closure)
la sezione 5 del capitolo 6;
le pagine 221-222 (i paragrafi Test for Acyclicity e Topological Sort).
In alternativa e possibile studiare
le sezioni 1, 3 e 4 del capitolo 23;
le pagine 536 e 537 (paragrafo Chiusura transitiva di un grafo orientato)
dal testo [3].
135
136
Bibliografia
[1] E. Lodi, G. Pacini Introduzione alle strutture dati, Bollati
Boringhieri, 1990
[2] T.H. Cormen, C.E. Leiserson, R.L. Rivest, Introduzione agli
algoritmi, Vol. 1, Jackson Libri, 1995
[3] T.H. Cormen, C.E. Leiserson, R.L. Rivest, Introduzione agli
algoritmi, Vol. 2, Jackson Libri, 1995
[4] A.V. Aho, J.E Hopcroft, J.D. Ullman, Data structures and
algorithms, Addison Wesley, 1983
[5] A.A. Bertossi, Strutture, algoritmi, complessita, Ed. Culturali
Internazionali Genova, 1990
137