Teoria dei numeri e Crittografia: lezione del 28 marzo 2011 Complessità di alcuni algoritmi aritmetici a) Nella lezione precedente abbiamo costruito un algoritmo Somma(n.m) che calcola la somma n+m di due numeri naturali n, m dati in input. Il suo schema è il seguente: 1) input n=(ax-1ax-2…..a1a0)2 , m=(by-1by-2…..b1b0)2 con x=L(n) y=L(m) 2) se x>y si pone by=by+1=…=bx-1=0 3) si inizializza c=0 4) per i=0,1…,x-1 si calcola BitSum(ai,bi,c)=(t,c1) e si pone zi=t (bit della somma n+m), c=c1 (valore aggiornato del carry) 5) se c=0 (ultimo carry) si esce con output n+m=(zn-1…..z1z0)2; se invece c=1 si esce con output n+m=(1zn-1…..z1z0)2 Come si vede, oltre ad operazioni trascurabili (come la scrittura di bits), in tale algoritmo si eseguono (nel ciclo dell’istruzione 3)) x operazioni elementari (dove x è la lunghezza maggiore dei due addendi), dunque TA(x)=x e l’algoritmo ha complessità polinomiale (lineare). Per le considerazioni che faremo sulla complessità dei prossimi algoritmi, è utile citare alcune proprietà della lunghezza (binaria) della somma e del prodotto di due generici numeri naturali a, b: 1) se k=max(L(a),L(b)) allora L(a+b) = k oppure L(a+b) = k+1 2) L(ab) = L(a)+L(b)-1 oppure L(ab) = L(a)+L(b) Infatti, ponendo k=L(a), h=L(b) e supponendo per esempio h k = max(L(a),L(b)) da: 2k-1 a < 2k 2h-1 b < 2h segue: 2k-1< 2k-1+2h-1 a+b< 2k+2h 2k+2k = 2k+1 2k-12h-1= 2k+h-2 ab< 2k2h = 2k+h e ricordando che L(a+b)=s è il più grande dei numeri naturali che soddisfano 2s-1 a+b, mentre L(ab)=t è il più grande dei numeri naturali che soddisfano 2t-1 ab, si conclude che k s< k+2, e che k+h-1 t < k+h+1, ossia la 1) e la 2). b) Costruiamo un algoritmo A=Diff(n,m) per calcolare la differenza n-m di due numeri naturali n,m (con n>m in modo che la differenza n-m sia un numero naturale) dati in input: se x=L(n),y=L(m) sono rispettivamente le lunghezze binarie, si avrà certamente xy (in modo che x=max(x,y) ed x sarà dunque l’argomento della funzione TA(x)). Si può ricondurre il caso a quello dell’algoritmo Somma(n,m) con il seguente ragionamento: consideriamo il numero binario m1 ottenuto da m sostituendo ogni bit 0 con 1 e ogni bit 1 con 0 e poi aggiungendo a sinistra un numero (x-y) di bits =1 (osserviamo che m1 è allora un numero di lunghezza x tale che la somma m+m1 nella sua rappresentazione binaria abbia tutti i suoi x bits =1 cioè m+m1=2x-1). Si ha dunque m+(m1+1)=2x (si dice anche che m1+1 è il “complemento a 2 del numero m”) da cui: n-m=[n+(m1+1)]-2x. Per ottenere la differenza n-m basta allora sommare n con il complemento a 2 di m e poi sottrarre 2x al risultato. Esaminiamo la complessità di tale algoritmo: - costruire m1 comporta solo la scrittura di alcuni bits =1 quindi è trascurabile - calcolare m1+1 (complemento a 2 del numero m) comporta la somma di due addendi dei quali il maggiore (che è m1) ha lunghezza x, dunque comporta x operazioni elementari con l’algoritmo Somma(m1,1) - il numero (m1+1) ha lunghezza x (infatti per le regole sulla lunghezza della somma si ha L(m1+1)=x oppure L(m1+1)=x+1, ma m1+1=2x-m<2x quindi la seconda possibilità si esclude) dunque per calcolare la somma n+(m1+1) (addendi di lunghezza x) si eseguono x operazioni elementari (con l’algoritmo Somma(n, m1+1)) - la somma n+(m1+1) ha lunghezza x+1 (essendo x la lunghezza degli addendi, la sua lunghezza può essere x oppure x+1 ma n+(m1+1) = n-m+2x > 2x dunque la prima possibilità è esclusa); pertanto sottrarre 2x da n+(m1+1) equivale ad elidere l’ultimo bit =1 a sinistra nel numero n+(m1+1), quindi è trascurabile Si conclude allora che il numero di operazioni elementari eseguite dall’algoritmo é TA(x)=x+x=2x, ossia che O(TA(x))=O(x) e anche questo algoritmo ha complessità polinomiale (lineare). c) Costruiamo un algoritmo A=Prod(n,m) per calcolare il prodotto nm di due numeri naturali n,m dati in input: supponiamo che x=L(n),y=L(m) siano le lunghezze binarie degli addendi, e che si abbia xy (in modo che x=max(x,y) ed x sarà dunque l’argomento della funzione TA(x)). L’algoritmo di prodotto si può eseguire con gli stessi metodi “scolastici” che si usano per moltiplicare numeri naturali rappresentati in base 10: si moltiplica il numero n per ogni cifra del numero m (per le cifre =0 si salta questo passaggio) shiftando ad ogni passo a sinistra il risultato di tale prodotto (per esempio formalmente aggiungendo alcune bits =0 a destra) e infine si sommano tali prodotti per ottenere il risultato finale. Il vantaggio di utilizzare la rappresentazione binaria, e quindi solo le cifre 0,1, consiste nel fatto che il prodotto di n per 1 (unica cifra binaria non nulla) non comporta un vero calcolo ma solo una “copia” del numero n, ed è quindi trascurabile nel calcolo della complessità. Vediamo un esempio: siano dati n=(1011011)2 , m=(101011)2 (quindi x=L(n)=7,y=L(m)=6) e calcoliamo il prodotto nm seguendo i vari passi dell’algoritmo: 1011011 x 101011 1011011 (si moltiplica n per l’ultima cifra 1 di m, ricopiando n) 1011011 x 101011 1011011 (si moltiplica n per la penultima cifra 1 di m, ricopiando n, ma shiftato 10110110 a sinistra di 1 posizione, con l’aggiunta di 1 bit =0 a destra ) 1011011 x 101011 1011011 (si dovrebbe moltiplicare n per la terzultima cifra 0 di m, ma essendo 0 10110110 il risultato, questo passo si salta, e si moltiplica n per la quartultima 1011011000 cifra 1 di m, ricopiando n, ma shiftato a sinistra di 3 posizioni, con l’aggiunta di 3 bits =0 a destra ) 1011011 x 101011 1011011 (si dovrebbe moltiplicare n per la quintultima cifra 0 di m, ma questo 10110110 passo si salta, e si moltiplica n per la quartultima cifra 1 di m, 1011011000 ricopiando n, ma shiftato a sinistra di 5 posizioni, con l’aggiunta di 101101100000 5 bits =0 a destra ) Per ottenere il risultato finale si dovrebbero ora sommare i 4 numeri ottenuti nelle 4 righe. A tale scopo possiamo usare l’algoritmo Somma(n,m), che permette di sommare 2 numeri alla volta: sommiamo la prima riga con la seconda, poi sommiamo il risultato con la terza riga ed infine sommiamo il risultato con la quarta riga. L’esempio può essere utile per calcolare la complessità dell’algoritmo: gli unici passi non trascurabili sono quelli in cui si sommano le righe (2 alla volta) utilizzando più volte l’algoritmo Somma. Il caso peggiore si ha quando le righe sono il maggior numero possibile (al massimo il numero delle righe è y) e precisamente quando nessuna cifra di m è =0 (di modo che nessun passo viene saltato) e quando x=y, di modo che il numero di righe sia =x. In questo caso, se le righe sono i numeri a1, a2, …., ax si dovranno sommare a 2 a 2 utilizzando più volte l’algoritmo Somma: z1=Somma(a1, a2), z2=Somma(z1, a3), z3=Somma(z2, a4),………., zx-1=Somma(zx-2, ax) e l’output dell’algoritmo sarà nm = zx-1. Le lunghezze delle righe sono crescenti (perché si aggiungono bits =0 a destra): L(a1)=x, L(a2)=x+1, L(a3)=x+2,…., L(ax)=x+x-1=2x-1 Ricordando la regola della lunghezza della somma, si avrà (nel caso peggiore): L(z1)=x+1, L(z2)=x+2, L(z3)=x+3,…., L(zx-1)=x+x-1=2x-1 Dunque (ricordando quanto sappiamo sull’algoritmo Somma) il numero di operazioni elementari eseguite dall’algoritmo (nel caso peggiore) sarà: TA(x) = (x+1)+(x+2)+(x+3)+ …. + (2x-1)= x(x-1)+(1+2+3+….+(x-1))= = x(x-1)+(x-1)x/2 = (3x2-3x)/2 (polinomio di 2° grado in x) Si può concludere che O(TA(x))=O(x2), e che l’algoritmo ha complessità polinomiale (quadratica).