Complessità - Dipartimento di Informatica e Automazione

Fondamenti di informatica
un approccio a oggetti con Java
Luca Cabibbo
Complessità
Capitolo 22
Marzo 2006
1
Complessità
Fondamenti di informatica
Contenuti
‹
2
Complessità dei metodi
„ determinazione sperimentale della complessità
„ analisi di complessità
„ analisi asintotica
„ operazione dominante
‹
Complessità degli algoritmi e dei problemi
‹
Giustificazione dell’analisi asintotica
‹
Discussione
Complessità
Fondamenti di informatica
Introduzione
Una importante qualità del software è l’efficienza
„ un programma è tanto più efficiente quanto minori sono le
risorse di calcolo necessarie per la sua esecuzione
„ le risorse di calcolo usate da un programma sono, tra
l’altro, il processore, le memorie (centrale e secondarie), la
rete di calcolatori
Questo capitolo studia il problema determinare l’efficienza dei
metodi rispetto all’impiego della risorsa processore
„ l’efficienza di un metodo viene espressa mediante una
funzione che descrive il costo computazionale del metodo,
ovvero il numero di “operazioni elementari” eseguite durante
l’esecuzione del metodo, contato rispetto alla dimensione dei
dati di ingresso del metodo
„ il costo computazionale di un metodo è una stima
(approssimata) della funzione che descrive il tempo di
esecuzione del metodo rispetto ai dati di ingresso
3
Complessità
Fondamenti di informatica
Complessità dei metodi
Un metodo è tanto più efficiente quanto minori sono le risorse di
calcolo necessarie per la sua esecuzione
„ la risorsa di calcolo su cui viene concentrata l’attenzione è il
processore, il cui uso è misurato dal tempo impiegato
nell’esecuzione del metodo
Il tempo di esecuzione di un metodo viene considerato un costo
speso nella sua esecuzione, chiamato costo computazionale o
complessità computazionale del metodo
„ intuitivamente, l’analisi di complessità di un metodo M ha lo
scopo di determinare una funzione tM(I), chiamata funzione di
costo del metodo, che descrive il tempo di esecuzione di M in
modo parametrico rispetto all’insieme I di dati di ingresso
„ le funzioni di costo vengono normalmente determinate in
modo approssimato, per diverse ragioni
4
Complessità
Fondamenti di informatica
Obiettivi della determinazione della complessità
Prima di studiare come determinare la complessità
computazionale dei metodi, è importante comprendere le finalità
della funzione di costo
„ la funzione di costo dei metodi è significativa in senso
assoluto
„ conoscendo la funzione di costo di un metodo è possibile
capire se il tempo di esecuzione di un metodo su certi dati
di ingresso è accettabile (perché limitato superiormente da
un periodo di tempo ragionevolmente piccolo) oppure non
accettabile (perché limitato inferiormente da un periodo di
tempo molto grande)
„ la funzione di costo dei metodi è significativa anche in senso
relativo
„ sulla base della complessità, è possibile ad esempio
decidere quale metodo scegliere per risolvere un
problema, tra due o più metodi che risolvono quel
problema
5
Complessità
Fondamenti di informatica
Determinazione della complessità
Come è possibile determinare la complessità computazionale di
un metodo?
„ nell’approccio sperimentale il tempo di esecuzione di un
metodo viene misurato con un cronometro
„ l’analisi di complessità ha invece l’obiettivo di determinare la
funzione di costo dei metodi in modo analitico (e
approssimato), superando alcuni dei limiti dell’approccio
sperimentale
6
Complessità
Fondamenti di informatica
Determinazione sperimentale della complessità
Prima di introdurre l’analisi di complessità (una tecnica analitica
per la valutazione della complessità) viene discussa la possibilità
di misurare sperimentalmente la complessità dei metodi
„ vengono anche illustrati i limiti e le problematiche nella
determinazione sperimentale della complessità, che
giustificano le ipotesi che vengono fatte nell’analisi di
complessità
La complessità temporale di un metodo può essere determinata in
modo sperimentale eseguendo più volte il metodo, usando ogni
volta un diverso insieme di dati di ingresso, e misurando con un
cronometro il tempo impiegato per ciascuna esecuzione del
metodo
7
Complessità
Fondamenti di informatica
Dimensione dei dati di ingresso
In generale, la funzione di costo di un metodo non viene espressa
in modo parametrico rispetto all’insieme di dati di ingresso, ma
piuttosto in modo parametrico rispetto alla dimensione dei dati di
ingresso
„ la dimensione di un insieme di dati è una misura dello spazio
di memoria necessario a memorizzare quell’insieme di dati
„ ad esempio, la complessità di un metodo che calcola la
somma o il massimo tra gli elementi di un array (passato
come parametro) può essere ragionevolmente descritta in
funzione della lunghezza dell’array, trascurando invece il
valore dei singoli elementi dell’array
8
Complessità
Fondamenti di informatica
Determinazione mediante un cronometro
Il risultato della determinazione sperimentale della complessità di
un metodo può essere visualizzato disegnando un grafico in cui
ciascun punto descrive una diversa esecuzione del metodo
„ l’ascissa del grafico indica la dimensione N dell’insieme dei
dati di ingresso (un numero naturale)
„ l’ordinata del grafico indica il tempo di esecuzione (ad
esempio, in millisecondi)
Esecuzione del metodo M
t (ms)
160
140
120
100
80
60
40
20
0
N
0
„
9
20
40
60
80
100
120
140
160
nel grafico, un punto di coordinate (N,T) indica che il tempo
dell’esecuzione del metodo per un insieme di dati di ingresso
di dimensione N è stato T
Complessità
Fondamenti di informatica
Problemi della misurazione sperimentale
La determinazione sperimentale della complessità di un metodo
presenta un certo numero di problemi
„ la determinazione sperimentale è poco oggettiva, perché
dipende dal calcolatore utilizzato
„ il tempo di esecuzione di un metodo dipende, oltre che
dall’insieme di dati di ingresso, anche dalla configurazione
hardware/software del calcolatore utilizzato, nonché dal
carico del calcolatore al momento della sperimentazione
„ a parità di configurazione e carico del calcolatore, il tempo di
esecuzione di un metodo non dipende solo dalla dimensione
dei dati di ingresso, ma anche dal particolare valore dei dati di
ingresso
„ la misurazione sperimentale della complessità di un metodo è
possibile solo se
„ il metodo è stato completamente implementato
„ si ha un calcolatore a disposizione per eseguire il metodo
10
Complessità
Fondamenti di informatica
Dipendenza dall’insieme dei dati di ingresso
Un problema relativo al voler esprimere il tempo di esecuzione di
un metodo rispetto alla dimensione dei dati di ingresso, è che in
realtà il tempo di esecuzione non dipende solo dalla dimensione
dei dati di ingresso, ma anche dal particolare valore dei dati di
ingresso
„ infatti, il grafico può contenere più punti relativi a insiemi di
dati di ingresso che hanno la stessa stessa dimensione ma per
cui il tempo di esecuzione è diverso
Esecuzione del metodo M
t (ms)
160
140
120
100
80
60
40
20
0
N
0
11
20
40
60
80
100
Complessità
120
140
esperimenti
relativi a due
insiemi di dati
di ingresso che
hanno la stessa
dimensione
160
Fondamenti di informatica
Dipendenza dal calcolatore utilizzato
Un problema della misurazione sperimentale della complessità è
che l’esecuzione dello stesso metodo su calcolatori differenti può
richiedere tempi di esecuzione diversi, a causa delle diverse
configurazioni dei calcolatori utilizzati (nonché del loro carico)
t (ms) Esecuzione del metodo M sul calcolatore A
160
140
120
100
80
60
40
20
0
140 ms
N
0
20
40
60
80
100
120
140
160
stesso insieme
di dati di
ingresso
t (ms) Esecuzione del metodo M sul calcolatore B
160
140
120
100
80
60
40
20
0
100 ms
N
0
12
20
40
60
80
100
120
Complessità
140
160
Fondamenti di informatica
Analisi di complessità
Per superare i limiti e i problemi della determinazione
sperimentale della complessità dei metodi, viene adottato un
approccio analitico, chiamato analisi di complessità, per
esprimere la funzione di costo dei metodi in modo
„ indipendente
„ dal particolare calcolatore
„ dalla particolare scelta dei dati di ingresso
„ ma (per quanto possibile) dipendente
„ dalla dimensione dei dati di ingresso
13
Complessità
Fondamenti di informatica
Operazioni elementari e costo computazionale
Nell’analisi di complessità, il costo computazionale di un metodo
è dato dal numero di “operazioni elementari” eseguite durante
dell’esecuzione del metodo, espresso in funzione della
dimensione dei dati di ingresso per il metodo
„ il modello di costo più semplice è quello in cui sono
considerate “elementari” le seguenti operazioni
„ ogni istruzione semplice
„ la valutazione di una condizione
„ per semplicità, per il momento viene trascurata la possibilità
di invocare metodi e costruttori
14
Complessità
Fondamenti di informatica
Esempio — somma degli elementi di un array
Determinare, usando il semplice modello di costo proposto, la
funzione di costo del seguente metodo che calcola la somma
degli elementi di un array dati di interi
/* Calcola la somma degli elementi di dati. */
public static int somma(int[] dati) {
// pre: dati!=null
int n;
// lunghezza di dati
int s;
// somma degli elementi di dati
int i;
// indice per la scansione di dati
/* calcola la somma degli elementi di dati */
n = dati.length;
s = 0;
for (i=0; i<n; i++)
s += dati[i];
return s;
}
15
Complessità
Fondamenti di informatica
Dimensione dei dati di ingresso
/* Calcola la somma degli elementi di dati. */
public static int somma(int[] dati) { ... }
L’insieme dei dati di ingresso per il metodo somma è costituito
dall’array dati di interi
„ la dimensione di questo insieme di dati può essere
quantificata dal numero di elementi dell’array dati, ovvero
dalla lunghezza di dati
Nell’analisi di complessità, la dimensione dell’insieme dei dati di
ingresso dei metodi viene solitamente espressa mediante un
numero naturale indicato dal simbolo N, opportunamente
accompagnato da una descrizione della sua interpretazione
„ ad esempio, in questo caso si vuole determinare la funzione
di costo del metodo somma rispetto alla lunghezza N
dell’array dati
16
Complessità
Fondamenti di informatica
Funzione di costo
/* Calcola la somma degli elementi di dati. */
public static int somma(int[] dati) { ... }
Lo scopo dell’analisi di complessità di un metodo è determinare la
funzione di costo del metodo rispetto alla dimensione dei dati di
ingresso del metodo
„ in questo caso, bisogna determinare la funzione tsomma(N) che
esprime il costo computazione dell’esecuzione del metodo
somma rispetto alla lunghezza N dell’array dati
„ in generale, per un particolare valore N, tsomma(N) indica il
numero unità di costo spese durante l’esecuzione di somma
quando dati ha lunghezza N
„ nel modello di costo considerato, ciascuna unità di costo
corrisponde all’esecuzione di una operazione elementare
„ dato che si sta considerando la complessità temporale dei
metodi, per un particolare valore N, il numero tsomma(N) è da
considerarsi commisurato al tempo di esecuzione di somma
quando dati ha lunghezza N
17
Complessità
Fondamenti di informatica
Determinazione della funzione di costo
Quante operazioni elementari vengono eseguite nell’esecuzione
del metodo somma quando dati ha lunghezza N?
/* Calcola la somma degli elementi di dati. */
public static int somma(int[] dati) {
// pre: dati!=null
int n;
// lunghezza di dati
int s;
// somma degli elementi di dati
int i;
// indice per la scansione di dati
/* calcola la somma degli elementi di dati */
n = dati.length;
s = 0;
for (i=0; i<n; i++)
s += dati[i];
return s;
}
„
18
bisogna identificare le operazioni elementari nel corpo del
metodo, e contare quante volte ciascuna operazione
elementare venga eseguita (in funzione di N)
Complessità
Fondamenti di informatica
Determinazione della funzione di costo
Quante operazioni elementari vengono eseguite nell’esecuzione
del metodo somma quando dati ha lunghezza N?
„ n = dati.length è una operazione elementare
„ viene eseguita una volta
„ s = 0 è una operazione elementare
„ viene eseguita una volta
„ return s è una operazione elementare
„ viene eseguita una volta
„ rimane da determinare il costo computazione dell’esecuzione
dell’istruzione ripetitiva
19
Complessità
Fondamenti di informatica
Determinazione della funzione di costo
In generale, relativamente a una esecuzione di una istruzione
ripetitiva for
„ l’inizializzazione viene eseguita una sola volta
„ se il corpo viene eseguito N volte, allora la condizione viene
valutata N+1 volte e l’incremento viene eseguito N volte
Relativamente all’istruzione ripetitiva del metodo somma
„ se dati ha lunghezza N, allora il corpo dell’istruzione
ripetitiva for viene eseguito N volte
„ i = 0 è una operazione elementare
„ viene eseguita una volta
„ i < n è una operazione elementare
„ viene eseguita N+1 volte
„ i++ è una operazione elementare
„ viene eseguita N volte
„ s += dati[i] è una operazione elementare
„ viene eseguita N volte
20
Complessità
Fondamenti di informatica
Determinazione della funzione di costo
/* Calcola la somma degli elementi di dati. */
public static int somma(int[] dati) {
// pre: dati!=null
int n;
// lunghezza di dati
int s;
// somma degli elementi di dati
int i;
// indice per la scansione di dati
/* calcola la somma degli elementi di dati */
n = dati.length;
s = 0;
for (i=0; i<n; i++)
s += dati[i];
return s;
}
Complessivamente, la funzione di costo tsomma(N) è data da
tsomma(N) = 1 + 1 + 1 + (N+1) + N + N + 1 = 3N+5
costo di
costo di costo di
n = dati.length s = 0
i=0
21
costo di
i<N
Complessità
costo di
costo di
i++
s += dati[i]
costo di
return s
Fondamenti di informatica
Discussione
La funzione di costo di un metodo descrive il costo di esecuzione
del metodo in termini di generiche “unità di costo,” corrispondenti
al costo (temporale) di esecuzione delle operazioni elementari
„ ad esempio, la funzione di costo tsomma(N) = 3N+5 può essere
interpretata come segue
„ per calcolare la somma degli elementi di un array di
lunghezza N sono necessarie 3N+5 unità di costo, ovvero
bisogna eseguire 3N+5 operazioni elementari
„ intuitivamente, questo significa che il tempo di esecuzione di
somma è in qualche modo commisurato a 3N+5
„ in effetti, l’analisi di complessità ha lo scopo di
determinare l’andamento del tempo di esecuzione di un
metodo, anche se solo in modo approssimato
22
Complessità
Fondamenti di informatica
Funzione di costo e tempo di esecuzione
In generale, non è possibile determinare esattamente, in modo
analitico, il tempo di esecuzione (in secondi) di un metodo, per
diversi motivi
„ il tempo di esecuzione di una operazione elementare dipende
dalla particolare operazione elementare considerata
„ ad esempio, il tempo di esecuzione dell’operazione i++ è
(intuitivamente) diverso da quello di s += somma[i]
„ il tempo di esecuzione di ogni operazione varia da calcolatore
a calcolatore, anche in modo dipendente dalla configurazione
e dal carico del calcolatore
„ inoltre, a parità di calcolatore, configurazione e carico, il
tempo di esecuzione di una operazione può dipendere, in
modo complesso, dalla particolare sequenza di operazioni
eseguita dal calcolatore
„ nell’approccio analitico, è quindi possibile determinare il
tempo di esecuzione di un metodo solo in modo
approssimato
23
Complessità
Fondamenti di informatica
Modelli di costo e approssimazioni
Ogni possibile modello di costo introduce comunque delle
approssimazioni
„ ad esempio, il semplice modello di costo considerato assegna
costo computazionale 1 a qualsiasi operazione elementare
„ in realtà il tempo di esecuzione di una operazione
elementare op è (circa) uguale a un qualche valore top, che
può essere determinato sperimentalmente con riferimento
a un particolare calcolatore
„ confondere top con 1 vuol dire accettare approssimazioni a
meno di fattori costanti
„ nell’analisi di complessità sono in effetti ritenute accettabili le
approssimazioni a meno di fattori costanti (e a meno di
termini di ordine inferiore), che sono le approssimazioni che
vengono effettivamente introdotte dai modelli di costo
utilizzati in pratica
24
Complessità
Fondamenti di informatica
Un modello di costo per Java
D’ora in poi verrà fatto riferimento al modello di costo descritto
sinteticamente nella seguente tabella
Operazione s
Costo ts di una esecuzione di s
istruzione semplice (senza
invocazioni di metodo) —
espressione, assegnazione,
incremento, istruzione return e new
blocco — { s1 s2 ... sK }
1
ts1 + ts2 + ... + tsK
istruzione condizionale —
if (cond) s1 else s2
tcond + ts1 — se cond è true
tcond + ts2 — se cond è false
istruzione ripetitiva —
while (cond) s —
nell’ipotesi che il corpo del while
venga eseguito N volte
tcond(1) + ts(1) + tcond(2) + ts(2) +
... + tcond(N) + ts(N) + tcond(N+1)
invocazione di metodo o
costruttore r.M(p1, ..., pK)
25
tr + tp1 + ... + tpK + tM
Complessità
Fondamenti di informatica
Un modello di costo per Java
Istruzioni semplici
„ l’esecuzione di una istruzione semplice (ovvero, che non
contiene invocazione di metodi o costruttori) ha costo unitario
„ intuitivamente, viene assegnato costo unitario a ciascuna
operazione che corrisponde all’esecuzione di un numero
finito e costante di operazioni “primitive” — le operazioni
primitive comprendono le operazioni aritmetiche, l’accesso
a un elemento di un array e il calcolo di una espressione e
la restituzione del suo valore da parte di un metodo
„ è considerato unitario anche il costo dell’esecuzione di una
operazione new
„ nel caso di creazione di un oggetto è necessario però
considerare anche il costo di esecuzione del costruttore
Blocco
„ il costo dell’esecuzione di un blocco di operazioni è pari alla
somma dei costi di esecuzione delle operazioni che
costituiscono il blocco
26
Complessità
Fondamenti di informatica
Un modello di costo per Java
Istruzioni condizionali
„ il costo dell’esecuzione di una istruzione if-else è dato dal
costo della valutazione della condizione più il costo
dell’esecuzione della parte (if oppure else) che viene eseguita
„ il costo dell’esecuzione di una istruzione if o di una cascata di
istruzioni condizionali può essere determinato in modo simile
Istruzioni ripetitive
„ il costo dell’esecuzione di una istruzione while è dato dalla
somma dei costi delle valutazioni della condizione più la
somma dei costi delle esecuzioni del corpo
„ se il corpo di una istruzione ripetitiva while viene eseguito
N volte allora la condizione viene valutata N+1 volte
„ è possibile che ciascuna valutazione della condizione ed
esecuzione del corpo abbia costo diverso
„ il costo dell’esecuzione di una istruzione for o do-while può
essere determinato in modo simile
27
Complessità
Fondamenti di informatica
Un modello di costo per Java
Invocazione di metodo (o costruttore)
„ il costo dell’esecuzione di una invocazione di metodo è data
dalla somma del costo del calcolo dell’oggetto ricevitore e dei
parametri attuali, più il costo dell’esecuzione del metodo
„ nel caso in cui l’oggetto ricevitore o un parametro attuale
sia semplicemente una variabile, un letterale oppure una
espressione semplice (che non contenga nessuna
invocazione di metodo), il costo del calcolo del ricevitore o
del parametro attuale viene considerato nullo
„ bisogna invece tenere in considerazione il costo del calcolo
dell’oggetto ricevitore e dei parametri attuali in particolare
nella determinazione del costo di una cascata o
composizione di invocazioni di metodi
28
Complessità
Fondamenti di informatica
Esempio — somma degli elementi di un array
Determinare la funzione di costo del metodo somma (rispetto alla
lunghezza N di dati) con riferimento al modello di costo proposto
/* Calcola la somma degli elementi di dati. */
public static int somma(int[] dati) {
// pre: dati!=null
int n;
// lunghezza di dati
int s;
// somma degli elementi di dati
int i;
// indice per la scansione di dati
/* calcola la somma degli elementi di dati */
n = dati.length;
s = 0;
for (i=0; i<n; i++)
s += dati[i];
return s;
}
„
29
in questo caso, il modello di costo non è in contraddizione
con il semplice modello di costo considerato in precedenza, e
la funzione di costo è data ancora da tsomma(N) = 3N + 5
Complessità
Fondamenti di informatica
Esempio — somma degli elementi di una matrice
Determinare la funzione di costo del metodo sommaMatrice che
calcola la somma degli elementi dell’array bidimensionale mat,
rispetto al numero R di righe e al numero C di colonne di mat
/* Calcola la somma degli elementi di mat. */
public static int sommaMatrice(int[][] mat) {
// pre: mat!=null && mat è rettangolare
int r;
// numero di righe di mat
int s;
// somma degli elementi di mat
int i;
// indice per la scansione delle righe di mat
/* calcola la somma degli elementi di mat */
r = mat.length;
s = 0;
for (i=0; i<r; i++)
/* somma a s la somma degli elementi della riga
* i-esima di mat */
s += somma(mat[i]);
return s;
}
30
Complessità
Fondamenti di informatica
Somma degli elementi di una matrice
Nel metodo sommaMatrice
„ l’operazione r=mat.length viene eseguita una volta e ha costo
unitario
„ l’operazione s=0 viene eseguita una volta e ha costo unitario
„ l’operazione return s viene eseguita una volta e ha costo
unitario
„ l’istruzione ripetitiva viene eseguita una sola volta
„ bisogna determinare il costo dell’istruzione ripetitiva
31
Complessità
Fondamenti di informatica
Somma degli elementi di una matrice
Relativamente all’esecuzione dell’istruzione ripetitiva
„ il corpo dell’istruzione ripetitiva viene eseguito R volte (R è il
numero di righe di mat), con i che varia tra 0 (compreso) e R
(escluso)
„ l’operazione i = 0 viene eseguita una volta e ha costo unitario
„ l’operazione i < r viene eseguita R+1 volte, e ciascuna
esecuzione ha costo unitario
„ l’operazione s += somma(mat[i]) viene eseguita R volte, con i
che varia tra 0 (compreso) e R (escluso)
„ bisogna determinare il costo delle esecuzioni di questa
operazione
„ l’operazione i++ viene eseguita R volte, e ciascuna esecuzione
ha costo unitario
32
Complessità
Fondamenti di informatica
Somma degli elementi di una matrice
Relativamente alle esecuzioni dell’operazione s +=
somma(mat[i]), che viene eseguita con i che varia tra 0
(compreso) e R (escluso)
„ per ogni singola invocazione del metodo somma, il costo
della valutazione dei parametri e dell’oggetto ricevitore è
nullo
„ per ogni singola esecuzione del metodo somma, il costo
dell’esecuzione del metodo è tsomma(N) = 3N + 5, in cui N è il
numero di elementi di mat[i]
„ essendo mat una matrice rettangolare, tutte le righe di
mat hanno la stessa lunghezza, C
„ ciascuna esecuzione di somma ha costo 3C + 5
33
Complessità
Fondamenti di informatica
Somma degli elementi di una matrice
Il costo complessivo del metodo sommaMatrice è dato dai
seguenti termini
„ 3 per le operazioni fuori dall’istruzione ripetitiva
„ 1 per l’inizializzazione dell’istruzione ripetitiva
„ R+1 per la condizione dell’istruzione ripetitiva
„ R per l’incremento dell’istruzione ripetitiva
„ (3C + 5)•R per il corpo dell’istruzione ripetitiva
Sommando questi termini, è possibile concludere che
tsommaMatrice(R,C) = 2R + 5 + (3C + 5)•R = 3RC + 7R +5
„
nel caso in cui mat sia quadrata, se N è il numero di elementi
di mat allora R = C = √N e la funzione di costo rispetto a N è
tsommaMatrice(N) = 3N + 7√N +5
34
Complessità
Fondamenti di informatica
Esercizio — somma della diagonale principale
Determinare la funzione di costo di sommaDiagonale, che
calcola la somma degli elementi della diagonale principale della
matrice quadrata mat, rispetto al numero N di elementi di mat
/* Calcola la somma della diagonale principale di mat. */
public static int sommaDiagonale(int[][] mat) {
// pre: mat!=null && mat è quadrata
int r;
// numero di righe e colonne di mat
int s;
// somma della diagonale principale di mat
int i;
// indice per la scansione di mat
/* calcola la somma della diagonale principale di mat */
r = mat.length;
s = 0;
for (i=0; i<r; i++)
s += mat[i][i];
return s;
}
Soluzione
tsommaDiagonale(N) = 3√N +5
35
Complessità
Fondamenti di informatica
Dipendenza dall’insieme di dati di ingresso
Si consideri ora il problema di determinare la funzione di costo
del seguente metodo di ricerca di un valore chiave nell’array di
interi dati
„ rispetto alla lunghezza N di dati
/* Verifica se dati contiene un elemento uguale a chiave. */
public static boolean ricerca(int[] dati, int chiave) {
// pre: dati!=null
int n;
// lunghezza di dati
int i;
// indice per la scansione di dati
boolean contiene;
// dati contiene un elemento
// uguale a chiave
/* cerca un elemento uguale a chiave in dati */
n = dati.length;
contiene = false;
for (i=0; i<n; i++)
if (dati[i]==chiave)
contiene = true;
return contiene;
}
36
Complessità
Fondamenti di informatica
Dipendenza dall’insieme di dati di ingresso
/* Verifica se dati contiene un elemento uguale a chiave. */
public static boolean ricerca(int[] dati, int chiave) {
// pre: dati!=null
int n;
// lunghezza di dati
int i;
// indice per la scansione di dati
boolean contiene;
// dati contiene un elemento
// uguale a chiave
/* cerca un elemento uguale a chiave in dati */
eseguita 1 volta
n = dati.length;
eseguita 1 volta
contiene = false;
eseguite (1, N+1, N) volte
for (i=0; i<n; i++)
eseguita N volte
if (dati[i]==chiave)
eseguita K volte (0≤K≤N)
contiene = true;
eseguita 1 volta
return contiene;
}
In questo caso, la funzione di costo di ricerca è
tricerca(N) = 3N + K + 5
„
37
in cui K indica il numero di elementi di dati che sono uguali a
chiave (0≤K≤N)
Complessità
Fondamenti di informatica
Caso peggiore e caso migliore
Il costo tricerca(N) = 3N + K + 5 di esecuzione del metodo ricerca
dipende non solo dalla la lunghezza N di dati, ma anche dal
numero K di elementi di dati uguali a chiave
tricerca(N) = 3N + K + 5
In questa formula, K è compreso tra 0 e N
„ K vale 0 se nessun elemento di dati è uguale a chiave
„ in questo caso, 3N + 5 indica il costo dell’esecuzione di
ricerca nel caso migliore (nel senso di caso che conduce
al costo minimo, e quindi migliore, e non nel senso di caso
che produce il miglior risultato)
„ K vale N se tutti gli elementi di dati sono uguali a chiave
„ in questo caso, 3N + N + 5 = 4N + 5 indica il costo
dell’esecuzione di ricerca nel caso peggiore (nel senso di
caso che conduce al costo massimo, e quindi peggiore)
38
Complessità
Fondamenti di informatica
Analisi di caso peggiore e di caso migliore
Un possibile modo per limitare la dipendenza della funzione di
costo dall’insieme di dati di ingresso alla sola dimensione di tale
insieme consiste nel determinare la funzione di costo nel caso
peggiore o nel caso migliore
„ nell’analisi di caso peggiore si fa riferimento a quei valori
per l’insieme di dati di ingresso per cui il costo di esecuzione
è massimo (a parità di dimensione)
„ per ogni N, tM(N) indica il costo massimo di esecuzione di
M tra tutti gli insiemi di dati di ingresso di dimensione N
„ similmente per l’analisi di caso migliore
3N + 5 — nel caso migliore
tricerca(N) =
39
4N + 5 — nel caso peggiore
Complessità
Fondamenti di informatica
Analisi di caso peggiore
Nell’analisi di complessità, la funzione di costo viene solitamente
determinata rispetto al caso peggiore
„ nell’analisi di caso peggiore, la funzione di costo indica il
costo massimo di esecuzione del metodo, rispetto alla
dimensione dei dati di ingresso
„ in effetti, l’analisi di complessità si pone solitamente
l’obiettivo di determinare la quantità massima (ovvero,
sicuramente sufficiente) di risorse che vanno impiegate
nell’esecuzione di un metodo o programma
„ solo in alcuni casi, vengono effettuate analisi di caso
migliore (o “medio”), indicando esplicitamente che non si
tratta di analisi di caso peggiore
Quindi, nel caso del metodo ricerca, si arriva alla conclusione che
la funzione di costo è tricerca(N) = 4N + 5, da interpretarsi come
„ il costo computazionale per l’esecuzione di ricerca è 4N + 5
nel caso peggiore, rispetto alla lunghezza N di dati
40
Complessità
Fondamenti di informatica
Esempio — coppia di elementi uguali
Determinare la funzione di costo del seguente metodo, che
verifica se l’array a contiene una coppia di elementi uguali
/** Verifica se a contiene una coppia di elementi uguali. */
public static boolean coppiaUguali(int[] a) {
// pre: a!=null
int n;
// lunghezza di a
boolean trovataCoppia;
// a contiene una coppia
// di elementi uguali
int i, j;
// indici per la scansione di a
/* cerca una coppia di elementi uguali esaminando
* comunque tutte le coppie di elementi distinti */
n = a.length;
trovataCoppia = false;
for (i=0; i<n; i++)
for (j=i+1; j<n; j++)
if (a[i]==a[j])
trovataCoppia = true;
return trovataCoppia;
}
41
Complessità
Fondamenti di informatica
Coppia di elementi uguali
trovataCoppia = false;
n = a.length;
for (i=0; i<a.length; i++)
...
return trovataCoppia;
Per questa porzione di codice valgono le seguenti considerazioni
„ l’operazione trovataCoppia = false viene eseguita una volta
„ l’operazione n = a.length viene eseguita una volta
„ l’operazione return trovataCoppia viene eseguita una volta
„ relativamente all’istruzione ripetitiva più esterna
„ il corpo di questa istruzione ripetitiva viene eseguito N
volte (N è la lunghezza di a), con i che varia tra 0
(compreso) e N (escluso)
„ l’operazione i = 0 viene eseguita una volta
„ l’operazione i < n viene eseguita N+1 volte
„ l’operazione i++ viene eseguita N volte
42
Complessità
Fondamenti di informatica
Coppia di elementi uguali
for (j=i+1; j<n; j++)
...
Per questa porzione di codice valgono le seguenti considerazioni
„ questa istruzione ripetitiva (più interna) viene eseguita N volte
„ la prima volta con i che vale 0, la seconda con i che vale 1,
..., l’ultima volta con i che vale N–1
„ relativamente all’esecuzione di questa istruzione ripetitiva nel
caso in cui i che vale I
„ il corpo di questa istruzione ripetitiva viene eseguito N–1–I
volte (N–1 volte quando I vale 0, ..., 0 volte quando I vale
N–1), con j che varia tra I+1 (compreso) e N (escluso)
„ l’operazione j = i+1 viene eseguita una volta
„ l’operazione j < n viene eseguita N–1–I+1 = N–I volte
„ l’operazione j++ viene eseguita N–1–I volte
43
Complessità
Fondamenti di informatica
Coppia di elementi uguali
for (j=i+1; j<n; j++)
...
Per questa porzione di codice valgono le seguenti considerazioni
„ complessivamente (ovvero, considerando tutte le esecuzioni
di questa istruzione ripetitiva)
N–1 (1) = N volte
„ l’operazione j = i+1 viene eseguita ΣI=0
N–1 (N–I) =
„ l’operazione j < n viene eseguita ΣI=0
N2 – N(N–1)/2 = (N2 + N)/2 volte
N–1 (N–1–I) = (N2 – N)/2
„ l’operazione j++ viene eseguita ΣI=0
volte
„ il corpo di questa istruzione ripetitiva viene eseguito
ΣI=0N–1 (N–1–I) = (N2 – N)/2 volte
Nel calcolo delle sommatorie è stata utilizzata la formula di Gauss
(si noti che viene spesso applicata con M = N–1)
Σk=0M k = M (M+1) / 2
44
Complessità
Fondamenti di informatica
Coppia di elementi uguali
if (a[i]==a[j])
...
Per questa porzione di codice valgono le seguenti considerazioni
„ questa istruzione condizionale viene eseguita una volta per
ciascuna esecuzione del corpo dell’istruzione ripetitiva più
interna
„ in ciascuna esecuzione di questa istruzione condizionale,
la condizione a[i]==a[j] viene valutata una volta, e la parte
if viene eseguita solo se la condizione si è verificata
„ complessivamente, questa istruzione condizionale viene
eseguita (N2 – N)/2 volte
„ la condizione a[i]==a[j] viene valutata complessivamente
(N2 – N)/2 volte
„ la parte if viene eseguita un numero di volte compreso tra
0 (se gli elementi di a sono tutti distinti) e (N2 – N)/2 (se gli
elementi di a sono tutti uguali)
45
Complessità
Fondamenti di informatica
Coppia di elementi uguali
trovataCoppia = true;
Per questa porzione di codice valgono le seguenti considerazioni
„ questa istruzione è la parte if dell’istruzione condizionale
„ complessivamente, l’istruzione condizionale viene eseguita
(N2 – N)/2 volte, e la sua parte if un numero di volte compreso
tra 0 e (N2 – N)/2
„ l’operazione trovataCoppia = true viene eseguita un numero
di volte compreso tra 0 (se gli elementi di a sono tutti distinti,
caso migliore) e (N2 – N)/2 (se gli elementi di a sono tutti
uguali, caso peggiore)
46
Complessità
Fondamenti di informatica
Coppia di elementi uguali
È ora possibile determinare la funzione di costo del metodo
coppiaUguali
„ l’operazione trovataCoppia = false viene eseguita
(complessivamente) una volta
„ l’operazione n = a.length viene eseguita una volta
„ l’operazione return trovataCoppia viene eseguita una volta
„ l’operazione i = 0 viene eseguita una volta
„ l’operazione i < n viene eseguita N+1 volte
„ l’operazione i++ viene eseguita N volte
„ l’operazione j = i+1 viene eseguita N volte
2
„ l’operazione j < n viene eseguita (N + N)/2 volte
2
„ l’operazione j++ viene eseguita (N – N)/2 volte
2
„ la condizione a[i]==a[j] viene valutata (N – N)/2 volte
2
„ l’operazione trovataCoppia = true viene eseguita (N – N)/2
volte (nel caso peggiore)
47
tcoppiaUguali(N) = 2N2 + 2N + 5
Complessità
Fondamenti di informatica
Esercizio
Analizzare la funzione di costo del seguente metodo ricercaSeq
/* Verifica se dati contiene un elemento uguale a chiave. */
public static boolean ricercaSeq(int[] dati, int chiave) {
// pre: dati!=null
int n;
// lunghezza di dati
int i;
// indice per la scansione di dati
boolean contiene; // dati contiene un elemento uguale a chiave
/* cerca un elemento uguale a chiave in dati */
n = dati.length;
contiene = false;
i = 0;
while (!contiene && i<n) {
if (dati[i]==chiave)
contiene = true;
i++;
}
return contiene;
}
„
48
discutere inoltre il caso peggiore e il caso migliore per il
metodo
Complessità
Fondamenti di informatica
Analisi asintotica
Il calcolo esatto della funzione di costo tM(N) di un metodo M (in
cui viene stabilito esattamente quante volte viene eseguita
ciascuna operazione) è nella maggioranza dei casi molto
laborioso
„ il livello di dettaglio descritto dalla funzione di costo è
veramente necessario?
„ quanto è importante calcolare con precisione quante
“operazioni elementari” vengono eseguite?
„ quanto è importante stabilire l’insieme delle “operazioni
elementari” (ed eventualmente il loro costo di esecuzione
unitario)?
„ se è stata fatta la scelta di dare lo stesso costo a operazioni
molto diverse tra loro (come i++ e s += dati[i]) — ritenendo
accettabile la semplificazione di fattori costanti — come è
possibile semplificare l’analisi senza introdurre ulteriori
approssimazioni?
49
Complessità
Fondamenti di informatica
Analisi asintotica di complessità
L’analisi asintotica di complessità si pone l’obiettivo di
determinare la funzione di costo in modo asintotico, ovvero
„ a meno di fattori moltiplicativi costanti
„ a meno di termini additivi di ordine inferiore
„ descrivendo l’andamento della funzione di costo per N (la
dimensione dei dati di ingresso) tendente all’infinito
Nell’analisi asintotica, il costo computazionale dei metodi viene
determinato in modo più approssimato (ovvero, meno preciso)
rispetto a quanto fatto finora
„ i risultati ottenuti mediante l’analisi asintotica sono comunque
significativi nella maggioranza dei casi
„ il modello di costo mostrato in precedenza è già
approssimato a meno di fattori moltiplicativi costanti —
per essere indipendente dal calcolatore usato
„ i termini additivi di ordine inferiore sono asintoticamente
meno importanti dei fattori moltiplicativi costanti
50
Complessità
Fondamenti di informatica
Comportamento asintotico della funzione di costo
L’analisi asintotica di complessità di un metodo M si pone
l’obiettivo di determinare l’andamento asintotico TM(N) della
funzione di costo tM(N) del metodo M rispetto al modello di costo
basato sul conteggio delle operazioni eseguite (che è il modello di
costo che è stato considerato finora)
„ nel seguito, saranno usato i simboli
„ tM(N) per indicare la funzione di costo basata sul conteggio
delle operazioni (notare la ‘t’ minuscola)
„ TM(N) per indicare la funzione di costo asintotico calcolata
mediante l’analisi asintotica (notare la ‘T’ maiuscola)
T M(N)
tM(N)
N
51
Complessità
Fondamenti di informatica
Comportamento asintotico della funzione di costo
La funzione di costo asintotico TM(N) di un metodo M indica il
costo di M rispetto alla dimensione N dei dati di ingresso per M,
calcolato al crescere della dimensione N e trascurando fattori
moltiplicativi costanti e termini additivi di ordine inferiore
T M(N)
tM(N)
N
„
„
52
l’analisi asintotica viene svolta
„ rispetto a un modello di costo asintotico
„ se possibile, rispetto a una “istruzione dominante” del
metodo
prima di mostrare questi strumenti, vengono introdotte le
basi matematiche dell’analisi asintotica
Complessità
Fondamenti di informatica
Notazione O
La notazione O (si legge ‘o’ grande) è il principale strumento
matematico per l’analisi asintotica
Date le funzioni f(N) e g(N), si dice che f(N) è O( g(N) ) — scritto
anche f(N) ∈ O(g(N)) oppure f(N) = O(g(N)) e letto f è ‘o’ grande
di g oppure f(N) è ordine di g(N) — se esistono due costanti
positive C e N0 tali che f(N) ≤ C•g(N) per tutti gli N ≥ N0
„ f(N) = O( g(N) ) vuole dire che la funzione f cresce (in modo
asintotico) al più quanto la funzione g
„ a meno di fattori costanti e termini di ordine inferiore, e
per N sufficientemente grande
Ad esempio
„ la funzione 3N + 5 è O( N )
„ infatti, 3N + 5 ≤ C• N per tutti gli N maggiori di N0 è vera,
ad esempio, scegliendo C=4 e N0=5
2
„ la funzione 3N + 5 è anche O( N )
„ la funzione log10 N è O( N )
53
Complessità
Fondamenti di informatica
Notazione O
In pratica, f(N) è O( g(N) ) se il limite per N tendente all’infinito di
f(N)/g(N) esiste e ha un valore finito (eventualmente zero)
Ad esempio
„ la funzione 3N + 5 è O( N )
„ infatti, limN→∞ (3N + 5)/N è finito (vale 3)
2
„ la funzione 3N + 5 è O( N )
2
„ infatti, limN→∞ (3N + 5)/N è finito (vale zero)
„ la funzione log10 N è O( N )
„ infatti, limN→∞ (log10 N)/N è finito (vale zero)
Va osservato che nell’analisi asintotica è possibile dire che f(N) è
O( g(N) ), leggendolo f(N) è ordine di g(N), anche se
limN→∞ f(N)/g(N) = 0, ovvero anche se f(N) ha un ordine di
grandezza diverso (inferiore) da quello di g(N)
54
Complessità
Fondamenti di informatica
Notazione O
Esempi di funzioni f(N) che sono O( g(N) )
g(N)
g(N)
f(N)
f(N)
N
N
g(N)
g(N)
f(N)
f(N)
N
N
55
Complessità
Fondamenti di informatica
Notazione Θ
La notazione Θ (si legge teta grande) è lo strumento matematico
che permette di descrivere il fatto che due funzioni crescono
asintoticamente nello stesso modo
Date le funzioni f(N) e g(N) si dice che f(N) è Θ( g(N) ) — scritto
anche f(N) = Θ( g(N) ) e letto f è teta grande di g — se f(N) è
O(g(N)) e g(N) è O(f(N))
„ f(N) è Θ( g(N) ) se esistono tre costanti positive C1 , C2 e N0 tali
che C1•g(N) ≤ f(N) ≤ C2•g(N) per tutti gli N ≥ N0
„ f(N) = Θ( g(N) ) vuol dire che le funzioni f(N) e g(N) crescono
asintoticamente nello stesso modo
„ a meno di fattori costanti e termini di ordine inferiore, e
per N sufficientemente grande
56
Complessità
Fondamenti di informatica
Funzioni usate nell’analisi asintotica
Nell’analisi asintotica viene fatto in genere riferimento ad alcune
funzioni che hanno una forma particolarmente “semplice”, tra cui
„ 1 — funzione costante
„ log N — funzione logaritmica (per log N si intende log2 N)
„ √ N
„ N — funzione lineare
„ N log N
2
„ N — funzione quadratica
N
„ 2 — funzione esponenziale
Osservazioni
„ queste funzioni sono state elencate in ordine asintoticamente
crescente (in senso stretto)
2
„ le funzioni √ N, N e N sono casi particolari delle funzioni
polinomiali Nk — che crescono tutte meno di 2N
57
Complessità
Fondamenti di informatica
Analisi asintotica e notazione Θ
L’obiettivo dell’analisi asintotica di complessità di un metodo M è
determinare una funzione di costo asintotico TM(N) per M che
approssimi la funzione di costo tM(N) in modo asintotico (e che sia
possibilmente “semplice”)
„ in linea di principio, l’analisi asintotica di complessità ha lo
scopo di determinare una funzione TM(N) tale che tM(N) sia
Θ(TM(N))
„ ma senza prima calcolare tM(N) !!!
„ ovvero, bisogna determinare una funzione (“semplice”) TM(N)
che abbia un andamento simile a quello del costo
computazionale tM(N) di M
„ a meno di fattori costanti, termini di ordine inferiore, e per
N sufficientemente grande
„ la notazione Θ formalizza, nell’analisi asintotica di
complessità, la nozione di “quantità di risorse (da impiegare
nell’esecuzione di un metodo o programma) a meno di fattori
costanti e termini di ordine inferiore”
58
Complessità
Fondamenti di informatica
Analisi asintotica e notazione O
In pratica, non sempre è possibile determinare una funzione di
costo asintotico TM(N) per un metodo M che approssimi la
funzione di costo tM(N) in modo asintotico, ovvero tale che tM(N)
sia Θ( TM(N) )
„ è invece normalmente possibile determinare una funzione di
costo asintotico TM(N) per M che sia una maggiorazione della
funzione di costo tM(N) in modo asintotico, ovvero tale che
tM(N) sia O( TM(N) )
„ la notazione O formalizza, nell’analisi asintotica di
complessità, la nozione di “quantità sufficiente di risorse (da
impiegare nell’esecuzione di un metodo o programma) a
meno di fattori costanti e termini di ordine inferiore”
59
Complessità
Fondamenti di informatica
Analisi asintotica e notazione O
Ad esempio, l’analisi asintotica si pone l’obiettivo di concludere
che
„ il metodo ricerca ha costo asintotico lineare, Tricerca(N) = O(N)
„ tricerca(N) = 4N + 5
„ il metodo coppiaUguali ha costo asintotico quadratico,
TcoppiaUguali(N) = O( N2 )
2
„ tcoppiaUguali(N) = 2N + 2N + 5
„ il metodo sommaDiagonale ha costo asintotico
TsommaDiagonale(N) = O( √N )
„ tsommaDiagonale(N) = 3√N +5
60
Complessità
Fondamenti di informatica
Analisi asintotica e notazione O
La funzione di costo asintotico TM(N) deve essere determinata in
modo da avere un comportamento asintotico il più possibile
simile a quello di tM(N)
„ ovvero, la notazione O dovrebbe essere utilizzata per
caratterizzare la funzione di costo quanto più possibile,
trascurandone solo i fattori moltiplicativi costanti e i termini
di ordine inferiore
„ si consideri ad esempio il metodo ricerca
„ sebbene sia matematicamente lecito scrivere tricerca(N) = 4N
+ 5 = O( N3 ) per indicare che il costo computazionale di
ricerca è al più cubico in N
„ è chiaramente preferibile scrivere Tricerca(N) = O(N), per
indicare che il costo computazionale di ricerca è lineare (e
non cubico) rispetto a N
In pratica, la notazione O viene solitamente preferita alla
notazione Θ, anche se spesso la notazione O viene usata
attribuendogli il significato della notazione Θ
61
Complessità
Fondamenti di informatica
Funzioni usate nell’analisi asintotica
L’andamento asintotico delle funzioni “semplici” usate nell’analisi
asintotica è mostrato dal seguente grafico
2^n
n^2
1
log n
n
n
„
62
il grafico conferma che, per N sufficientemente grande,
trascurare un termine additivo di ordine inferiore è una
approssimazione meno grave rispetto al trascurare un fattore
moltiplicativo costante
Complessità
Fondamenti di informatica
Analisi asintotica di complessità
L’analisi asintotica di complessità di un metodo M si pone dunque
l’obiettivo di determinare il costo asintotico TM(N) di M, in modo
tale che la funzione di costo tM(N) sia O ( TM(N) )
„ evidentemente, l’analisi asintotica non viene svolta
determinando prima tM(N) e poi TM(N)
„ piuttosto, l’analisi asintotica di complessità di un metodo
viene svolta determinando direttamente la funzione di costo
asintotico TM(N)
La funzione di costo asintotico di un metodo può essere
determinata utilizzando un modello di costo asintotico
„ la differenza principale tra il modello di costo basato sul
conteggio delle operazioni e il modello di costo asintotico è
che, in quest’ultimo, il costo di una sequenza formata da un
numero finito e costante di operazioni non è dato dalla
somma dei costi delle operazioni, ma piuttosto dal solo costo
dell’operazione di costo massimo
63
Complessità
Fondamenti di informatica
Un modello di costo asintotico
Operazione s
Costo asintotico Ts di s
istruzione semplice (senza
invocazioni di metodo) —
1
espressione, assegnazione,
incremento, istruzione return e new
blocco — { s1 s2 ... sK }
istruzione condizionale —
if (cond) s1 else s2
max ( Ts1, Ts2, ..., TsK )
max(Tcond, Ts1) – se cond è true
max(Tcond, Ts2) – se cond è false
istruzione ripetitiva —
while (cond) s —
Tcond(1) + Ts(1) + Tcond(2) + Ts(2) +
nell’ipotesi che il corpo del while ... + Tcond(N) + Ts(N) + Tcond(N+1)
venga eseguito N volte
invocazione di metodo o
costruttore r.M(p1, ..., pK)
64
max ( Tr, Tp1, ..., TpK, TM )
Complessità
Fondamenti di informatica
Esempio — costo asintotico del metodo somma
Determinare la funzione di costo asintotico del metodo somma
(rispetto alla lunghezza N di dati)
/* Calcola la somma degli elementi di dati. */
public static int somma(int[] dati) {
// pre: dati!=null
int n;
// lunghezza di dati
int s;
// somma degli elementi di dati
int i;
// indice per la scansione di dati
/* calcola la somma degli elementi di dati */
n = dati.length;
s = 0;
for (i=0; i<n; i++)
s += dati[i];
return s;
}
65
Complessità
Fondamenti di informatica
Costo asintotico del metodo somma
n = dati.length;
s = 0;
for (i=0; i<n; i++)
s += dati[i];
return s;
Il costo asintotico del metodo somma è dato dal costo della
sequenza di istruzioni che ne forma il corpo
„ il corpo di somma è formato da due assegnazioni, da una
istruzione ripetitiva e da una istruzione return
„ le due assegnazioni e l’istruzione return non contengono
invocazioni di metodi, e quindi hanno costo unitario
„ il costo dell’istruzione ripetitiva è sicuramente maggiore di
(o uguale a) 1
„ l’istruzione ripetitiva ha sicuramente costo massimo tra le
operazioni che formano il corpo del metodo somma, e quindi
il costo asintotico del metodo somma è uguale al costo
asintotico della sola istruzione ripetitiva
66
Complessità
Fondamenti di informatica
Costo asintotico del metodo somma
for (i=0; i<n; i++)
s += dati[i];
Il costo asintotico del metodo somma è dato dal costo asintotico
della sola istruzione ripetitiva
„ nell’esecuzione dell’istruzione ripetitiva (N è la lunghezza di
dati)
„ l’inizializzazione i=0 ha costo unitario e viene eseguita una
sola volta
„ la condizione i<n (che ha costo unitario) viene valutata
N+1volte
„ il blocco formato dal corpo dell’istruzione ripetitiva
s+=dati[i] e dall’incremento i++ (che ha costo unitario)
viene eseguito N volte
„ ogni singola esecuzione del blocco formato dal corpo
dell’istruzione ripetitiva s+=dati[i] (che ha costo unitario) e
dall’incremento i++ (che ha costo unitario) ha
complessivamente costo asintotico unitario
67
Complessità
Fondamenti di informatica
Costo asintotico del metodo somma
for (i=0; i<n; i++)
s += dati[i];
Il costo asintotico del metodo somma è dato dal costo asintotico
della sola istruzione ripetitiva
„ il costo asintotico dell’istruzione ripetitiva è il massimo tra il
costo dell’inizializzazione e la somma del costo delle
valutazioni della condizione e delle esecuzioni del corpo e
dell’incremento
„ il costo dell’inizializzazione è minore del costo delle
valutazioni della condizione e delle esecuzioni del corpo e
dell’incremento, e pertanto può essere trascurato
„ le valutazioni della condizione hanno complessivamente
costo N+1
„ le esecuzioni di corpo e incremento hanno costo asintotico
complessivo N
„ l’istruzione ripetitiva ha costo asintotico 2N+1
„ il metodo somma ha costo asintotico Tsomma(N) = 2N+1 =
68
Complessità
Fondamenti di informatica
O(N)
Esercizio
Determinare il costo asintotico per tutti i metodi che sono stati
già analizzati rispetto al modello di costo basato sul conteggio
delle operazioni
69
Complessità
Fondamenti di informatica
Operazione dominante
Nella determinazione della funzione di costo asintotico basata sul
modello di costo asintotico, solitamente si procede
„ identificando i blocchi di operazioni (di lunghezza finita e
costante) che vengono eseguiti in sequenza
„ determinando, per ciascun blocco di operazioni, il costo
asintotico della sola operazione più costosa
„ infatti, il costo asintotico dell’intero blocco di operazioni è
dato dal costo della sola operazione più costosa
Questa idea viene formalizzata dalla nozione di operazione
dominante di un metodo
„ intuitivamente, una operazione dominante di un metodo è
una operazione il cui costo di esecuzione è asintoticamente
uguale al costo di esecuzione dell’intero metodo
„ l’analisi del costo asintotico di un metodo può essere
semplificata identificando una operazione dominante del
metodo e determinando il costo della sola operazione
dominante, trascurando il costo delle altre operazioni
70
Complessità
Fondamenti di informatica
Operazione dominante
Sia M un metodo la cui funzione di costo è tM(N)
„ una operazione o istruzione D di M è una operazione
dominante se, per ogni N e nell’ipotesi di caso peggiore,
indicando con tMD (N) il costo dell’esecuzione della sola
operazione D nell’ambito dell’esecuzione di M, risulta tM (N) =
O( tMD (N) )
„ ovvero, l’operazione D è dominante se il costo complessivo
dell’esecuzione di D nell’ambito dell’intera esecuzione di M
è asintoticamente non inferiore al costo complessivo di M
Il costo asintotico dell’esecuzione di un metodo che ha una
operazione dominante è pari al costo asintotico dell’esecuzione
della sola operazione dominante
„ se è possibile identificare una operazione dominante del
metodo, allora l’analisi del costo asintotico del metodo può
essere semplificata, riducendola alla determinazione del costo
di esecuzione della sola operazione dominante
„ un metodo può avere zero, una o più operazioni dominanti
71
Complessità
Fondamenti di informatica
Esempio — operazioni dominanti del metodo ricerca
Si consideri nuovamente il seguente metodo (che ha costo
tricerca(N) = 4N + 5)
„ nella figura sono evidenziate le operazioni dominanti del
metodo
/* Verifica se dati contiene un elemento uguale a chiave. */
public static boolean ricerca(int[] dati, int chiave) {
// pre: dati!=null
int n;
// lunghezza di dati
int i;
// indice per la scansione di dati
boolean contiene;
// dati contiene un elemento
// uguale a chiave
/* cerca un elemento uguale a chiave in dati */
n = dati.length;
contiene = false;
for (i=0; i<n; i++)
if (dati[i]==chiave)
contiene = true;
return contiene;
}
72
Complessità
Fondamenti di informatica
Operazioni dominanti del metodo ricerca
Nel metodo ricerca sono operazioni dominanti tutte le operazioni
il cui contributo al costo asintotico è O(N)
„ la condizione dell’istruzione ripetitiva è dominante
„ perché viene valutata N+1 volte e ciascuna valutazione ha
costo unitario
„ l’incremento dell’istruzione ripetitiva è dominante
„ perché viene eseguito N volte e ciascuna esecuzione ha
costo unitario
„ il corpo dell’istruzione ripetitiva è dominante
„ perché viene eseguito N volte e ciascuna esecuzione ha
costo unitario
„ la condizione dell’istruzione condizionale è dominante
„ perché viene valutata N volte e ciascuna valutazione ha
costo unitario
„ la parte if dell’istruzione condizionale è dominante
„ perché (nel caso peggiore) viene eseguita N volte e
ciascuna esecuzione ha costo unitario
73
Complessità
Fondamenti di informatica
Esempio — operazioni dominanti di coppiaUguali
Nella seguente figura sono evidenziate le operazioni dominanti
per il metodo coppiaUguali (che ha costo asintotico quadratico)
/** Verifica se a contiene una coppia di elementi uguali. */
public static boolean coppiaUguali(int[] a) {
// pre: a!=null
int n;
// lunghezza di a
boolean trovataCoppia;
// a contiene una coppia
// di elementi uguali
int i, j;
// indici per la scansione di a
/* cerca una coppia di elementi uguali esaminando
* comunque tutte le coppie di elementi distinti */
n = a.length;
trovataCoppia = false;
for (i=0; i<n; i++)
for (j=i+1; j<n; j++)
if (a[i]==a[j])
trovataCoppia = true;
return trovataCoppia;
}
74
Complessità
Fondamenti di informatica
Identificazione dell’operazione dominante
Se un metodo è formato solo da operazioni semplici (ovvero, se
non contiene nessuna invocazione di metodo o costruttore) allora
„ ciascuna operazione del metodo ha costo unitario
„ il costo complessivo di ciascuna operazione (semplice) è
uguale al numero di volte che l’operazione viene eseguita
„ l’operazione dominante del metodo è l’operazione che viene
eseguita più spesso
„ solitamente, in questo caso, le operazioni dominanti
appartengono alle istruzioni ripetitive più annidate del
metodo, come nel caso del metodo coppiaUguali
Tuttavia, se un metodo non è formato solo da operazioni semplici
(ovvero, se contiene invocazioni di metodi o costruttori) allora
„ il costo complessivo di una operazione è solitamente dato dal
prodotto del numero di volte che l’operazione viene eseguita
per il costo di ciascuna esecuzione dell’operazione
„ l’operazione dominante del metodo non è necessariamente
l’operazione che viene eseguita più spesso
75
Complessità
Fondamenti di informatica
Esercizi
Identificare le operazioni dominanti in tutti i metodi che sono
stati analizzati
Determinare il costo asintotico per tutti i metodi che sono stati
analizzati, basandosi sull’identificazione di una operazione
dominante
76
Complessità
Fondamenti di informatica
Esercizio
/* Verifica se dati contiene un elemento uguale a chiave. */
public static boolean ricercaSeq(int[] dati, int chiave) {
// pre: dati!=null
int n;
// lunghezza di dati
int i;
// indice per la scansione di dati
boolean contiene; // dati contiene un elemento uguale a chiave
/* cerca un elemento uguale a chiave in dati */
n = dati.length;
contiene = false;
i = 0;
while (!contiene && i<n) {
if (dati[i]==chiave)
contiene = true;
i++;
}
return contiene;
}
Discutere le seguenti affermazioni (che sono entrambe vere)
„ l’operazione dati[i]==chiave è dominante
„ l’operazione contiene=true non è dominante
77
Complessità
Fondamenti di informatica
Complessità degli algoritmi e dei problemi
Le tecniche di analisi di complessità, che sono state introdotte per
l’analisi dei metodi, possono essere usate anche per l’analisi di
complessità degli algoritmi
„ uno stesso algoritmo può essere implementato da metodi
diversi
„ la complessità di un algoritmo può essere definita come la
complessità del metodo più efficiente che implementa
l’algoritmo
In pratica, per analizzare la complessità degli algoritmi
„ viene considerato un linguaggio di programmazione astratto e
un modello di costo generico per questo linguaggio
„ non vengono presi in considerazione tutti i dettagli della
programmazione in uno specifico linguaggio
„ inoltre, nei casi più semplici (come quelli che considereremo),
è possibile ragionare correttamente anche con algoritmi
descritti in modo informale e incompleto
78
Complessità
Fondamenti di informatica
Esempio — complessità di un algoritmo
Determinare la complessità del seguente algoritmo, che verifica
se gli elementi di un array di interi sono tutti uguali
„ viene ipotizzato che gli elementi dell’array sono tutti uguali
„ viene confrontato ogni elemento dell’array con ogni altro
elemento dell’array di indice maggiore
„ se viene trovata almeno una coppia di elementi diversi,
allora è possibile concludere che non è vero che gli
elementi dell’array sono tutti uguali
L’algoritmo ha complessità quadratica
„ infatti
„ l’operazione dominante è il confronto tra coppie di
elementi (in cui il primo elemento ha indice minore del
secondo)
„ il numero di coppie in cui il primo elemento ha indice
minore del secondo è quadratico nella lunghezza dell’array
79
Complessità
Fondamenti di informatica
Complessità di problemi
Uno stesso problema può essere risolto da più algoritmi
„ la complessità di un problema è la complessità
dell’algoritmo più efficiente tra quelli che risolvono il
problema
L’analisi di complessità permette di scegliere il “migliore”
algoritmo tra gli algoritmi noti che risolvono un problema
„ si supponga ad esempio di dover risolvere un problema P e di
conoscere gli algoritmi A e B che risolvono P
2
„ si supponga inoltre che l’algoritmo A sia Θ(N ) e che
l’algoritmo B sia Θ(N log N)
„ è possibile in questo caso concludere che B è
asintoticamente migliore di A, e quindi decidere di
risolvere il problema P implementando l’algoritmo B
Va osservato che non sempre sono noti tutti gli algoritmi che
risolvono un certo problema, e quindi bisogna accontentarsi di
poter scegliere il migliore algoritmo tra quelli noti
80
Complessità
Fondamenti di informatica
Esempio — complessità degli algoritmi e dei problemi
Si consideri il problema di verificare se gli elementi di un array di
interi sono tutti uguali
„ un primo algoritmo è quello considerato in precedenza
„ un secondo algoritmo è il seguente
„ viene ipotizzato che gli elementi dell’array sono tutti
uguali
„ viene confrontato ogni elemento dell’array con l’elemento
che lo segue
„ se viene trovata almeno una coppia di elementi consecutivi
diversi, allora è possibile concludere che gli elementi
dell’array non sono tutti uguali
81
Complessità
Fondamenti di informatica
Complessità degli algoritmi e dei problemi
Il primo algoritmo ha complessità quadratica
Il secondo algoritmo ha complessità lineare
„ l’operazione dominante è il confronto tra coppie di elementi
„ vengono confrontati solo coppie di elementi consecutivi
„ la complessità è lineare
In questo caso è possibile concludere che il secondo algoritmo è
asintoticamente migliore del primo
„ inoltre, è possibile osservare che tutti gli algoritmi che
risolvono questo problema devono (nel caso peggiore)
accedere a tutti gli elementi dell’array, e quindi hanno costo
asintotico almeno lineare rispetto al numero di elementi
dell’array
„ perciò, non esistono algoritmi di costo sub-lineare per il
problema in esame
„ anche il problema ha complessità lineare
82
Complessità
Fondamenti di informatica
Complessità dei metodi delle API di Java
Un problema che finora non è stato considerato è quello
dell’analisi della complessità dei metodi delle API di Java o di altre
operazioni di librerie di classi predefinite
„ ad esempio, qual è la complessità dei metodi substring e
indexOf della classe String?
La documentazione delle API di Java riporta la complessità delle
operazioni solo in alcuni casi (in particolare, nei casi in cui la
complessità è particolarmente significativa)
„ tuttavia, in molti casi la documentazione non riporta la
complessità dei metodi
„ conoscere l’algoritmo usato da un certo metodo (o quelli noti
per risolvere un certo problema) può essere sufficiente per
determinare la complessità asintotica del metodo
„ non è necessario conoscere in dettaglio il codice del
metodo
„ è spesso sufficiente una conoscenza approssimativa
dell’algoritmo implementato dal metodo
83
Complessità
Fondamenti di informatica
Giustificazione dell’analisi asintotica
La rilevanza dell’analisi asintotica di complessità può essere
mostrata mediante alcuni dati numerici
„ la seguente tabella mostra il costo computazionale effettivo
(basato sul conteggio delle operazioni) per cinque classi di
metodi caratterizzate da complessità asintotica differente, per
alcuni possibili valori della dimensione N dei dati di ingresso
„ viene mostrato il tempo di esecuzione in secondi,
ipotizzando che il tempo di esecuzione di ciascuna
operazione sia 1µs (10–6 s)
84
t(N) \ N
10
100
1 000
10 000
40 N
0.000 4
0.004
0.04
0.4
20 N log N
0.000 6
0.013
0.2
2.65
2 N2
0.000 2
0.02
2
200
N4
0.01
100
2N
0.001
1018 anni
Complessità
277 giorni 7 610 anni
10289 anni
Fondamenti di informatica
Giustificazione dell’analisi asintotica
La seguente tabella propone dati analoghi a quelli della
precedente tabella
„ mostrando per le cinque classi di metodi la dimensione
massima dell’insieme di dati di ingresso che può essere
elaborato in un secondo, un minuto e un’ora
„ ipotizzando che il tempo di esecuzione di ciascuna
operazione sia 1µs
85
t(N)
1 secondo
1 minuto
1 ora
40 N
25 000
1 500 000
90 000 000
20 N log N
4 096
166 666
7 826 087
2 N2
707
5 477
42 426
N4
31
88
244
2N
19
25
31
Complessità
Fondamenti di informatica
Giustificazione dell’analisi asintotica
La seguente tabella mostra il miglioramento che può essere
ottenuto in termini di incremento della dimensione massima M
dell’insieme di dati di ingresso che può essere elaborato
„ in una stessa quantità di tempo usando un calcolatore 256
volte più veloce
„ oppure usando lo stesso calcolatore e una quantità di tempo
256 volte maggiore
86
t(N)
nuova dimensione massima
40 N
256 M
20 N log N
256 M ((log M) / (7 + log M))
2 N2
16 M
N4
4M
2N
M+8
Complessità
Fondamenti di informatica
Discussione sull’analisi asintotica
Le precedenti tabelle mostrano che
„ le funzioni usate nell’analisi asintotica si comportano in modo
simile per N piccolo, ma in modo molto diverso per N grande
„ le funzioni “semplici” usate nell’analisi asintotica sono
sostanzialmente diverse, e caratterizzano delle classi di
complessità significative
„ i problemi trattabili (ovvero, per cui è ragionevole realizzare
e utilizzare una soluzione automatica) sono quelli
caratterizzati da una funzione di costo asintotico al più
polinomiale
„ i problemi intrattabili (ovvero, per cui non è ragionevole
realizzare e utilizzare una soluzione automatica, quantomeno
per N grande) sono quelli caratterizzati da una funzione di
costo asintotico esponenziale (o maggiore)
„ problemi con costo esponenziale sono ad esempio quelli
che richiedono di determinare una permutazione degli
elementi dell’insieme di dati di ingresso che sia ottimale
rispetto a un criterio non banale
87
Complessità
Fondamenti di informatica
Discussione
Vengono ora discussi brevemente alcuni aspetti legati all’analisi
di complessità e all’implementazione di algoritmi efficienti
„ l’efficienza di un metodo e la semplicità di realizzazione del
metodo sono aspetti contrastanti
„ ovvero, la realizzazione di un metodo che risolve un
problema in modo efficiente può richiedere più tempo che
non la realizzazione di un metodo che risolve lo stesso
problema in modo poco efficiente
„ se bisogna realizzare un metodo che risolve un problema
„ se il metodo deve essere eseguito “poche volte” oppure
solo su insiemi di dati di ingresso “piccoli” allora può
essere opportuno implementare un algoritmo semplice
anche se poco efficiente
„ se il metodo deve essere eseguito “spesso” (ad esempio,
perché inserito in una libreria di metodi) oppure su insiemi
di dati di ingresso “grandi” allora è solitamente opportuno
implementare l’algoritmo più efficiente tra quelli noti
88
Complessità
Fondamenti di informatica
Discussione
„
„
„
89
l’analisi asintotica trascura i fattori moltiplicativi costanti e i
termini di ordine inferiore
„ se bisogna identificare un algoritmo per risolvere un
problema in modo molto efficiente, allora è opportuno
considerare anche i fattori moltiplicativi costanti,
eseguendo l’analisi di complessità basata sul conteggio
delle operazioni (o su un modello di costo ancora più
sofisticato) e non l’analisi asintotica
l’analisi di caso peggiore è in alcuni casi poco realistica
„ in alcuni casi è opportuno eseguire delle analisi di caso
medio (in cui il costo computazionale viene calcolato in
modo medio rispetto a tutti i possibili insiemi di dati di
ingresso) oppure altre forme di analisi
in alcuni casi è utile (o necessario) considerare la complessità
computazionale anche rispetto ad altre risorse di calcolo
„ come ad esempio la complessità spaziale (che stima
l’occupazione di memoria per l’esecuzione di un
programma o metodo)
Complessità
Fondamenti di informatica
Discussione
„
90
i modelli di costo proposti trascurano il costo delle attivazioni
dei metodi
„ in realtà, il costo dell’attivazione di un metodo può essere
non trascurabile
„ se bisogna implementare un algoritmo in modo molto
efficiente, può essere opportuno limitare il numero di
attivazioni di metodi (ad esempio, implementando
algoritmi ricorsivi mediante metodi iterativi)
Complessità
Fondamenti di informatica