Un algoritmo è una procedura ben definita per risolvere un problema: una sequenza di passi che, eseguiti da un esecutore, portano alla soluzione del problema.
La sequenza di passi che definisce un algoritmo deve essere descritta in modo finito.
Viene di seguito riportato un semplice algoritmo che calcola il numero delle coppie (i,j) di numeri compresi tra 1 e N, tali che i ≤ j.
int Count1(int N)
sum = 0
for i = 1 to N
for j = i to N
sum = sum + 1
return sum
Il modello RAM (Random-Access Memory) è un modello di computazione che consiste di:
Il modello RAM è un’astrazione dei moderni computer.
Un problema di conteggio
Algoritmo che calcola il numero delle coppie (i,j) di numeri compresi tra 1 e N, tali che i ≤ j.
int Count1(int N)
1 sum = 0
2 for i = 1 to N
3 for j = i to N
4 sum = sum + 1
5 return sum
Esempio
Input: N=4
(1,1), (1,2), (1,3), (1,4), (2,2), (2,3), (2,4), (3,3), (3,4), (4,4)
Output = 10
Come si calcola il tempo di esecuzione di un algoritmo?
Vogliamo calcolare il numero di operazioni elementari necessarie a completare la computazione per un valore di N in ingresso (che in questo caso rappresenta la dimensione dell’istanza del problema).
int Count1(int N)
1 sum = 0 (assegnamento a sum)
2 for i = 1 to N
3 for j = i to N
4 sum = sum + 1
5 return sum
(lettura di sum)
Assumiamo che la testa di un ciclo for impieghi 2 operazioni elementari (una per l’incremento o assegnamento e una per la valutazione della condizione di terminazione).
Si noti che la testa di un ciclo for (così come per un ciclo while) viene sempre eseguita una volta un più del suo corpo.
Vogliamo calcolare il numero di operazioni elementari necessarie a completare la computazione per un valore di N in ingresso (che in questo caso rappresenta la dimensione dell’istanza del problema).
int Count1(int N)
1 sum = 0 (assegnamento a sum)
2 for i = 1 to N
3 for j = i to N
4 sum = sum + 1
5 return sum
(lettura di sum)
Sommando tra di loro i termini a destra delle frecce otteniamo la seguente funzione di T(N) per il tempo di esecuzione dell’algoritmo:
Poiché è evidente che il numero di incrementi di sum, fissato i, è pari a N-i+1, anche la seguente versione dell’algoritmo risolve correttamente il problema:
int Count2(int N)
1 sum = 0
(assegnamento a sum)
2 for i = 1 to N
3 sum = sum + (N-i+1)
4 return sum
(lettura di sum)
Assumendo che la riga 3 corrisponda a 7 operazioni elementari (3 operazioni aritmetiche, 3 letture in memoria e una scrittura).
Smmando tra di loro i termini a destra delle frecce otteniamo la seguente funzione di T(N) per il tempo di esecuzione dell’algoritmo:
Poiché, nell’algoritmo precedente il valore di sum è inizializzata a 0 e, per ogni valore di i (), viene incrementata di N-i+1, è evidente che il valore finale di sum sarà
Quindi il seguente algoritmo risolve il problema correttamente:
int Count3(int N)
1 sum = N*(N+1)/2 (3 op. aritmetiche, una lettura e una scrittura)
2 return sum (lettura di sum)
È, quindi, chiaro che il tempo di esecuzione risulta essere
Tempi di esecuzione di algoritmi con la funzione di tempo indicata nella riga in prima colonna, al variare della dimensione dell'input (supponenedo che un'operazone atomica impieghi 1 ηs=10-9 s).
In figura: Rappresentazione grafica della relazione
Si consideri la funzione .
Per mostrare che dobbiamo individuare due costanti positive
e
tali per cui
Scegliendo e
otteniamo:
Si noti che, per ogni n ≥ 3, , quindi
per ogni n ≥ 3
Per transitività, possiamo quindi affermare che, per ogni n ≥ 3
Concludiamo, quindi, che .
In figura: Rappresentazione grafica della relazione
In figura: Rappresentazione grafica della relazione
Si consideri la funzione .
Per mostrare che dobbiamo individuare due costanti positive
e
tali per cui
Scegliendo e
otteniamo:
Si noti che, per ogni n ≥ 6, , quindi
per ogni n ≥ 6
Per transitività, possiamo quindi affermare che, per ogni n ≥ 6
Concludiamo, quindi, che .
Alcuni teoremi sulle notazioni asintotiche:
Le notazioni asintotiche sono ricondicubili ai seguenti limiti asintotici:
Se allora
Se allora
e
quindi
Se allora
Consideriamo il seguente problema:
Siano dati in input un intero N, con N ≥ 1, e una sequenza A=(a1, a2,…, aN) di N valori interi. Si vuole calcolare il più grande intero S tale che per qualche
Si noti che tutti gli elementi nella sommatoria sono elementi contigui nella sequenza in input.
Esempio:
Input: N = 9 e A = (2,-4,8,3,-5,4,6,-7,2)
Output: 16, cioè la somma dei valori nella sottosequenza (8,3,-5,4,6) di A.
L’algoritmo seguente risolve il problema:
int Max_seq_sum_1(int N, array A[])
maxsum = 0
for i = 1 to N do
for j = i to N do
sum = 0
for k = i to j do
sum = sum + A[k]
maxsum = max(maxsum,sum)
return maxsum
int Max_seq_sum_1(int N, array A[])
1 maxsum = 0
2 for i = 1 to N do
3 for j = i to N do
4 sum = 0
5 for k = i to j do
6 sum = sum + A[k]
7 maxsum = max(maxsum,sum)
8 return maxsum
Eseguendo i calcoli della sommatoria otteniamo:
L’algoritmo seguente risolve il problema incorporando l’osservazione appena fatta:
int Max_seq_sum_2(int N, array A[])
maxsum = 0
for i = 1 to N do
sum = 0
for j = i to N do
sum = sum + A[j]
maxsum = max(maxsum,sum)
return maxsum
Procediamo al calcolo del tempo di esecuzione:
int Max_seq_sum_2(int N, array A[])
1 maxsum = 0
2 for i = 1 to N do
3 sum = 0
4 for j = i to N do
5 sum = sum + A[j]
6 maxsum = max(maxsum,sum)
7 return maxsum
Eseguendo i calcoli della sommatoria otteniamo:
Considerando la figura a fianco, possiamo osservare che le seguenti affermazioni sono vere:
Nel caso 2, è evidente che ogni sottosequenza di A il cui punto d’inizio è inferiore a r+1 (e che termina oltre r) risulta avere una somma inferiore alla sua sottosequenza che parte da r+1.
In questo caso è dunque possibile ignorare tutte queste sottosequenze e considerare solo quelle che iniziano a partire dall’indice r+1.
L’algoritmo seguente risolve il problema sfruttando le osservazioni della slide precedente.
int Max_seq_sum_3(int N, array A[])
maxsum = 0
sum = 0
for j = 1 to N do
if (sum + A[j] > 0) then
sum = sum + A[j]
else
sum = 0
maxsum = max(maxsum,sum)
return maxsum
Procediamo al calcolo del tempo di esecuzione:
int Max_seq_sum_3(int N, array A[])
1 maxsum = 0
2 sum = 0
3 for j = 1 to N do
4 if (sum + A[j] > 0) then
5
5 sum = sum + A[j]
else
6 sum = 0
7 maxsum = max(maxsum,sum)
8 return maxsum
Considerando che sia una esecuzione della linea 5 che una della linea 6 impiegano tempo costante (rispettivamente, 5 e 1 operazioni elementari ), possiamo concludere che, complessivamente, il numero di operaizoni elementari dovute alle esecuzioni del costrutto if then else non è superiore a 10N.
In conclusione, risulta .