Introduzione
Per ogni problema esistono molteplici algoritmi in
grado di risolverlo
Esempio:
Ricerca di un numero in un insieme di numeri
Ricerca sequenziale
Ricerca dicotomica
Questi due algoritmi risolvono lo stesso problema ma
uno è migliore dell’altro.
Definizioni
Un problema è costituito da:
Una descrizione generale in cui sono presenti i
parametri che possono variare (variabili)
Una questione cioè un insieme di proprietà che la
soluzione del problema deve soddisfare.
Istanza di un problema
L’istanza di un problema è una possibile formulazione
del problema quando alle sue variabili sono stati
assegnati valori particolari.
Per ogni problema esistono infinite istanze.
Esempio
Determinare se un numero è primo
Una possibile istanza del problema potrebbe essere:
I 1 = 5, I2 ? 27, …., In=2394892348
n – dimensione di un’istanza
Se indichiamo con n la dimensione di un’istanza,
questa raggruppa tutti gli input che hanno la stessa
dimensione
La indicheremo come cardinalità n dei dati in
ingresso.
Per ogni istanza del problema, l’algoritmo
risolutivo deve fornire la soluzione
indipendentemente dai valori, dal tipo o dalla
cardinalità delle variabili.
Definizione – Algoritmo risolutore
Un algoritmo A risolve un problema p se applicato
a ogni istanza I produce la soluzione corretta.
Algoritmo risolutore
La generalità dell’algoritmo, propria della definizione
di algoritmo, ci permette di affermare che la
risoluzione del problema implica la risoluzione di
tutte le sue istanze, quindi deve fornire il corretto
risultato al variare di tutti i dati in ingresso.
Individuare la complessità
computazionale
Il nostro obiettivo è individuare la complessità
computazionale indipendentemente dalla cardinalità
n e dalla tipologia x dei dati in ingresso.
Parametri di qualità di un
algoritmo
Scrivere un algoritmo non è sempre semplice.
Può capitare di scrivere una soluzione ma notare fin da
subito che ne avremmo potuto trovare una migliore.
Parametri di qualità di un
algoritmo
Per definire la qualità degli algoritmi dobbiamo anche
tenere presente com’è stato descritto un algoritmo e
cioè:
La semplicità di comprensione/modificabilità del codice
sorgente
La qualità di scrittura del codice del programma
L’accessibilità per l’utilizzo del programma
La bellezza grafica dell’interfaccia utente
ecc
Parametri di qualità di un
algoritmo
Non è possibile utilizzare parametri soggettivi o
qualitativi per classificare gli algoritmi.
È necessario scegliere parametri oggettivi così da
ottenere un criterio di classificazione universale
Criteri di efficienza
Complessità temporale: tempo impiegato
dall’esecuzione dell’algoritmo
Complessità spaziale: dimensione del programma,
occupazione della memoria
Complessità di I/O: tempo impiegato per
l’acquisizione o trasferimenti di dati tra periferiche
Complessità di trasmissione: efficienza di I/O di un
algoritmo rispetto a “stazioni remote”.
Criteri di efficienza
Tempo di elaborazione e quantità di memoria sono i
parametri più utilizzati.
Per effettuare la classificazione degli algoritmi
utilizzeremo la complessità tempoale
Complessità temporale
Studiare la complessità di tempo di un algoritmo
significa stimare la quantità di tempo necessaria
affinché la CPU esegua il corrispondente
programma.
La complessità temporale è un ottimo parametri per
scegliere il migliore tra due o più algoritmi che
risolvono lo stesso problema e costituirà la base per
poter passare dalla complessità di calcolo di un
algoritmo A alla complessità di calcolo del problema p.
Modello di costo per il calcolo del
tempo di esecuzione
Possiamo calcolare re il tempo di esecuzione di un
algoritmo cronometrandone l’esecuzione su alcune
istanze e riportando i risultati in una tabella.
Esempio
Elenchiamo in secondi il tempo di esecuzione di quattro
programmi che implementano rispettivamente due
algoritmi A1 e A2 che risolvono lo stesso problema,
mandandoli in esecuzione su due architetture hardware.
Computer 1
Computer 2
Dim. input
A1
A2
A1
A2
50
0,005
0,07
0.05
0.25
100
0,003
0,13
0,18
0,55
200
0,13
0,27
0,73
1,18
300
0,32
0,42
1,65
1,85
400
0,55
0,57
2,93
2,67
500
0,67
0,72
4,60
3,28
1000
3,57
1,60
18,32
7,03
Esempio
Possiamo osservare come non sia molto significativo
misurare il tempo in secondi
I tempi sono fortemente influenzati dal compilatore,
dall’architettura e dalle caratteristiche hardware del
computer, dai dati in ingresso.
Modello di costo per il calcolo per il
calcolo del tempo di esecuzione
Non è possibile esprimere la complessità temporale
intrinseca di un algoritmo utilizzando come unità di
misura i secondi.
Modello di costo per il calcolo per il
calcolo del tempo di esecuzione
È necessario svincolarsi dalla tecnologia
La valutazione non deve dipendere da una
particolare macchina o da un particolare
compilatore.
Per individuare un parametro indipendente da questi
fattori non analizzeremo il programma ma
l’algoritmo.
Modello di costo per il calcolo per il
calcolo del tempo di esecuzione
Effettueremo l’analisi della complessità
computazionale dell’algoritmo e stabiliremo una
classificazione degli algoritmi definendo non il
tempo di esecuzione ma l’ordine di grandezza del
numero di istruzioni eseguite tenendo conto dei dati
in input e della loro dimensione
Esempio
Supponiamo di avere due algoritmi diversi per
ordinare n numeri interi
Il primo impiega n2 istruzioni
Il secondo impiega nlog(n) istruzioni
Supponiamo che ogni istruzione venga eseguita in un
µsec e osserviamo i tempi di esecuzione al variare della
dimensione del problema /per esempio dimensione
del vettore) e quindi il numero di istruzioni eseguite
Esempio
Numero di
istruzioni
n=10
n=1000
n=106
n2
0,0001 sec
100 sec
106 sec (12 gg ca)
nlog(n)
0,00003 sec
0,13 sec
19 sec
Osserviamo come per i piccoli valori i tempi siano
simili mentre al variare della dimensione del
problema i comportamenti siano divergenti
Considerazioni
Non è importante tanto conoscere il tempo di
esecuzione di un’istanza quanto il suo comportamento
approssimato, cioè l’ordine di grandezza con il quale
un algoritmo si comporta al variare della dimensione
del problema complessità asintotica.
Non occorre calcolare un valore ma “la tendenza” cioè
l’andamento che un algoritmo assume all’aumentare
della dimensione del problema nel caso peggiore dei
dati in ingresso.
Caso migliore, peggiore e medio
La tipologia di dati in input influenza inevitabilmente
il tempo di calcolo poiché a parità di dimensione
incidono notevolmente sul numero di istruzioni che
vengono eseguite.
Caso migliore, peggiore e medio
Esempio:
Considerando l’ordinamento di un vettore tramite un
algoritmo di bubble sort con sentinella dove possiamo
individuare due casi estremi:
Vettore già ordinato numero di operazioni minimo
Vettore completamente disordinato numero di operazioni
massimo
Siamo in presenza di due casi estremi: caso migliore e
caso peggiore.
Le condizioni normali sono il caso intermedio ma per
confrontare gli algoritmi indipendentemente dai dati è più
significativo considerare sempre il caso peggiore.
Calcolo della complessità in
funzione del caso base
La dimensione di un problema incide pesantemente
nel calcolo del tempo di esecuzione di un algoritmo
È necessario esprimere il tempo di calcolo di un
algoritmo A mediante una funzione T(n) dove n è la
dimensione del problema (dati in ingresso).
Funzione T(n): esprime il tempo necessario affinché
l’algoritmo A possa produrre la soluzione di un’istanza
di dimensione n.
Chi è n?
Nel caso di vettori o matrici n è la dimensione del
vettore.
Nel caso di stringhe è la dimensione di una stringa
Etc
In caso di algoritmi complessi non è semplice
individuare il parametro.
Inoltre utilizziamo sempre il caso peggiore.
Tp(n)= max{T(x), per ogni x istanza del problema}
Criteri per l’individuazione del
tempo di esecuzione
Definiamo il tempo di esecuzione delle istruzioni
elementari introducendo il costo unitario
Istruzione a costo unitario: operazione (statement)
la cui esecuzione non dipende né dal valore né dal tipo
di variabili e prende il nome di passo base.
Istruzioni elementari a costo
unitario
Assegnamento
Operazioni di I/O (solo da tastiera e non da file)
Operazioni aritmetiche elementari
Valutazione di espressioni booleane
Accesso a elementi di un array
Assunto che:
Si acceda a qualsiasi cella della RAM con un costo uniforme
Le istruzioni vengano svolte una volta senza grado di
parallelismo
Indichiamo con C una costante numerica: il costo del test
di un’operazione booleana composta da più condizioni
booleane semplici sarà sempre minore o uguale a C volte il
costo del test di una condizione semplice costo unitario
Analogamente per le condizioni aritmetiche composte:
un’espressione con molteplici operazioni ha sempre un
costo inferiore a C volte una singola operazione aritmetica.
Non consideriamo gli accessi a file.
Esempio
Esempio
Esempio
Esempio
Esempio
Il costo potrebbe dipendere dai
valori in ingresso..
Considerazioni
Se estendiamo il procedimento a un intero programma
otteniamo comunque un’espressione matematica che
esprime la complessità in termini di passi base
Questa espressione può essere un polinomio oppure,
nel caso generale, una funzione che può risultare
complessa da ricavare.
Se abbiamo un programma strutturato in composto da
procedure e funzioni si deve:
Calcolare la complessità di ogni procedura/funzione
Per ogni chiamata di procedura/funzione aggiungere la
complessità della stessa al costo globale del programma.
Esercizi
i = 1;
k = 2;
while (i<=n) {
i = i+1;
k = k*1;
printf(k);
}
i = 1;
while (i<=n) {
i = i+1;
printf(i);
}
k = 1;
while(k<=m)
k = k+1;
Esercizi
i = 1;
k = 2;
while (i<=n) {
i = i+1;
k = k*1;
printf(k);
}
1 + 1 + (n+1) + n (1+1+1)
4n + 3
i = 1;
while (i<=n) {
i = i+1;
printf(i);
}
k = 1;
while(k<=m)
k = k+1;
1 + (n+1) + n(1+1) + 1 +
m+1 + m(1)=
3n + 2m +4
Esercizi
i = 1;
k = 1;
while (i <= n) {
i = i+1;
printf(i);
while(k<=m) {
k = k+1;
}
}
Esercizi
i = 1;
k = 1;
while (i <= n) {
i = i+1;
printf(i);
while(k<=m) {
k = k+1;
}
}
1+
1+
n+1 +
n(
1+
1+
n+1+
n(1)
)
= 2n2+4n+3
Confronto tra algoritmi
Dopo aver espresso la complessità dei programmi in
termini di passi base ora vogliamo effettuare il
confronto tra algoritmi
Confronto tra algoritmi
Supponiamo di avere, per uno stesso problema, sette
algoritmi diversi con diversa complessità.
Per poterli confrontare meglio traduciamo la
complessità in tempo effettivo di elaborazione
supponendo che per un passo base occorra un
microsecondo (10-6 sec) e riportiamo i risultati nella
tabella per quattro istanze con dimensioni di dati di
input diverse
Confronto tra algoritmi
I primi tre algoritmi hanno complessità descritta da un
polinomio di primo grado
Il quarto e il quinto hanno una complessità descritta da un
polinomio di secondo grado
Il sesto polinomio ha una complessità descritta da un
polinomio di terzo grado
Il settimo polinomio ha una complessità descritta da una
funzione esponenziale.
Confronto tra algoritmi
Osserviamo che per piccoli valori della dimensione n del
problema (1° colonna) tutti gli algoritmi hanno tempi di
risposta non significativamente differenti.
Già con n = 100 l’algoritmo di capacità esponenziale si
differenzia in maniera considerevole
Per dimensioni di input di grandi dimensioni (n=106) i sette
algoritmi si partizionano in cinque classi.
Algoritmo √n frazioni di secondo
Algoritmo n+5, 2*n secondi
Algoritmi n2,n2+n -_> giorni
Algoritmo n3 secoli
Algoritmo 2n ?
Confronto tra algoritmi
Possiamo dunque affermare che all’aumentare delle
dimensioni dei dati in ingresso di un dato problema
risulta determinante solo l’ordine di grandezza con
cui le funzioni crescono al crescere di n.
Concetto di complessità asintotica
Gli O grandi
È un criterio matematico per partizionare gli algoritmi in
classi di complessità
Si dice che una funzione f(n) è di ordine g(n) e si scrive:
f(n) = O(g(n))
se esiste una costante numerica C positiva tale che valga, salvo al
più per un numero finito di valori di n, la seguente condizione:
f(n) ≤ C*g(n)
Se g(n) non è identicamente nulla (fatto assolutamente certo
nell’esecuzione di un algoritmo), la condizione precedente può
essere espressa come:
f(n) / g(n) ≤ C
ovvero la funzione f(n)/g(n) è limitata
Esempi
Tempo 3*n2+2*n+4 = O(n2)
dato che 3*n2 + 2n + 4 ≤ 4*n2 per n > 3
Tempo 4*n3 + 5logn + 8 = O(n3)
dato che 4*n3 + 5logn+8 ≤ 5*n3 per n > 2
Tempo 2n + 7n = O(2n)
Dato che 2n + 7n ≤ 2* 2n per n>4
Termine prevalente
Dato che a noi interessa il comportamento
asintotico ovvero con n tendente ad infinito non è
necessario soffermarsi a individuare i vari valori
Basta soffermarsi a studiare il termine prevalente.
Se abbiamo una generica funzione di n possiamo
trascurare sia eventuali fattori moltiplicativi sia
eventuali termini addizionati e considerare solo il
termine prevalente.
Complessità asintotica
Si dice che f(n) ha complessità asintotica g(n) se
valgono le seguenti condizioni:
f(n) = O(g(n))
g(n) è la più piccola di tutte le funzioni che soddisfano la
prima condizione
Complessità asintotica
Esempi:
Dalla complessità in passi base alla
complessità asintotica
In pratica, la complessità asintotica è definita dal blocco
di complessità maggiore
Non contano le costanti, né additive, né moltiplicative
Andamento caratteristico
Algebra degli O grandi
In precedenza, abbiamo visto degli esempi di calcolo
della complessità in numero di passi base per
programmi a blocchi
Definiamo ora, tramite l’algebra degli «O grandi», un
criterio per il calcolo della complessità asintotica di
un programma strutturato
In un programma strutturato a blocchi si possono
presentare due situazioni:
blocchi in sequenza
blocchi annidati
Termine di complessità maggiore
Nel caso di una funzione composta da diversi termini
la complessità asintotica è definita dal termine di
complessità maggiore
Inoltre non contano le costanti, né additive, né
moltiplicative, ma solo l’ordine di grandezza del
termine più significativo.
Teorema della somma
La complessità di un blocco costituito da più blocchi in
sequenza è quella del blocco di complessità
maggiore.
Teorema della somma - esempio
Teorema del prodotto
La complessità di un blocco costituito da più blocchi
annidati è data dal prodotto delle complessità dei
blocchi componenti (applicazione del teorema del
prodotto).
Teorema del prodotto - esempio
Calcolare la complessità di un
qualsiasi algoritmo strutturato
Con il concetto di «O grande» e le due operazioni
definite dall’algebra degli «O grandi» si può calcolare
la complessità di qualsiasi algoritmo strutturato senza
conoscerne preventivamente la complessità in passi
base
Esempio
Classi di complessità degli algoritmi
Classi di complessità degli algoritmi
Classi di complessità degli algoritmi