Moltiplicazione veloce.
Si vogliono rappresentare interi positivi di lunghezza variabile
N non limitata a priori.
In tal modo, è possibile
memorizzare le N cifre decimali di un qualunque intero in
N celle
del modello RAM, formando così una sequenza di N
caratteri scelti tra {0, 1, 2, ..., 9}.
La controparte,
però, risiede nel fatto che leggere o scrivere un tale intero
richiede
O(N) tempo, in quanto vanno lette o scritte N
celle.
Siano dati due numeri interi positivi A e B di N cifre ciascuno.
Se vogliamo addizionare tali
numeri, possiamo usare l'algoritmo della scuola elementare in O(N) passi.
Tale algoritmo è
chiaramente ottimo al caso pessimo in quanto dobbiamo esaminare le
N cifre sia di A che di B.
Se invece vogliamo moltiplicare i due numeri con l'algoritmo
della scuola elementare,
scopriamo che quest'ultimo impiega O(N2) passi al
caso pessimo.
Vediamo in questa lezione come tale soluzione possa essere migliorata
con il metodo
divide-et-impera, ottenendo un algoritmo che richiede O(Nlog2
3)= O(N1.59) passi
(si veda il paragrafo 5.2.2 in [Lu] per una discussione dettagliata).
Assumiamo che N sia una potenza del 2 e dividiamo A e
B
in due parti uguali di N/2 cifre,
per cui possiamo scrivere:
A = A1 * 10N/2 + A2
,
B = B1 * 10N/2 + B2
.
Ora esprimiamo il loro prodotto in base alle loro metà:
A * B = (A1 * 10N/2 + A2)
* (B1 * 10N/2 + B2)
= A1 B1 * 10N + (A1 B2
+ A2 B1) * 10N/2 + A2 B2.
In altre parole, assumendo di avere delle variabili DIECI_ENNE
e DIECI_ENNE_MEZZI che
contengono rispettivamente i valori
10N e 10N/2,
possiamo calcolare ricorsivamente
la moltiplicazione come segue.
int MOLTIPLICAZIONE(A, B, N) : if (N == 1) { return (A * B); else { // dividi A e B in due meta' uguali A1, A2 e B1, B2 rispettivamente X = MOLTIPLICAZIONE(A1, B1, N/2); Y = MOLTIPLICAZIONE(A2, B2, N/2); Z = MOLTIPLICAZIONE(A1, B2, N/2) + MOLTIPLICAZIONE(A2, B1, N/2); return X * DIECI_ENNE + Z * DIECI_ENNE_MEZZI + Y; }.
L'equazione di ricorrenza corrispondente è data da:
T(1) = costante,
T(N) = 4 T(N/2) + O(N).
Tale equazione può essere risolta con il ``master theorem'' in
[CLR], dove a=4, b = 2,
f(N) = O(N), per cui abbiamo T(N) = O(N2).
Non c'è quindi alcun guadagno, a meno di
eseguire un numero di chiamate ricorsive inferiore ad a=4.
Il miglioramento si ottiene calcolando la variabile Z in una maniera
alternativa che richieda
una sola chiamata ricorsiva invece che due. Si veda il codice
seguente.
int MOLTIPLICAZIONE(A, B, N) : if (N == 1) { return (A * B); else { // dividi A e B in due meta' uguali A1, A2 e B1, B2 rispettivamente X = MOLTIPLICAZIONE(A1, B1, N/2); Y = MOLTIPLICAZIONE(A2, B2, N/2); Z := MOLTIPLICAZIONE_VELOCE(A1+A2, B1+B2, N/2) - X - Y; return X * DIECI_ENNE + Z * DIECI_ENNE_MEZZI + Y; }.
Notiamo che il valore calcolato in Z nella procedura MOLTIPLICAZIONE_VELOCE
è identico
a quello calcolato nella procedura MOLTIPLICAZIONE,
anche se meno intuitivo in quanto
sommiamo le due metà di ogni numero e le moltiplichiamo ricorsivamente.
Esercizio: Dimostrare che il valore di Z è lo stesso in entrambe le procedure.
L'equazione di ricorrenza corrispondente è data da:
T(1) = costante,
T(N) = 3 T(N/2) + O(N),
per cui la soluzione diventa T(N) = O(Nlog2
3)= O(N1.59) passi. Notiamo per completezza
che esiste una maniera abbastanza complicata di eseguire la moltiplicazione
in
O(N log N)
passi (mediante la Trasformata di Fourier, si veda il paragrafo
32.1 in [CLR]).
Passiamo a vedere un esempio con A = 3587 e B= 2831. Il
loro prodotto è A * B = 10154797.
In base all'algoritmo MOLTIPLICAZIONE_VELOCE,
dividiamo A e B in due metà, ottenendo
A1 = 35, A2
= 87
B1 = 28, B2
= 31.
Poi calcoliamo
X = 35 * 28 = 980
Y = 87 * 31 = 2697
Z = (35+87) * (28+31) - X - Y = 3521.
Infine, restituiamo il prodotto (con N=4)
A * B = X * 104 + Z *
102 + Y = 10154797 .