Lezione del 3 marzo 2007
Oggetto di studio del corso: l’insieme N dei numeri naturali (interi >0) e più in generale l’insieme Z
dei numeri interi relativi (positivi, negativi e lo zero).
Applicazioni: crittografia.
Testi consigliati:
Rosen: “Elementary Number theory”
Koblitz: “A Course in Number Theory and Cryptography”
Uno degli argomenti che affronteremo sarà quello relativo agli algoritmi sui numeri naturali:
ognuno di essi è un procedimento composto da un valore in entrata (input) costituito da uno (o più)
numeri naturali, da un numero finito di passaggi (steps), e da un valore in uscita (output) costituito
da uno (o più) numeri (non necessariamente naturali) oppure da una affermazione relativa all’input
(del tipo “si”o “no”, “vero” o “falso” etc…)
Esempi di algoritmi:
a) algoritmo della divisione: input a,b (numeri naturali); steps: con l’usuale procedimento
“scolastico” si trovano gli interi non negativi q,r tali che a=bq+r, con r<b; si esce con output q
(quoziente), r (resto)
b) test di primalità: input n (numero naturale >1); steps: per i=2,3,…,n-1 si verifica se i è divisore
di n; se per qualche i la verifica è positiva si esce con output “n non è primo”, altrimenti si esce con
output “n è primo”
Gli algoritmi si implementano per lo più su un computer, dunque supporremo sempre che i dati
numerici siano rappresentati in base 2 cioè in forma binaria: in particolare ogni numero naturale m
ha una rappresentazione binaria della forma m=at2t+at-12t-1+…+a121+a020 (dove ai sono le cifre
binarie 0,1 dette bits) e in tale caso scriveremo m=(atat-1……a0)2 .
Se vogliamo dare una “stima” del tempo necessario affinché un algoritmo venga eseguito, e quindi
della sua “complessità” di calcolo, dobbiamo scegliere delle operazioni “elementari”, che fungano
da “unità di misura” e mediante le quali si possano costruire tutte le altre operazioni eseguite nei
diversi algoritmi; sceglieremo come operazione elementare l’operazione di somma sui singoli
bits con riporto (carry). Tale operazione, dati 2 bits x,y=0,1 da sommare e fissato un valore c=0,1
del carry, calcola il bit risultato x+y e il nuovo valore c1 del carry, secondo le regole esposte nella
seguente tabella:
x
0
0
1
1
0
0
1
1
y
0
0
0
0
1
1
1
1
c x+y 0
0 0 0
1 1 0
0 1 0
1 0 1
0 1 0
1 0 1
0 0 1
1 1 1
Nella prima e seconda colonna vi sono rispettivamente il valore del bit x e il
valore del bit y; nella terza colonna il valore c del carry prima della somma;
nella quarta il valore del bit somma x+y; nella quinta il nuovo valore c1 del
carry dopo la somma. Per esempio se x=1, y=0 e se il carry (prima della
somma) è c=1, allora dalla quarta riga si ricava il bit somma x+y=0 (quarta
colonna), e il valore aggiornato del carry c1=1 (quinta colonna).
Formalmente l’operazione di somma sui singoli bits con riporto é una funzione:
BitSum: {0,1}x{0,1}x{0,1}  {0,1}x{0,1} definita da BitSum(x,y,c)=(x+y,c1)
e in genere è implementata nell’hardware nel processore centrale (CPU) del computer.
Se trascuriamo il tempo che il computer impiega per altri tipi di operazioni più veloci (accesso alla
memoria, operazioni di shift, confronto fra bits etc..) possiamo ragionevolmente supporre che il
tempo totale dell’algoritmo sia proporzionale al numero di operazioni elementari eseguite (secondo
una costante di proporzionalità che all’incirca coincide con il tempo impiegato dal computer per
eseguire una operazione elementare).
(Nota: E’ ovvio che la nostra scelta dell’operazione BitSum come operazione elementare è dovuta
al fatto che ci occuperemo di algoritmi di tipo “aritmetico”. Per altre categorie di algoritmi potrebbe
essere invece opportuno una scelta diversa: per esempio per gli algoritmi di ordinamento (sorting) le
operazioni elementari potrebbero essere quelle di confronto di bits e di accesso alla memoria per la
lettura e scrittura dei dati da ordinare).
Per fare in modo che la nostra stima sia funzione della “grandezza” dell’input dobbiamo scegliere
un modo per misurare quest’ultima: poiché le operazioni elementari agiscono sui singoli bits, è
naturale ricorrere al concetto di lunghezza di un numero naturale m nella sua rappresentazione
binaria.
Se m=ak-12k-1+ak-22k-2+…+a121+a020==(akak-2……a0)2 (dove ai sono le cifre binarie =0,1) e se
supponiamo che non vi siano cifre nulle (non significative) fra quelle più a sinistra (cioè in pratica
se ak-1=1), il numero k=L(m) di cifre della rappresentazione binaria di m è detto lunghezza
(binaria) di m. In tale caso 2k-1≤m<2k, dunque k-1≤log2m<k ossia k-1=log2m (dove in generale,
dato un numero reale x, indichiamo con x la cosiddetta parte intera di x, ossia il massimo intero
che non supera x). Da ciò si ricava ls formula per il calcolo della lunghezza di m:
k=L(m)=log2m+1.
Chiameremo dimensione dell’input (di un algoritmo) la lunghezza binaria dell’input stesso (se
l’input è costituito da più numeri naturali, sceglieremo la lunghezza massima di tali numeri come
valore della dimensione dell’input).
Dato un algoritmo A potremmo allora definire il valore TimeA(n) come il numero di operazioni
elementari eseguite nell’algoritmo quando l’input ha dimensione n: tale definizione non sarebbe
però univoca perché due diversi input di eguale dimensione n potrebbero dar luogo a un diverso
numero di operazioni elementari nell’esecuzione dell’algoritmo.
Definiremo allora il valore TimeA(n) come il numero di operazioni elementari eseguite
nell’algoritmo quando l’input ha dimensione n nel caso peggiore (cioè quello in cui il numero di
operazioni elementari è il massimo, fra tutti i casi in cui l’input ha dimensione n).