Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
1. Qualità di un algoritmo
Dati uno o più algoritmi che risolvono un problema, confrontarli per individuare il
migliore sulla base di un'analisi qualitativa.
1
INTORNO ALLA COMPLESSITÀ COMPUTAZIONALE 2
1.2.La misura della qualità. Daremo per scontato che gli algoritmi siano cor-
misura della qualità. In certi casi è
retti e cercheremo di assegnare loro una
importante valutre il tempo di esecuzione di un algoritmo, altre volte serve va-
lutare dierenti caratteristiche.
Oltre al fatto che il risultato sia corretto (e lo diamo per scontato) e che possa
essere anche semplice, in certi casi è importante valutare il tempo di esecuzione
dell'algoritmo.
Nel mondo di Internet, se un'immagine non viene visualizzata entro un lasso di
tempo ritenuto ragionevole, non accadranno disastri, ma si avrà un degrado delle
prestazioni. Questo evento, di fatto, renderà il sistema ineciente o poco utilizza-
bile. Siamo pertanto interessati a dare ai nostri algoritmi un'organizzazione interna
tale che il processo corrispondente impieghi il minor numero di risorse durante la
sua esecuzione.
Tra le risorse, particolare attenzione andrà posta a spazio di memoria e tempo di
esecuzione.
Gli algoritmi considerati saranno sempre algoritmi buoni, cioè dovranno essere:
• soluzioni semplici, ecaci e, soprattutto generali;
• facilmente modicabili in caso di necessità;
• indipendenti dal linguaggio di programmazione che si vuole utilizzare.
• il test è svolto sullo stesso computer, dedicato esclusivamente alla sua
esecuzione;
• l'esecuzione del test avviene più volte con dati di input diversi per dimen-
sione e disposizione.
Per ottenere una valutazione adabile, nel seguito della nostra analisi misureremo
il tempo di esecuzione in numero di operazioni (assegnazioni, scambi, confronti,
operazioni di I/O e così via) che l'algoritmo deve compiere per fornire i risultati.
Chiameremo tale numero costo dell'algoritmo.
Costo = 1
2. I costrutti iterativi quali WHILE, DO{ ... } WHILE, hanno un costo pari
alla somma dei costi del test e del corpo del ciclo. In particolare:
il test del ciclo, essendo un'istruzione semplice, ha complessità pari a
uno;
il costo del corpo del ciclo, invece, è dato dalla somma dei costi del-
le singole istruzioni. Naturalmente va tenuto conto di quante volte
ciascuna istruzione viene eseguita. Nel calcolo non includiamo mai le
istruzioni di apertura e di chiusura del corpo del ciclo.
Se il ciclo viene iterato K volte, allora il costo è:
Nei cicli a controllo in testa però, il test viene eseguito K + 1 volte e non K
volte (l'ultimo test, il (K + 1)esimo, è quello di uscita dal ciclo), quindi:
3. I costrutti iterativi come FOR hanno un costo ottenuto dalla somma dei
seguenti costi:
INTORNO ALLA COMPLESSITÀ COMPUTAZIONALE 4
4. Il costrutto di selezione IF... THEN ha costo pari alla somma dei costi
del test (che ormai sappiamo essere uguale all'unità) più quello delle singole
istruzioni semplici contenute all'interno del ramo THEN. Analogamente vie-
ne calcolato il costo dell'istruzione di selezione binaria IF ... THEN ...
ELSE, dove conveniamo di utilizzare il costo massimo tra i due rami.
Costo = 1 + CostoSottoprogramma
Costo = Σ CostoIstruzioniSemplici
Consideriamo il corpo dell'algoritmo Alg1 che calcola la somma dei primi N nu-
meri naturali.
// Algoritmo Alg1
SCRIVI(inserisci un numero)
LEGGI(N)
I ←− 1
Som ←− 0
WHILE (I <= N) DO {
Som ←− Som + I
I ←− I + 1
}
Se N = 10, il costo del solo ciclo è pari a 31, valore ottenuto da (3 * 10) + 1.
Infatti il ciclo è costituito da un test che ha costo uguale a 1 e da un corpo com-
posto da 2 istruzioni semplici, anch'esse di costo unitario: il costo dell'intero ciclo
è, pertanto, uguale a 3. Considerato che il ciclo deve essere eseguito dieci volte, il
costo totale è dato da 3 * 10 = 30. Ma non abbiamo ancora nito! Quando il ci-
clo viene eseguito per la decima volta, la variabile I viene incrementata di un'unità
assumendo, così, il valore 11. La condizione di ciclo viene eseguita un'undicesima
volta in modo da poter vericare la falsità della condizione e, quindi, consentirne
l'uscita. Tale esecuzione comporta l'aggiunta di un'ulteriore unità portando, così,
il valore a 31. Il costo complessivo dell'intero algoritmo (costo del ciclo + costo
delle altre istruzioni) è inne 31 + 5 = 36, includendo anche le 4 istruzioni prima
del ciclo e l'istruzione di scrittura nale.
Generalizzando, quindi, per N qualsiasi il costo è: (3 * N) + 1 + 5 = 3N + 6.
È più corretto parlare di dimensione del problema invece che di dimensione dei
dati in input, perché la dimensione N varia da problema a problema e spesso non è
facilmente individuabile.
Consideriamo, per esempio, il seguente problema: trovare la somma dei primi N
numeri primi. Supponiamo che il valore di input di N sia 100. Qual è la dimensione
N del problema ? Si potrebbe pensare che sia 1 (in quanto viene fornito solo un nu-
mero, 100), ma non è così: se venisse fornito un numero più grande di 100, sarebbe
necessario trovare più numeri primi e fare quindi più operazioni. La dimensione
del problema è quindi il valore dell'unico numero dato, cioè 100 e, in generale, è N.
Ricordiamo che, malgrado possa sembrare banale, spesso non è facile individuare
la dimensione di un problema.
IF (y >= 0)
THEN {
R ←− 1
Z ←− 2
}
Il costo del costrutto IF ... THEN precedente è pari a 3: 1 per il test della con-
dizione +2 per le istruzioni contenute nel ramo THEN.
INTORNO ALLA COMPLESSITÀ COMPUTAZIONALE 7
IF (Y >= 0)
THEN
{ R ←− 1 }
ELSE {
Z ←− 2
P ←− 0
R ←− 2
}
Il costo del costrutto IF ... THEN ... ELSE precedente è pari a 4: 1 per il test
della condizione, 3 per le istruzioni del ramoELSE (si sceglie il ramo ELSE poiché
le istruzioni in esso contenute sono in numero maggiore rispetto a quelle del ramo
THEN).
// ALGORITMO Alg1Bis
LEGGI(N) // (2)
I ←− 1 // (3)
Som ←− 0 // (4)
I ←− I + 1 // (7)
}
SCRIVI(La somma dei primi, N, numeri è, Som) // (8)
PerN = 10 abbiamo:
Costo di Alg1Bis = 5 + CostoCostruttoWHILE =
= 5 + (3 + CostoChiamata + CostoFunzioneSomma) * 10 + 1 =
= 5 + (3 + 1 + 2) * 10 + 1 =
= 66
...dove:
5 è il costo delle istruzioni (1), (2), (3), (4) e (8)
3 è il costo delle istruzioni (5), (6) e (7)
INTORNO ALLA COMPLESSITÀ COMPUTAZIONALE 8
IF (X > 0)
THEN {
IF (Y > 0)
THEN
{ R ←− 1 }
ELSE {
R ←− 2
T ←− 1
}
Z ←− 3
}
Costo istruzione composta = Costo Test IF esterno + Costo Ramo THEN IF
Esterno =
= 1 + Costo IF Interno + Costo Istruzione Semplice =
= 1 + (1 + 2) + 1 = 5
// ALGORITMO A1
int I, N;
I ←− 0
WHILE (I < N) DO {
I ←− I + 1
}
Il ciclo WHILE si compone di un test(I < N) e di un corpo costituito da una
operazione di assegnazione I ←− I + 1. Per ogni test positivo si esegue un'asse-
gnazione, quindi i costi sono:
// ALGORITMO A2
int I, J, N;
I ←− 0
DO
{
I ←− I + 1
J ←− J * 3 + 42
}
WHILE(I >= N);
Il corpo del ciclo si compone di due passi base.
I costi sono:
8. Costo per costrutto iterativo FOR...: calcolo dei primi N numeri pari
int SommaPrimiNumPari(int N) {
int M, I, P;
M ←− 0
FOR(I ←− 1; I <= N; I++) DO {
P ←− 2 * I
M ←− M + P
}
return M
}
// ALGORITMO A3
int I, J, N;
I ←− 0
WHILE (I < 2 * N) DO {
I ←− I + 1
J ←− J * 3 + 4367
}
Il test del ciclo WHILE viene eseguito 2 * N + 1 volte (compreso il test nale) e il
corpo è costituito da due istruzioni. Pertanto i costi sono:
// ALGORITMO A4
int I, J, N;
I ←− 0
WHILE (I < N) DO {
FOR(J ←− 1; J <= N; J++) DO {
SCRIVI(CIAO!)
}
I ←− I + 1
}
Abbiamo un ciclo FOR WHILE, una istruzione di output per ogni ciclo
per ogni ciclo
FOR WHILE. Pertanto, i costi sono:
e una assegnazione per ogni ciclo
• assegnazione esterna: 1 +
• test WHILE: N + 1 +
• ciclo WHILE: N * (
• assegnazione iniziale ciclo FOR: 1 +
• controlli ciclo FOR: N + 1 +
• incrementi ciclo FOR: N +
• corpo ciclo FOR: N +
• assegnazione ciclo WHILE: 1)
Costo A4 = costo inizializzazione esterna al ciclo + CostoCostruttoWHILE
= 1 + ((CostoCostruttoFOR + 1) * N + 1) = 2 + (CostoCostruttoFOR) * N
+ N =
= 2 + (1 + N + 1 + N + N + 1) * N + 1) * N + N =
= 2 + 3N + 3 * N2 + N = 2 + 4 * N + 3 * N2 .
INTORNO ALLA COMPLESSITÀ COMPUTAZIONALE 11
assegnazione esterna: 1 +
assegnazione iniziale FOR esterno: 1 +
test FOR esterno: N + 1 +
corpo ciclo FOR esterno N * (
− assegnazione iniziale ciclo FOR interno: 1 +
− controlli cicloFOR interno: M + 1 +
− incremento ciclo FOR interno: M +
− corpo ciclo FOR interno: 2M ) +
incremento ciclo FOR esterno: N
istruzione RETURN: 1
// ALGORITMO Alg2
int X, N, I;
SCRIVI(Inserisci il valore N)
LEGGI(N)
X ←− N
I ←− 1
WHILE (I <= 5) DO {
X ←− X * N
I ←− I + 1
}
SCRIVI(N)
L'algoritmo Alg2 ha complessità pari a 18 qualunque sia N, poiché richiede 18 ope-
razioni elementari; possiamo esprimere la complessità come TAlg2 (N) = 18. Infatti,
oltre alle istruzioni iniziali e a quella nale, di costo complessivo pari a 5, il ciclo
(che comprende un test e due istruzioni, con costo ciclo = 3) viene ripetuto 4 volte.
Il test viene ripetuto un'altra volta, prima di constatare che la condizione è diven-
tata falsa. Quindi TAlg2 (N) = 5 + 4 * 3 + 1 = 18.
// ALGORITMO Alg4
int Q, X, N, I;
SCRIVI(Inserisci la base)
LEGGI(X)
SCRIVI(Inserisci l'esponente)
LEGGI(N)
Q ←− 1
I ←− N
WHILE (I > 1) DO {
IF ((I MOD 2) = 0)
THEN {
X ←− X * X
I ←− I / 2
}
ELSE {
Q ←− Q * X
i ←− i − 1
}
}
Q ←− Q * X
SCRIVI(Q)
Qui e nel seguito indicheremo con log il logaritmo in base 2, ovvero in generale
log N sta per log2 N.
INTORNO ALLA COMPLESSITÀ COMPUTAZIONALE 14
In questi casi, allora, conviene fare riferimento all'ordine di grandezza della comples-
sità, cioè valutare la complessità per valori molto grandi delle dimensioni del pro-
blema. Si parla di complessità asintotica.
Riprendiamo i graci delle funzioni T(N) degli algoritmi Alg2, Alg3 e Alg4. No-
tiamo che, per valori di N 1 e l'ascissa del punto P1, l'algoritmo Alg4
compresi tra
compie un numero di operazioni inferiore all'algoritmo Alg2, pur avendo comples-
sità asintotica superiore. Al crescere di N, però, è TAlg3 che cresce di più.
lim T(N)
N→∞
che si legge: limite per N che tende a innito della funzione T(N). In denitiva,
si ottiene un'espressione in funzione di N che indica quale è il comportamento
asintotico dell'algoritmo.
INTORNO ALLA COMPLESSITÀ COMPUTAZIONALE 16
Siano f(N) e g(N) due funzioni. Si dice che f(N) è di ordine di grandezza
g(N) e si scrive O(g(N)) e si legge:
O grande di g di N se esiste una costante C > 0 tale che, per tutti i valori nel
dominio di N, f(N) < C * g(N).
Pertanto, dire che T(N) è O(g(N)), signica che g(N) è un limite superiore alla
legge di crescita di T(N), cioè T(N) non cresce più di g(N).
Il graco di T(N) da un certo punto in poi starà, per le ragioni appena esposte,
al di sotto di quello di C * g(N). Diremo inoltre che: