Algoritmi e Laboratorio a.a. 2009-10 Lezioni prof. Elio Giovannetti

09/10/09
Esercizio preliminare (Programmazione 1)
Università di Torino – Facoltà di Scienze MFN
Corso di Studi in Informatica
Curriculum SR (Sistemi e Reti)
Algoritmi e Laboratorio a.a. 2009-10
Lezioni
prof. Elio Giovannetti
Lezione 6– Segmento di somma massima
versione 09/10/09
Dato un array di interi rappresentante una sequenza di
temperature giornaliere, trovare la lunghezza del più lungo
periodo di gelo, cioè della più lunga sequenza consecutiva di
temperature negative o zero.
INVARIANTE:
• la variabile max contiene la lunghezza massima di gelo nel
periodo a[0 .. i-1]
• la variabile lun contiene la lunghezza del periodo corrente di
gelo, cioè del periodo di gelo che arriva fino ad a[i – 1].
i
Quest' opera è pubblicato sotto una Licenza Creative Commons
Attribution-NonCommercial-ShareAlike 2.5.
a:
AlgELab-09-10 - Lez. 6
Definizioni preliminari
Origine storica del problema
• segmento non vuoto di una sequenza non vuota a0, a1, ..., an-1
è una qualunque sottosequenza ak, ak+1 ..., ah di elementi
consecutivi, con 0 ≤ k ≤ h ≤ n-1;
Il problema nacque in una procedura di pattern-matching
bidimensionale progettata da Ulf Grenander della Brown
University. In essa, la sotto-matrice di somma massima forniva
una stima della massima probabilità di in certo tipo di pattern
in un'immagine digitalizzata.
Poiché l'algoritmo ingenuo cubico era troppo lento, Grenander
semplificò il problema considerando una sola dimensione, e
scoprì un algoritmo
g
quadratico. Nel 1977 descrisse il problema
a M. Shamos, che in una notte trovò un algoritmo di
complessità n log n.
Poco tempo dopo, Shamos presentò il problema in un
seminario alla Carnegie-Mellon University cui assisteva il
matematico Jay Kadane, il quale nel tempo di un minuto trovò
l'algoritmo lineare. L'algoritmo non fu poi utilizzato direttamente, ma la tecnica su cui si basa ha avuto un forte impatto sui
metodi di progettazione degli algoritmi.
• in particolare, per una sequenza realizzata come array:
segmento non vuoto di un array non vuoto a è una qualunque
sottosequenza a[k], a[k+1] ..., a[h] di elementi consecutivi, con
0 ≤ k ≤ h < a.length;
• useremo la notazione abbreviata a[k .. h] per indicare il
segmento a[k], a[k+1] ..., a[h] (cioè il segmento da k ad h,
estremi compresi); potremo scrivere a[k] invece di a[k .. k];
• useremo talvolta la notazione abbreviata somma(a[k .. h]) per
indicare la somma degli elementi del segmento, cioè:
a[k] + a[k+1] + ... + a[h]
(Nota Bene: tali notazioni NON sono del linguaggio Java).
AlgELab-09-10 - Lez. 6
Problema: ricerca del segmento di somma massima.
Si definisca una procedura la quale, preso un array (non vuoto)
di interi, restituisca la somma degli elementi del segmento (non
vuoto) di somma massima.
Esempi
array {-7, 4, -8, 3, 4, -2, 6, -10, 1, 3, -9, 9}:
il segmento di somma massima è quello sottolineato,
e la sua somma è 11;
AlgELab-09-10 - Lez. 6
Il segmento di somma massima: soluzione ingenua
• Si calcolano le somme di tutti i possibili segmenti, tenendo
memoria della massima.
array {4, 3, 5, 0, 8, 1}
il segmento di somma massima è l’intero array,
e la sua somma è 21;
array {-4, -3, -5, -8}
il segmento di somma massima è quello costituito dal solo
elemento sottolineato, e la sua somma è -3.
AlgELab-09-10 - Lez. 6
AlgELab-09-10 - Lez. 6
1
09/10/09
Il segmento di somma massima
Soluzione naturale:
public static int sss(int[] a) {
int n = a.length;
int max = a[0];
for(int i = 0; i < n; i++) {
calcola la somma di a[i .. j] per ogni j compreso fra i ed n-1
e confrontala con max, eventualmente aggiornando max;
}
}
Complessità dell'algoritmo (numero di passi)
n iterazioni del ciclo esterno:
prima iterazione:
n iterazioni del ciclo interno;
seconda iterazione:
n-1 iterazioni del ciclo interno;
...
i-esima iterazione:
n-i iterazioni del ciclo interno;
Complessità della soluzione naturale
n + (n-1) + (n-2) + ... + 2 + 1 = n(n+1)/2 = Θ(n2)
C'è un algoritmo più efficiente e più semplice !
Per trovarlo, occorre scrivere un invariante un po' sofisticato.
AlgELab-09-10 - Lez. 6
AlgELab-09-10 - Lez. 6
Soluzione ottima: l’idea di base
La soluzione ottima: definizioni ausiliarie.
• Si percorre l'array sommando gli elementi successivi in una
variabile som che contiene quindi la somma corrente, tenendo
in una variabile maxSom la massima somma trovata fino a
quell'istante, e confrontando ad ogni passo som con maxSom
(e aggiornando, se è il caso, maxSom).
• Nota: si sommano anche gl’interi negativi, perché possono
essere seguiti da interi positivi che permettono di raggiungere
una somma più grande. Esempio: 3, 4, -2, 1, 3 somma 9.
• Se però diventa negativa la somma corrente, al passo
successivo bisogna cominciare una nuova somma corrente,
perché ogni somma che contenga il segmento “negativo” sarà
minore della corrispondente somma che non lo contiene.
Esempio: 3, -2, 4, 6, -15, 7, 3, …
con -15 si ottiene la somma -4; allora si riparte da 7
Per semplificare il discorso successivo,
introduciamo alcune definizioni e notazioni ausiliarie.
Sia a l'array considerato. Allora definiamo:
STj = l'insieme delle somme dei segmenti terminanti in j,
cioè l'insieme dei valori somma(a[k .. j]), per 0 ≤ k ≤ j
Esempio:
6
0 1 2 3 4
5 -3 8 -2 6
0 1 2 3 ...
5 -2 -6
si ha ovviamente
ST4 = {8, 12, 6, 4, 9}
som (4) = 12
8 -2 6
9
-3 8 -2 6
14
5 -3 8 -2 6
Che relazione c'è fra
som(j –1) (la massima somma terminante in j-1) e
som(j)
(la massima somma terminante in j) ?
Le somme (di segmenti) terminanti nella posizione j sono:
• il valore a[j];
• le somme terminanti in j-1 incrementate di a[j]
STj-1
STj
j-1 a[j]
j-1
a[j]
a[j]
ST2 = {-6, -8, -3}
som (2) = -3
a[j]
a[j]
s ∈ STj ⇒ s ≤ som(j)
AlgELab-09-10 - Lez. 6
12
AlgELab-09-10 - Lez. 6
Definizioni ausiliarie (continua)
0 1 2 3 4
5 -2 -6 4 8
-2 6
ST4 = {6, 4, 12, 9, 14}
AlgELab-09-10 - Lez. 6
som(j) = massimo dell'insieme STj, cioè
maxst(j) è la più grande delle somme dei segmenti terminanti
iiiiiiiiiiiiiiiibbnella posizione j (inclusa), o
massima somma terminante in j.
Esempi:
0 1 2 3 4
ST4 = {6, 4, 12, 9, 14}
5 -3 8 -2 6
som (4) = 14
6
4
AlgELab-09-10 - Lez. 6
2
09/10/09
Qual è la massima somma terminante in j ?
a[j] ?
j-1
≤
a[j] ≤
≤
a[j] ≤
som(j-1)
≤
a[j] ≤
Qual è la massima somma terminante in j ?
Quale sia il maggiore fra som(j-1) + a[j] e a[j]
dipende dal segno di som(j-1).
som(j-1) a[j]
Infatti:
se B > 0, si ha B + A > A
se B ≤ 0, si ha B + A ≤ A
a[j] ≤
≤
• som(j-1) + a[j] è la massima somma terminante in j composta
di almeno due elementi, perché, quali che siano i segni di A,
idi B e di C, si ha:
A < B ⇒ A + C < B + C;
Analogamente:
se som(j-1) > 0,
allora a[j] + som(j-1) > a[j]
se som(j-1) ≤ 0,
allora a[j] + som(j-1) ≤ a[j],
• a[j] è l’unica altra somma terminante in j.
La max. somma terminante in j è quindi la maggiore delle due.
Quale delle due è la maggiore ?
AlgELab-09-10 - Lez. 6
AlgELab-09-10 - Lez. 6
Caso som(j –1) > 0: esempi.
In conclusione:
a[j] ≤
j-1
som(j-1) + a[j] se som(j-1) > 0
≤
a[j] ≤
≤
a[j] ≤
som(j-1)
≤
som(j) =
a[j] ≤
a[j] ≤
≤
a[j]
se som(j-1) ≤ 0
0
1
2
3
4
5
som(5) = som(4) +a[5]
5 -2 -6 4 8
ST4 = {8, 12, 6, 4, 9}
som(4) = 12
5 -2 -6 4 8 10
ST5 = {10, 18, 22, 16, 14, 19}
som(5) = 12 + 10 = 22
5 -2 -6 4 8 -5
ST5 = {-5, 3, 7, 1, -1, 4}
som(5) = 12 + (-5) = 7
AlgELab-09-10 - Lez. 6
AlgELab-09-10 - Lez. 6
Caso som(j –1) <= 0: rappresentazione pittografica
Caso som(j-1) <= 0: esempio
j-1
≤
≤
≤
0 1 2 3 ...
5 -2 -6 10
≤0
som(j-1)
0 1 2 3 ...
5 -2
2 -6
6 -4
4
a[j]
[j] ?
a[j] ≤
a[j] ≤
≤0
som(j-1) a[j]
ST2 = {-6, -8, -3}
som(2) = -3
ST3 = {10, 4, 2, 7}
som(3) = 10
≤
a[j] ≤
som(j)
som(j-1) a[j]
≤
a[j] ≤
a[j]
ST2 = {{-6
6, -8
8, -3}
3}
som(2) = -3
ST3 = {-4, -10, -12, -7}
som(3) = -4
Attenzione: come si è visto anche negli esempi, il segno di a[j]
è irrilevante per stabilire il valore di som(j); decisivo è solo il
segno di som(j-1).
AlgELab-09-10 - Lez. 6
AlgELab-09-10 - Lez. 6
3
09/10/09
La soluzione ottima.
L'invariante
L’insieme di tutti i segmenti possibili è ovviamente dato da:
• il segmento costituito dal solo primo elemento a[0];
• i (due) segmenti terminanti all’indice 1: a[0 .. 1] e a[1].
– som(1) è la massima delle somme di tali segmenti;
• i (tre) segmenti terminanti all’indice 2: a[0 .. 2], a[1 .. 2], a[1];
– som(2) è la massima delle somme di tali segmenti;
• i (quattro) segmenti terminanti all’indice
all indice 3;
•…
La somma massima è quindi il massimo di
som(0), som(1), ..., som(n-1),
cioè è il massimo dei massimi !
Ma, come abbiamo visto, conoscendo som(j-1) è possibile
ricavare som(j) senza calcolare tutte le somme dei segmenti
terminanti all'indice j.
Nel ciclo si dovranno tenere due variabili contenenti rispettiv.:
• la massima somma contenuta in a[0 .. j-1];
• la massima somma terminante all' indice j-1.
L'invariante è quindi:
maxsom = max{ somma(a[h..k]) | 0 ≤ h ≤ k < j } ⋀
som
= max{ somma(a[i..j-1]) | 0 ≤ i < j }
oppure, con formulazione alternativa equivalente:
som = som(j-1) ⋀ maxSom = max{som(k) | 0 ≤ k < j }
j
9 31 5 7
6 -2 10 3 -1
52
maxSom
16
som
AlgELab-09-10 - Lez. 6
AlgELab-09-10 - Lez. 6
Il corpo del ciclo, il test, l'inizializzazione
Una lieve variante del problema: segmenti vuoti.
INVARIANTE:
som = som(j-1) ⋀ maxSom = max somma in a[0..j-1].
CORPO DEL CICLO:
• aggiorno som ricavando som(j) a partire da som(j-1):
if(som > 0) som += a[j];
else som = a[j];
• confronto som con il massimo maxSom,
aggiornando maxSom se necessario:
if(som > maxSom) maxSom = som;
• infine incremento j:
j++;
TEST: j < a.length;
INIZIALIZZAZIONE: som = maxSom = a[0];
Definizioni preliminari
• segmento (eventualmente vuoto) di un array (eventualmente
vuoto) a è la sequenza vuota, oppure una qualunque
sottosequenza non vuota a[k], a[k+1] ..., a[h] di elementi
consecutivi, con 0 ≤ k ≤ h < a.length;
• indichiamo con a[] il segmento vuoto di a;
• indichiamo con a[k .. h] il segmento a[k], a[k+1] ..., a[h] (cioè il
segmento da k ad h, estremi compresi); stabiliamo inoltre che
per k > h la notazione a[k .. h] sia sinonima di a[] (seg. vuoto)
• esempio: i segmenti di un array a di tre elementi sono:
a[], a[0], a[1], a[2], a[0..1], a[1..2], a[0..2].
j = 1;
Nota: Invece di un ciclo while, si può utilizzare un ciclo for.
AlgELab-09-10 - Lez. 6
AlgELab-09-10 - Lez. 6
Una lieve variante del problema.
Nota
Si definisca una procedura la quale, preso un array di interi,
restituisca la somma degli elementi del segmento (eventualmente vuoto) di somma massima. La somma degli elementi di
un segmento vuoto sia per definizione uguale a 0.
Esempi
array {-7, 4, -8, 3, 4, -2, 6, -10, 1, 3, -9, 9}:
il segmento di somma massima è quello sottolineato,
e la sua somma è 11;
Il problema si risolve con un algoritmo strettamente analogo a
quello per il problema originale, percorrendo l'array una volta
sola, e quindi senza testare prima se si è nel caso di elementi
tutti negativi.
array {4, 3, 5, 0, 8, 1}
il segmento di somma massima è l’intero array,
e la sua somma è 21;
array {-4, -3, -5, -8}
il segmento di somma massima è il segmento vuoto,
la cui somma è 0 per definizione.
AlgELab-09-10 - Lez. 6
AlgELab-09-10 - Lez. 6
4