01 Algoritmi e Loro Implementazione Java
01 Algoritmi e Loro Implementazione Java
a.a. 2023/2024
ALGORITMI E LORO IMPLEMENTAZIONE IN JAVA:
Introduzione
Giovanna Melideo
Università degli Studi dell’Aquila
DISIM
Premessa
▪ Ricordiamo che ogni algoritmo che risolve un determinato
problema può essere tradotto in un programma scritto in un
linguaggio di programmazione e che tale programma verrà
trasformato in un processo a tempo di esecuzione
2
Algoritmo
▪ Da un punto di vista computazionale, un algoritmo è una
procedura che prende dei dati in input e, dopo averli
elaborati, restituisce dei dati in output
I dati devo essere organizzati e strutturati in modo tale
che la procedura che li elabora sia “parsimoniosa”
(efficiente)
Il concetto di algoritmo è inscindibile da quello di dato
3
Complessità computazionale
▪ L’esecuzione di un algoritmo su un dato input richiede
risorse di tempo e di spazio il cui ammontare prende
il nome di complessità computazionale
▪ Un consumo eccessivo di risorse può pregiudicare la
possibilità di utilizzo di un algoritmo
▪ È di fondamentale importanza saper trovare una
soluzione algoritmica efficiente e possibilmente ottimale,
a specifici problemi ben formalizzati.
4
Obiettivo
“Dato un problema trovare un algoritmo corretto e funzionante che
ne descriva il relativo procedimento risolutivo e codificarlo in un
determinato linguaggio di programmazione”
5
Ciclo di sviluppo di codice algoritmico
7
Fase progettuale: problema di ordinamento
9
Fase progettuale (2 di 5)
B. Si studia la difficoltà intrinseca del problema, ossia
la quantità minima di risorse di calcolo (tempo e
memoria di lavoro) di cui qualsiasi algoritmo ha
bisogno per risolvere una generica istanza del problema
dato.
Per molti problemi importanti non sono ancora noti limiti
inferiori precisi che ne caratterizzano la difficoltà intrinseca,
per cui non è ancora possibile stabilire se un algoritmo
risolutivo sia ottimo o meno
10
Fase progettuale (3 di 5)
C. Si progetta un algoritmo risolutivo, verificandone
formalmente la correttezza e stimandone le
prestazioni teoriche
• La complessità dell’algoritmo viene espressa in funzione
della dimensione n dell’istanza (analisi asintotica)
• Tra i vari algoritmi risolutivi, l’obiettivo è trovare l’algoritmo
che faccia il miglior uso possibile delle risorse di calcolo
disponibili (tempo di esecuzione ed occupazione di
memoria)
11
Fase progettuale (4 di 5)
▪ In grossi progetti software è fondamentale stimare le
prestazioni già a livello progettuale.
▪ Scoprire solo dopo la codifica che i requisiti prestazionali
non sono stati raggiunti potrebbe portare a conseguenze
disastrose o per lo meno molto costose.
12
Notazione asintotica
13
Fase progettuale (5 di 5)
▪ Il tempo/spazio di calcolo necessario alla risoluzione di un dato
problema (difficoltà intrinseca del problema): la quantità
minima di risorse di calcolo necessarie al caso peggiore per ogni
algoritmo che risolve una generica istanza del problema dato
▪ Il tempo/spazio di calcolo sufficiente alla risoluzione di un dato
problema: la quantità di risorse di calcolo necessarie al caso
peggiore ad uno specifico algoritmo che risolve una generica
istanza del problema dato
▪ Qualora la verifica della correttezza rilevi problemi o la stima delle
prestazioni risulti poco soddisfacente si torna al passo C (se non al
passo B…)
14
Fase realizzativa
▪ Si codifica l’algoritmo progettato in un linguaggio di
programmazione e lo si collauda per identificare eventuali errori
implementativi
▪ Si effettua un’analisi sperimentale del codice prodotto e se ne
studiano le prestazioni pratiche
▪ Si ingegnerizza il codice, migliorandone la struttura e l’efficienza
pratica attraverso opportuni accorgimenti
▪ Non è raro che l’analisi sperimentale fornisca suggerimenti utili
per ottenere algoritmi più efficienti anche a livello teorico.
15
Il problema dei duplicati (A)
Formulato come un problema di decisione
Input: una sequenza S di elementi qualsiasi S={s1, … , sn}
Output: true se esiste in S una coppia di elementi
duplicati (cioè esiste in S una coppia di indici distinti i, j ∈
{1, … ,n} tale che si = sj), false altrimenti.
16
Il problema dei duplicati (B)
▪ Difficoltà intrinseca del problema Ω(n): la
delimitazione inferiore banale di ogni algoritmo è
dell’ordine di grandezza di n (almeno la lettura dei dati
in ingresso)
17
Il problema dei duplicati (C)
▪ Analisi della correttezza e del tempo di esecuzione di
verificaDup per una generica istanza di dimensione n
(per n→∞)
18
verificaDup: correttezza
▪ L’algoritmo confronta almeno una volta ogni coppia di
elementi, per cui se esiste un elemento che si ripete in S
verrà sicuramente trovato.
19
verificaDup: complessità (1 di 3)
20
verificaDup: complessità (2 di 3)
22
Il problema dei duplicati (C-2)
Idea nuovo algoritmo:
▪ Ordinare la sequenza (sarà argomento del corso!)
• θ(n·log n), ordine di grandezza pseudo-polinomiale
▪ Cercare due elementi duplicati consecutivi
• O(n) nel c.p., ordine di grandezza lineare
▪ tempo di esecuzione complessivo: O(n·log n) nel c.p.
23
Il problema dei duplicati (C-2 continua)
Algoritmo verificaDupOrd (sequenza S)
ordina S in modo non-decrescente
for each elemento x della sequenza ordinata S,
tranne l’ultimo do
sia y l’ elemento che segue x in S
do if x=y then return true
return false
24
T(n)=O(n·log n) vs T(n)=O(n2)
n n log(n) n^2
10 33,22 100
100 664,39 10000
1000 9965,78 1000000
10000 132877,12 100000000
100000 1660964,05 10000000000
1000000 19931568,57 1000000000000
10000000 232534966,64 100000000000000
100000000 2657542475,91 10000000000000000
1000000000 29897352853,99 1000000000000000000
10000000000 332192809488,74 100000000000000000000
100000000000 3654120904376,10 10000000000000000000000
1000000000000 39863137138648,40 1000000000000000000000000
10000000000000 431850652335357,00 100000000000000000000000000
100000000000000 4650699332842310,00 10000000000000000000000000000
1000000000000000 49828921423310400,00 1000000000000000000000000000000
10000000000000000 531508495181978000,00 100000000000000000000000000000000
25
Altre funzioni a confronto
26
Misura delle prestazioni – cenni (1 di 4)
▪ Tempo di CPU relativo ad un programma: tempo effettivo
durante il quale la CPU lavora su quel programma
• Non comprende i tempi per l’accesso al disco, l’input/output,
• Non comprende il tempo speso dalla CPU per altri programmi
gestiti in contemporanea
▪ La velocità o frequenza di clock della CPU indica il numero
di operazioni elementari che la CPU è in grado di eseguire in
un secondo e si misura in Hertz
fCLOCK = # operazioni_elementari / tempo [Hertz]
• Giga Hertz: 1GHz = 109Hz = 109 cicli/s
27
Misura delle prestazioni – cenni (2 di 4)
Ordini di grandezza:
▪ La velocità di clock del primo
microprocessore della storia,
l’Intel 4004, era di 740 KHz
▪ Le CPU dei computer
moderni raggiungono i 5
GHz.
28
132.877
29
Misura delle prestazioni – cenni (4 di 4)
▪ Se un elaboratore esegue ~109 operazioni/sec:
N Time (N) Time (N2) Time (N3) Time (2N)
50 … 25⋅10-7= 2,5 125⋅10-6 = > 106 sec > 10 gg
µs 125 µs
100 10-7 sec= 0,1 10-5 sec= 10 10-3 sec = 1 > 1021 sec ~ 1016 gg >
µs µs ms 1013 anni
30
Il problema dei duplicati: realizzazione
▪ Fase realizzativa: Alcune scelte, se non ben ponderate,
potrebbero avere un impatto cruciale sui tempi di esecuzione
▪ Implementazione dell’algoritmo verificaDup mediante
liste: S è rappresentata tramite un oggetto della classe
LinkedList che implementa l’interfaccia java.util.List
fornita come parte del Java Collections Framework
▪ Il metodo get() consente l’accesso agli elementi di S in base
alla loro posizione nella lista.
31
Implementazione: verificaDupList
public static boolean verificaDupList (LinkedList S){
for (int i=0; i<S.size(); i++){
Object x=S.get(i);
for (int j=i+1; j<S.size(); j++){
Object y=S.get(j);
if (x.equals(y)) return true;
}
}
return false;
}
32
Implementazione basata su ordinamento
▪ Utilizziamo anche la classe java.util.Collections, che
fornisce metodi statici che operano su collezioni di oggetti
▪ In particolare fornisce il metodo sort, che si basa su una
variante dell’algoritmo mergesort
33
Implementazione: verificaDupOrdList
public static boolean verificaDupOrdList (LinkedList S){
Collections.sort(S);
for (int i=0; i<S.size()-1; i++)
if (S.get(i).equals(S.get(i+1))) return true;
return false;
}
34
Collaudo e analisi sperimentale (1 di 7)
▪ L’implementazione di un algoritmo va collaudata in modo
da identificare eventuali errori implementativi, ed
analizzata sperimentalmente, possibilmente su dati di
test reali
▪ L’analisi sperimentale delle prestazioni va condotta
seguendo una corretta metodologia per evitare
conclusioni errate o fuorvianti
35
Collaudo e analisi sperimentale (2 di 7)
Obiettivi dell’analisi sperimentale:
▪ Come raffinamento dell’analisi teorica o in sostituzione dell’analisi teorica
quando questa non può essere condotta con sufficiente accuratezza
▪ Per effettuare un confronto più preciso tra algoritmi apparentemente simili
(stimare quali sono le costanti nascoste dalla notazione asintotica)
▪ Per studiare le prestazioni su dati di test derivanti da applicazioni pratiche o da
scenari di caso peggiore. Spesso si ottengono risultati sorprendenti che la cui
spiegazione consente di raffinare e migliorare l’analisi teorica
▪ Se un risultato sembra in contraddizione con l’analisi teorica può essere utile
condurre ulteriori esperimenti
36
Collaudo e analisi sperimentale (3 di 7)
▪ Misurazione dei tempi (a scopo didattico in base all’orologio di
sistema e basato sul clock del processore): un aspetto cruciale è la
granularità delle funzioni di sistema usate per misurare i tempi. Se i
tempi di esecuzione sono troppo bassi per ottenere stime significative,
basta misurare il tempo totale di una serie di esecuzioni identiche dello
stesso codice e dividere il tempo totale per il numero di esecuzioni
▪ Usiamo il metodo java.lang.System.nanoTime() che fornisce un
valore di tipo long (nanosecondi) per prendere i tempi prima e dopo
l’esecuzione secondo il seguente schema:
long tempoInizio = System.nanoTime();
[porzione di codice da misurare]
long tempo=System.nanoTime()- tempoInizio;
37
Collaudo e analisi sperimentale (4 di 7)
▪ Siamo interessati alla relazione generale esistente tra tempo di
esecuzione e la dimensione dei dati da elaborare
▪ Si eseguono esperimenti indipendenti con molti diversi dati in
ingresso di diverse dimensioni
▪ Si visualizzano i risultati dell’esecuzione sotto forma di grafico
cartesiano dove la coordinata x rappresenta la dimensione n dei
dati in ingresso e la coordinata y il tempo di esecuzione t
▪ Il grafico ottenuto consente spesso di intuire la relazione esistente
tra la dimensione del problema ed il tempo di esecuzione
dell’algoritmo che lo risolve
38
Collaudo e analisi sperimentale (5 di 7)
▪ Un’analisi sperimentale condotta su sequenze di numeri interi
distinti generati in modo casuale ha evidenziato il vantaggio
derivante dal progetto di algoritmi efficienti:
• verificaDupOrdList molto più efficiente di verificaDupList
40
Collaudo e analisi sperimentale (7 di 7)
▪ Dunque il tempo di esecuzione di verificaDupList diventa
proporzionale a n3 , cioè:
Σi=1..n(i+Σj=(i+1)..n j)=O(n3)
▪ Vedremo che è possibile migliorare l’implementazione (tempo di
esecuzione quadratico O(n2) ) !
▪ Discorso analogo vale per il metodo verificaDupOrdList.
41
Messa a punto e ingegnerizzazione
▪ Richiede in particolare di decidere l’organizzazione e la
modalità di accesso ai dati
▪ In riferimento al nostro esempio, dove la sequenza S è
rappresentata mediante un oggetto LinkdList, l’uso
incauto del metodo get() ha reso le implementazioni
inefficienti
▪ Eliminare questa fonte di inefficienza: convertire la lista
in array!
42
Implementazione: verificaDupArray
public static boolean verificaDupArray (List S){
Object[] T = S.toArray();
for (int i=0; i<T.length(); i++){
Object x=T[i];
for (int j=i+1; j<T.length; j++){
Object y=T[j];
if (x.equals(y)) return true;
}
}
return false;
}
43
Implementazione: verificaDupOrdArray
public static boolean verificaDupOrdArray (List S){
Object[] T = S.toArray();
Arrays.sort(T);
for (int i=0; i<T.length(); i++){
if (T[i].equals(T[i+1])) return true;
}
}
return false;
}
▪ I tempi di esecuzione in questo caso sono perfettamente allineati con la
predizione teorica!
44
Domande?
Giovanna Melideo
Università degli Studi dell’Aquila
DISIM