Détection de cycles

publicité
informatique commune
Corrigé
Détection de cycles
Question 1.
def itere(f, x0):
lst = []
j, x = 0, x0
while not x in lst:
lst.append(x)
j += 1
x = f(x)
i = lst.index(x)
return (j−i, i)
Le coût spatial est lié au calcul du tableau [x0 , x1 , . . . , xλ+µ−1 ] ; c’est un Θ(λ + µ).
Si on admet que le coût de la méthode append est un Θ(1), le coût de cette fonction est principalement lié à la recherche
d’un élément dans le tableau. C’est une recherche de coût linéaire donc le coût temporel total de cette fonction est un
O((λ + µ)2 ).
Question 2.
On montre sans peine que pour tout n ∈ N, yn = x2n . Mais :
x2n = xn ⇐⇒ (n > µ et 2n ≡ n mod λ) ⇐⇒ (n > µ et λ divise n)
donc l’algorithme de Floyd se termine et retourne le plus petit multiple de λ qui soit supérieur ou égal à µ.
def floyd1(f, x0):
x, y = f(x0), f(f(x0))
while x != y:
x, y = f(x), f(f(y))
return x
def floyd2(f, x0):
i = 1
x, y = f(x0), f(f(x0))
while x != y:
i += 1
x, y = f(x), f(f(y))
return i
La suite des itérés de xi est une suite périodique : sa pré-période est nulle et sa période égale à λ ; la fonction floyd2
appliquée à xi retourne donc la valeur de λ. D’où la fonction :
def periode(f, x0):
xi = floyd1(f, x0)
return floyd2(f, xi)
Puisque i est un multiple de λ on a xi+µ = xµ . Il suffit donc pour trouver µ de comparer les suites des itérés de x0 et de xi
jusqu’à trouver une valeur commune.
def pre_periode(f, x0):
x, y = x0, floyd1(f, x0)
mu = 0
while x != y:
mu += 1
x, y = f(x), f(y)
return mu
page 1
Le coût spatial de chacune de ces fonctions est un Θ(1). Le coût temporel de l’algorithme de Floyd est un Θ(i), où i est le
plus petit multiple de λ qui soit supérieur ou égal à µ. Ce dernier est compris entre µ et µ + λ donc le coût temporel de
l’algorithme de Floyd est un O(λ + µ) et un Ω(µ). À ce coût s’ajoute un Θ(λ) pour le calcul de la période et un Θ(µ) pour la
pré-période donc le coût total de ces deux fonctions est un Θ(λ + µ).
Question 3.
def brent(f, x0):
i, j = 0, 1
xi, xj = x0, f(x0)
while xi != xj:
if j == 2 * i + 1:
i, xi = j, xj
j, xj = j + 1, f(xj)
return (i, j)
On constate sans peine que les valeurs prises par i sont les entiers de la forme i = 2n − 1 et que pour un tel entier j prend
toutes les valeurs de l’intervalle ~2n , 2n+1 − 1.
Mais xi = xj ⇐⇒ (i > µ et λ divise j − i), avec 1 6 j − i 6 2n . Notons donc n0 le plus petit entier vérifiant 2n − 1 > µ et
2n > λ. L’algorithme de Brent se termine lorsque i = 2n0 − 1 et j = λ + i.
On a 2n0 − 1 6 µ 6 2n0 − 1 donc 2n0 − 1 6 2µ − 1 et ainsi j 6 λ + 2µ − 1.Le coût de cet algorithme est donc un Θ(λ + µ).
Il y a deux avantages à appliquer l’algorithme de Brent plutôt que celui de Floyd : il n’est nécessaire que de faire un seul
parcours pour obtenir la valeur de λ, et à chaque étape un seul calcul de f est effectué, contre trois pour l’algorithme
précédent.
Question 4. On calcule bien entendu le pgcd à l’aide de l’algorithme d’Euclide :
def pgcd(a, b):
while b > 0:
a, b = b, a % b
return a
Il reste alors à définir :
def pollard(n, c):
def f(x):
return (x * x + c) % n
i, j = 0, 1
xi, xj = 2, f(2)
while xi != xj:
d = pgcd(n, abs(xj − xi))
if d > 1:
return d
if j == 2 * i + 1:
i, xi = j, xj
j, xj = j + 1, f(xj)
return None
6
À l’aide de cette fonction on obtient instantanément le diviseur 274 177 de F6 = 22 + 1 = 18 446 744 073 709 551 617.
Considérons v1 , . . . , vk des valeurs prises aléatoirement dans ~0, n − 1 et pour deux de ces valeurs vi et vj notons



1 si vi = vj
Xij = 

0 sinon
1
1
La probabilité pour que vi = vj est égale à donc E[Xij ] = .
k X
k
X
n
n
Notons X la variable aléatoire qui compte le nombre de paires de valeurs égales : X =
Xij . Par linéarité de
l’espérance nous avons :
i=1 j=i+1
k X
k
X
k(k − 1)
E[X] =
E[Xij ] =
.
2n
i=1 j=i+1
Ainsi, dès lors que k(k − 1) > 2n au moins une collision a probablement eu lieu, c’est-à-dire pour :
√
1 + 1 + 8n √
k>
≈ 2n.
2
page 2
√
Ceci laisse espérer qu’en moyenne on a λ + µ 6 2n. Or dans
√ l’algorithme de Brent on a i 6 j 6 λ + 2µ − 1 donc le nombre
moyen d’étapes avant la première collision est bien un Θ( n).
0
Si p est un diviseur non trivial de n et xi0 = xi mod p, cette suite vérifie la relation de récurrence xi+1
= f˜(xi ) avec
√
2
f˜(x) = x + c mod p. D’après ce qui précède, au bout d’un nombre d’étapes en Θ( p) l’algorithme de Brent va produire
deux valeurs i et j telles que xi0 = xj0 , ce qui implique que p divise xi − xj et donc pgcd(n, |xi − xj |). Or si p n il y a des
chances pour que xi , xj et donc que pgcd(n, |xi − xj |) < n, nous donnant ainsi un diviseur non trivial de n.
Cependant il se peut que dans certains cas la collision entre xi0 et xj0 se produise en même temps que celle entre xi et xj ;
dans ce cas la méthode échoue, mais dans ce cas il est possible de recommencer avec une autre valeur de c. Compte tenu
du calcul précédent, plus p est petit devant n, meilleures sont les chances que la méthode réussisse.
page 3
Téléchargement