Ricerca di Massimo e Minimo di un Array

Ricerca di Massimo e Minimo di un Array
Problema. Trova l’elemento di valore massimo e quello di
valore minimo in un array S[1 . . . n]. Quanti confronti tra
elementi di S occorrono?
Algoritmo semplice.
max = S[1]
for i = 2 to n do
if S[i] > max then max = S[i]
min = S[1]
for i = 2 to n do
if S[i] < min then min = S[i]
(# di confronti tra elementi di S) = 2(n − 1)
(si puó migliorare a 2n − 3)
Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 1/18
Uso di Divide et Impera nel calcolo del max e min
1. Se l’array ha ≤ 2 elementi, risolvi il problema
direttamente, altrimenti dividi l’array in metá
2. Trova il massimo ed il minimo in ciascuna metá
ricorsivamente
3. Ritorna il massimo dei due massimi ed il minimo dei due
minimi trovati al passo 2.
M AX M IN(x, y) % ritorna il max e min in S[x . . . y]
if y − x ≤ 1 then
return(max(S[x], S[y]), min(S[x], S[y]))
else
(max 1, min 1)=(M AX M IN(x, ⌊(x + y)/2⌋)
(max 2, min 2)=(M AX M IN((x + y)/2⌋ + 1, y )
return(max(max 1, max 2), min(min 1, min 2))
Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 2/18
Analisi di MaxMin
Assumiamo che n sia potenza di 2 e sia T (n) il numero di
confronti effuato da MaxMin
MaxMin chiama se stessa 2 volte su array di taglia n/2,
eseguendo quindi 2T (n/2) confronti
MaxMin effettua un altro confronto per calcolare il max a
partire da (max 1, max 2) ed un altro per calcolare il min a
partire da (min 1, min 2).
In totale
T (n) =



 1



se n
=2
2T (n/2) + 2 altrimenti
Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 3/18
T (n) =? (ricordando che n = 2i )
T (n) = 2T (n/2) + 2
= 2(2T (n/4) + 2) + 2
= 4T (n/4) + 4 + 2
= 8T (n/8) + 8 + 4 + 2
= 2i−1 T (n/2i−1 ) +
i−1
X
2j
j=1
log n−1
= 2log n−1 T (2) +
X
2j
j=1
= n/2 + (2log n − 2)
= n/2 + n − 2 = 1.5n − 2
Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 4/18
Nota
Sia nel caso del M ERGE S ORT che dell’algoritmo M AX M IN abbiamo
diviso il problema originale in due sottoproblemi di taglia uguale. É
stato un caso?
NO. In generale, la tecnica Divide et Impera produce algoritmi
efficienti quando riusciamo a dividere il problema originale in
sottoproblemi di taglia piú o meno simile, bilanciando quindi il lavoro
da fare tra le varie chiamate ricorsive dell’algoritmo sui rispettivi
sottoproblemi.
Inoltre, in generale, nella tecnica D&I si ricorre su sottoproblemi di
taglia sempre minore, fin quando essi non abbiano una taglia
talmente piccola da essere risolti direttamente, ovvero attraverso
una qualche semplice procedura non ricorsiva.
Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 5/18
Applicazione di D&I al Calcolo di Potenze
Problema: Dato un numero a ed un intero positivo n,
calcolare an usando il minor numero di moltiplicazioni.
Algoritmo semplice
S LOW P OWER(a, n)
x=a
for i = 2 to n do
x=x·a
return x
(# di moltiplicazioni effettuate da S LOW P OWER(a, n))=n − 1
Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 6/18
Applicazione di D&I al Calcolo di Potenze
Per applicare D&I osserviamo che
an = a⌊n/2⌋ · a⌈n/2⌉
Inoltre, a⌈n/2⌉ = a⌊n/2⌋ se n é pari, e a⌈n/2⌉ = a · a⌊n/2⌋ se n é
dispari.
Algoritmo veloce
FAST P OWER(a, n)
if
n = 1 return a
else
x = FAST P OWER(a, ⌊n/2⌋)
if n é pari return x · x
if n é dispari return x · x · a
Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 7/18
Complessitá di FAST P OWER(a, n)
FAST P OWER(a, n)
if
n = 1 return a else
x = FAST P OWER(a, ⌊n/2⌋)
if n é pari return x · x
if n é dispari return x · x · a
T (1) = 0. In generale
T (n) ≤ T (n/2) +2. Assumendo n = 2k , abbiamo
T (n) ≤ T (n/2) + 2 ≤ T (n/4) + 2 + 2 = T (n/4) + 4
≤ . . . ≤ T (n/2k ) + 2k = 2 log n
Quindi con D&I possiamo calcolare an usando O(log n)
moltiplicazioni, contro le n − 1 dell’algoritmo semplice.
Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 8/18
Applicazione di D&I alla Moltiplicazione di Interi
Come si moltiplicano due numeri?
abbiamo appreso che si fà così:
In seconda elementare
27×
32 =
54
81
864
Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 9/18
e vale anche per numeri in binario!
Qual è la complessità dell’algoritmo che
011011× moltiplica, nel modo “elementare”, due
110110 = numeri di n bits ciascuno?
000000
Vi sono n moltiplicazioni parziali, in ciascuna delle quali si moltiplicano n bits, in
011011
2
totale
O(n
) moltiplicazioni di bit
011011
Contando anche le addizioni, altre
O(n2 ), otteniamo che l’algoritmo elementare effettua O(n2 ) operazioni per
moltiplicare due numeri di n bit
000000
011011
011011
10110110010
Possiamo fare meglio?
Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 10/18
Applicazione di D&I alla Moltiplicazione di Numeri
Ricordiamo il paradigma centrale della tecnica D&I: “dividi il
problema in sottoproblemi, risolvi i sottoproblemi, e combina le
relative soluzioni in una soluzione per il problema globale”.
In ossequio a tale paradigma, dato un numero di n bit x iniziamo a
dividerlo in bit di “ordine alto” e bit di “ordine basso”, ovvero
x = x1 · 2n/2 + x0
. Esempio:
x = 11001110
| {z } 00111001
| {z } = |1100111000000000
{z
} + |0000000000111001
{z
}
x1
x0
x1 ·28
x0
Facciamo lo stesso con l’altro numero y da moltiplicare, scrivendo
y = y1 · 2n/2 + y0
Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 11/18
avendo scritto x = x1 · 2n/2 + x0 e y = y1 · 2n/2 + y0 otteniamo
x · y = (x1 · 2n/2 + x0 ) · (y1 · 2n/2 + y0 )
= x1 · y1 · 2n +(x1 · y0 + x0 · y1 ) · 2n/2 +x0 · y0
(1)
L’ uguaglianza (1) riduce il problema di calcolare un singolo
prodotto x · y di due numeri di n bit ciascuno, nel problema di
calcolare quattro prodotti (x1 · y1 , x1 · y0 , x0 · y1 , x0 · y0 ) di numeri di
n/2 bit ciascuno.
Abbiamo quindi un abbozzo di algoritmo D&I: “calcola
ricorsivamente ciascuno dei 4 sottoproblemi, di dimensione n/2
ciascuno, e combina i risultati ottenuti usando l’equazione (1)”.
Domanda: usando la tecnica D&I, di quanto abbiamo “stracciato”
l’agoritmo appreso in seconda elementare?
Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 12/18
Vediamo, Dobbiamo calcolare...
x · y = x1 · y1 · 2n +(x1 · y0 + x0 · y1 ) · 2n/2 +x0 · y0
Detto T (n) il numero di operazioni che il nostro algoritmo impiega
per calcolare il prodotto di due numeri di n bit, avremo che
T (n) = 4T (n/2) + dn
(ricordiamo: abbiamo 4 sottoproblemi di taglia n/2 ciascuno)
Questa è una equazione del tipo T (n) = aT (n/c) + dn, con
a = 4 > 2 = c, che risolta con i metodi visti nella lezione scorsa
dà...
T (n) = O(nlogc a ) = O(n2 )!!!
cioè la stessa complessità dell’algoritmo elementare.
Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 13/18
Dove abbiamo fallito?
Abbiamo tentato di risolvere un problema di taglia n
mediante 4 chiamate ricorsive a risoluzioni di problemi di
taglia n/2
Possiamo diminuire il numero di chiamate ricorsive per
calcolare
x · y = x1 · y1 · 2n +(x1 · y0 + x0 · y1 ) · 2n/2 +x0 · y0 ?
Vediamo...
Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 14/18
Occorre calcolare x · y = x1 · y1 · 2n +(x1 · y0 + x0 · y1 ) · 2n/2 +x0 · y0
x1 y1 + x1 y0 + x0 y1 + x0 y0 = (x1 + x0 ) · (y1 + y0 )
che ci possiamo calcolare con una singola chiamata ricorsiva al
prodotto del numero (x1 + x0 ) per (y1 + y0 ).
Partiamo da
Se ora ci calcoliamo x1 y1 (con una chiamata ricorsiva) e x0 y0 (con
un’altra chiamata ricorsiva), ci possiamo calcolare
x1 y0 + x0 y1 = (x1 + x0 ) · (y1 + y0 ) − x1 y1 − x0 y0
semplicemente sottraendo x1 y1 e x0 y0 da (x1 + x0 ) · (y1 + y0 ).
Quindi, con solo 3 chiamate ricorsive ci siamo calcolati tutti i termini
che compaiono nel prodotto
x · y = x1 · y1 · 2n +(x1 · y0 + x0 · y1 ) · 2n/2 +x0 · y0
e possiamo calcolarci tranquillamente x · y
Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 15/18
Algoritmo D&I per il calcolo di prodotti:
Recursive-Multiply(x, y)
Scrivi x = x1 · 2n/2 + x0
y = y1 · 2n/2 + y0
Calcola x1 + x0 e y1 + y0
p = Recursive-Multiply(x1 + x0 , y1 + y0 )
x1 y1 = Recursive-Multiply(x1 , y1 )
x0 y0 = Recursive-Multiply(x0 , y0 )
return x1 y1 · 2n + (p − x1 y1 − x0 y0 ) · 2n/2 + x0 y0
Detta T (n) la complessità di Recursive-Multiply(x, y)
per numeri x e y n bits, avremo
T (n) = 3T (n/2) + dn
Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 16/18
Risolviamo T (n) = 3T (n/2) + dn
Ricordiamo che equazioni del tipo
T (n) = aT (n/c) + dn,
con a > c
hanno come soluzione T (n) = O(nlogc a ), il che, nel nostro
caso ci dice
T (n) = O(nlogc a ) = O(nlog2 3 ) = O(n1.59 )
migliorando, finalmente, un algoritmo da seconda
elementare...
Per avere una idea del miglioramento, osserviamo che
n2 ≈ 17 × nlog2 3 per n = 1000, n2 ≈ 309 × nlog2 3 per
n = 1000000, e n2 ≈ 5436 × nlog2 3 per n = 1000000000.
Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 17/18
più seriamente...
Idee simili possono essere usate per ottenere algoritmi efficienti per
calcolare (rapidamente) la convoluzione f ∗ g di due funzioni f e g,
P∞
definita come (f ∗ g)[n] = m=−∞ f [m]g[n − m].
Tale operazione é cruciale in molti campi dell’Analisi dei Segnali ed ha
applicazioni pratiche importantissime (TAC, riconoscimento vocale,
compressione di immagini JPEG, riconoscimento automatico di immagini,
trattamento di segnali musicali, astronomia, ...)
Un algoritmo per il calcolo della convoluzione funzioni basato sulle idee
che abbiamo presentato ha rivoluzionato il campo dell’Analisi dei Segnali.
Tale algoritmo, chiamato Fast Fourier Transform (FFT), fu scoperto da
Cooley e Tukey nel 1965 (sebbene Gauss nel 1805 ne avesse pensato
uno simile).
L’algoritmo FFT é entrato nella lista “Top Ten Algorithms of the Century”,
compilata dalla rivista Computing in Science and Engineering nel
Gennaio 2000.
Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 18/18