San Vu Ngoc - IRMAR
%matplotlib inline
# inline can be replaced by notebook to get interactive plots
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as img
import matplotlib.cm as cm
import matplotlib.colors as colors
plt.rcParams['figure.figsize'] = (20.0, 12.0) # set figures display bigger
On fait ici le cas $a\geq 0$ et $b\geq 0$. À adapter pour le cas général !
L'algorithme est récursif. Il suffit de programmer deux étapes, qui correspondent à l'initialisation et l'hérédité dans le principe de récurrence.
l'initialisation: En fait, en programmation récursive on appelle plutôt ça la terminaison: c'est ce qui permet d'assurer que le programme ne va pas tourner indéfiniment: "Si b=0, alors le pgcd(a,b) est a"
l'hérédité:
Pour calculer pgcd(a,b)
, avec $a\geq b$, on se ramène à des entiers "plus petits" grâce à la formule
pgcd(a,b) = pgcd(b,r)
où r est le reste de la division euclidienne de a par b.
Si jamais $a<b$ ce n'est pas grave car la méthode ci-dessus va appeler la fonction pgcd(b,a)
!
(et ensuite, le problème ne se posera plus car $r<b$).
Important: Pour utiliser une méthode récursive, il faut démontrer que la notion de "plus petits" utilisée dans l'hérédité finit bien par aboutir à l'étape de terminaison en un nombre fini d'itérations...
Ici on sait que $r<b\leq a$. Donc $\min(b,r)\leq \min(a,b) - 1$. Donc au pire au bout de $N$ étapes avec $N=\min(a,b)$, on sait que le "reste" $r$ sera nul.
def pgcd (a,b):
if b == 0:
return a
else:
return pgcd(b, a % b)
pgcd(7,123)
pgcd(600,124)
pgcd(12,0), pgcd(0,0), pgcd(0,12)
pgcd(1234,2020)
def pgcdEuclide (a,b):
if b == 0:
return a
elif a < b:
return pgcdEuclide (b,a)
else:
return pgcdEuclide(b, a - b)
pgcdEuclide(600,124)
pgcdEuclide(1234,2020), pgcdEuclide(0,12), pgcdEuclide(0,0)
Les algorithmes récursifs sont très pratiques lorsqu'on peut raisonner par récurrence. Mais, en python, ils ne sont pas optimisés, il demandent davantage de mémoire (mémoire de pile ou stack) et ne sont donc pas adaptés pour un grand nombre d'itérations (en gros supérieur à 1000).
(Pour le pgcd ce n'est pas très grave car l'algorithme d'Euclide est très efficace: peu d'itérations même pour des entiers très grands, cf. ci-dessous. Il faudrait des nombres avec plus de 200 chiffres pour que ça plante...)
pgcd(9896667081063042353430215182058733503146666634384944373401765774819916952466570536757090685909051250971877555701522243102171464782938632257729627142425602168458229597715225909882920542638682441199086679, 16013143712502213357018698163653060025603471927161902106364041769343651616369837753524801567948803360284511232110892962053473043906000050639883022618085781173913128777782344520942246774419401664791597285)
pgcd(61164766314391710035884829815943265224568052927769577329622759945237346639032672167677108820397521093126336764093707189513015791230614183821533954756601790054548991800671186110593262317807192235925106064, 98966670810630423534302151820587335031466666343849443734017657748199169524665705367570906859090512509718775557015222431021714647829386322577296271424256021684582295977152259098829205426386824411990866793)
def pgcdI (a,b): # version "itérative"
while b != 0:
(a, b) = (b, a % b)
return a
pgcdI(1234,2020), pgcdI(12,18), pgcdI(7,123), pgcdI(0,12)
pgcdI(61164766314391710035884829815943265224568052927769577329622759945237346639032672167677108820397521093126336764093707189513015791230614183821533954756601790054548991800671186110593262317807192235925106064, 98966670810630423534302151820587335031466666343849443734017657748199169524665705367570906859090512509718775557015222431021714647829386322577296271424256021684582295977152259098829205426386824411990866793)
Rappel: on a tracé la semaine dernière la relation "i divise j". Maintenant on regarde le "graphe" de "pgcd(i,j)".
def GCD (n):
T = np.zeros((n,n))
for i in range(0, n):
for j in range(0, n):
T[i,j] = pgcdI(j,i)
return T
plt.imshow(GCD(100), interpolation='none', cmap=cm.Spectral)
plt.colorbar()
#img.imsave('pgcd-log1000.png', GCD(1000), cmap=cm.Spectral)
n=100
R = np.zeros((n,n))
for i in range(1, n):
for j in range(0, n):
R[i,j] = j % i + 1 # on ajoute 1 pour pouvoir faire
# une échelle de couleurs logarithmique
plt.imshow(R, interpolation='none', cmap=cm.Spectral, norm=colors.LogNorm(1,n))
plt.colorbar()
Top = R[1:20,::]
plt.imshow(Top, interpolation='nearest', cmap=cm.Spectral, norm=colors.LogNorm(1,n))
On compte le nombre de divisions euclidiennes
def pgcdIter (a,b):
r = a % b
if r == 0:
return 1
else:
return 1 + pgcdIter(b,r)
def pgcdIterI (a,b): # version "itérative"
n = 0
while b != 0:
(a, b) = (b, a % b)
n = n + 1
return n
pgcdIter(100,1), pgcdIterI(100,1)
pgcdIter(1234,2020), pgcdIterI(1234,2020)
def ITER (n):
T = np.zeros((n,n))
for i in range(1, n-1):
for j in range(1, n-1):
T[i,j] = pgcdIter(j,i)
return T
%matplotlib notebook
plt.rcParams['figure.figsize'] = (10.0, 6.0) # set figures display bigger
plt.imshow(ITER(100), interpolation='none', cmap=cm.gist_earth)
plt.colorbar()
ar=ITER(1000)
%matplotlib inline
plt.rcParams['figure.figsize'] = (20.0, 12.0) # set figures display bigger
plt.imshow(ar, cmap=cm.gist_earth)
plt.colorbar()
#img.imsave('image1000.png', ar, cmap=cm.gist_earth)
plt.imshow(ITER(1000), cmap=cm.Spectral)
plt.colorbar()
#img.imsave('image1000-2.png', ar, cmap=cm.Spectral)
Top = ar[1:200,::]
plt.imshow(Top, interpolation='nearest', cmap=cm.gist_earth)
pgcdIter(546,337)
pgcdIter(377,610)
pgcdIter(55,89)
On peut regarder l'image avec le gimp pour voir les pixels... cf (377,610)
def fibPaire (n):
if n == 0:
return (0,1)
else:
a,b = fibPaire (n-1)
return (b,a+b)
def fib (n):
a,b = fibPaire (n)
return a
[fib(n) for n in range(0,17)]
La complexité la "pire" est obtenue par des paires de nombres de Fibonacci consécutifs. Leur pgcd est toujours 1 (ils sont premiers entre eux). À démontrer par récurrence...
pgcdIter(9227465, 14930352)
Pour tester des grands nombres, il faut une version non récursive de l'algorithme.
def fibI (n):
a,b = 0,1
while n > 0:
(a,b) = (b,a+b)
n = n-1
return a
[fibI(n) for n in range(0,15)]
On constate que les points où le nombre d'itération est le plus grand correspondent à des couples (i,j) où les i,j sont des éléments consécutifs dans la suite de Fibonacci !
fibI(974), fibI(974+1)
float(fibI(974))
pgcdI(fibI(974), fibI(974+1))
pgcdIterI(fibI(974), fibI(974+1))
fibI(972), fibI(972+1)
pgcdIterI(fibI(972), fibI(972+1))
On trace les (i,j) qui sont les $(F_k,F_{k+1})$ et leurs multiples.
La couleur indique la complexité de l'algorithme d'Euclide pour pgcd(i,j)
. On commence à $k=k0$.
def BAD (n,k0):
T = np.zeros((n,n))
F1, F2 = fibPaire(k0) # on part les k0-ème et
m = 1 # (k0+1)-ème nombres de Fibonacci.
while F2 < n:
i, j = F1, F2
while j < n:
T[i,j] = m
T[j,i] = m
i, j = i+F1, j+F2
m = m+1
F1, F2 = F2, F1+F2 # on passe au couple de Fibonacci suivant
return T
ar=BAD(100,4)
plt.imshow(ar, interpolation='none', cmap=cm.gist_earth)
plt.colorbar()
Les pentes des droites de l'image ci-dessus sont données par le nombre d'or !
$\phi = \frac{1+\sqrt{5}}2$