PC – Lycée René CASSIN Correction du devoir maison Partie A – Une première construction d’une liste de nombres premiers
1) m%i renvoie le reste de la division euclidienne de m par i.
not(m%i) sera non nul si m%i est nul, donc si i divise m.
Ainsi, l’instruction Return sera exécutée, quittant la fonction test() avant l’ajout de m à la liste L si un des nombres de la liste L divise m.
m est donc ajouté à la liste L si aucun des nombres de L ne divise m.
2) Si N = 2, la boucle ne s’exécute pas et la liste retournée est L = [2].
Soit pour tout entier n ≥ 3, Ln la liste L après l’appel de test(n,L).
Posons L2 = [2].
Pour tout entier n ≥ 2, soit P(n) : « la liste Ln contient tous les nombres premiers inférieurs ou égaux à n. »
P(2) est vraie.
Supposons que P(n) est vraie, pour un entier n ≥ 2.
La liste Ln+1 est constitué des éléments de la liste Ln avec l’ajout éventuel de n+1, si aucun des nombres de Ln ne divise n+1, c’est-à-dire si n+1 est premier.
Ainsi, la liste Ln+1 contient tous les nombres premiers inférieurs ou égaux à n+1. P(n+1) est vraie.
Par récurrence, P(n) est vraie pour tout entier n ≥ 2, et donc P(N) est vraie, ce qui prouve bien que la fonction premier() retourne la liste des nombres premiers inférieurs ou égaux à N.
3) Plusieurs solutions, par exemple :
def test(m,L) : ou def test(m,L) : k = m**0.5 k = m**0.5 for i in L : i = 0
if not(m%i) : while L[i] <= k : return if not(m%L[i]) : if i > k : return break i += 1 L.append(m) L.append(m)
4) Le preuve donnée dans le 2) reste valable : après l’appel de test(n,L), la liste L contient tous les nombres premiers inférieurs ou égaux à n.
Puisque l’ensemble des nombres premiers est infini, si n est suffisamment grand, la longueur de L deviendra égale à N, et la boucle while s’interrompra. La liste L retournée par l’appel de premier(N) contiendra alors la liste des N premiers nombres premiers.
Partie B – Comparaison avec le crible d’Eratosthène 1) Nous négligeons les variables n et i.
Algorithme A : la liste L est de taille N+1 (même si L[0] et L[1] ne sont pas utilisés) et la liste P est de taille π(N), donc cet algorithme doit stocker N+1 + π(N) données, ce qui correspond à une complexité en mémoire en Θ(N) (puisque π(N) ∼ln NN = o(N)).
Algorithme B : la liste L est de taille π(N) donc la complexité en mémoire de l’algorithme B est en Θ�ln NN �.
L’algorithme A est beaucoup plus gourmand en mémoire.
2) On utilise par exemple le script : from time import clock N = int(input("Entrer N : ")) t0 = clock()
L = eratosthene(N) t1 = clock()
print("temps pour eratosthene : ",t1-t0) t0 = clock()
L = premier(N) t1 = clock()
print("temps pour premier : ",t1-t0) On obtient les temps suivants en seconde :
N eratosthene() premier()
10 000 0.0018 0.036
100 000 0.018 0.41
1 000 000 0.227 7.07
10 000 000 2.46 138.1
L’algorithme A est beaucoup plus rapide que le B.
De plus, la complexité en temps de l’algorithme A semble être linéaire ou presque, puisque lorsque N est multiplié par 10, le temps d’exécution est approximativement multiplié par 10 (légèrement plus).
Ce n’est pas du tout le cas de l’algorithme B où, lorsque N passe de 1 000 000 à 10 000 000, le temps d’exécution est presque multiplié par 20.
3) A chaque exécution, la première boucle réalise E(N/i) – 1 affectations, si i est premier, donc le nombre total d’affectations réalisées par la première boucle est :
S = ∑E(√N)𝑖𝑖=2 (E(N/𝑖𝑖) – 1)
𝑖𝑖∈𝑃𝑃
= ∑E(√N)𝑖𝑖=2 (N/𝑖𝑖 +ϵi)
𝑖𝑖∈𝑃𝑃
= N∑E(√N)𝑖𝑖=2 1𝑖𝑖
𝑖𝑖∈𝑃𝑃 +∑E(√N)ϵi 𝑖𝑖=2𝑖𝑖∈𝑃𝑃
avec ϵi = (E(N/𝑖𝑖) − N/𝑖𝑖 – 1) ∈ ]–2 ; –1]
Or, ∑E(√N)𝑖𝑖=2 1𝑖𝑖
𝑖𝑖∈𝑃𝑃 ∼ ln ln √N = ln(0,5 ln N) = ln 0,5 + ln ln N∼ ln ln N Donc N∑E(√N)𝑖𝑖=2 1𝑖𝑖
𝑖𝑖∈𝑃𝑃 ∼ N ln ln N et ∑E(√N)𝑖𝑖=2 ϵi
𝑖𝑖∈𝑃𝑃
= O(√N) = o(N ln ln N) D’où S ∼ N ln ln N.
La deuxième boucle for réalise π(N) affectations, ce qui est négligeable devant N ln ln N.
La ligne L = [True]*(N+1) réalise N+1 affectations, également négligeable devant N ln ln N. D’où un nombre total d’affectations en Θ(N ln ln N).
Partie C – Amélioration du crible d’Eratosthène
1) a) L[i*i::i] = [False]*(N//i-i+1) ou [False]*((N-i*i)//i+1)
b) L[2i], L[3i],…,L[(i–1 )*i] ne sont plus affectées par l’instruction L[i*i::i]
= [False]*(N//i-i+1), mais ont déjà reçu la valeur False lors des exécutions
précédentes de la boucle puisqu’il s’agit de multiples de 2, 3, …, (i–1) supérieurs à 2*2, 3*3, …(i–1)* (i–1).
2) Pour i ≥ 1, la primalité de 2i + 1 est donnée par L[i].
Nous dirons que 2i + 1 a pour indice i dans la table L.
Si 2i + 1 est premier, les multiples de 2i + 1 à partir de (2i + 1)2 doivent être codés comme non premiers (False).
(2i + 1)2 a pour indice ((2i + 1)2 – 1)/2 = 2i2 + 2i donc q = i(2i + 2).
Si m = (2i + 1)2 + k(2i + 1) est un nombre impair inférieur ou égal à N, avec k ∈ℕ, alors k est pair, donc sous la forme 2k’,
et m a pour indice i(2i + 2) + k’(2i + 1), donc p = 2i + 1.
Pour limiter le nombre de multiplications, on peut prendre : p = 2*i+1
q = i*(p+1)
3) Soit n un nombre premier différent de 2 et 3 et le reste r de la division euclidienne de n par 6.
Si r = 0, n est divisible par 6, donc n’est pas premier.
Si r = 2, n = 6i + 2 est divisible par 2, donc n’est pas premier.
Si r = 3, n = 6i + 3 est divisible par 3, donc n’est pas premier.
Si r = 4, n = 6i + 4 est divisible par 2, donc n’est pas premier.
Seuls cas possibles : r = 1, donc n est de la forme 6i + 1 ou r = 5, donc n est de la forme 6i’ + 5 soit 6i – 1, avec i ∈ℕ*.
4) 6i – 1 a pour indice i dans la table L.
6i + 1 a pour indice i dans la table M.
Si p = 6i + 1 est premier, alors p2 = (6i + 1)2 = 36i2 + 12i + 1 a pour indice 6i2 + 2i = i(6i + 2) = i(p + 1) dans la table M.
On doit coder False les indices des multiples de 6i + 1 supérieurs à (6i + 1)2, qui ne sont pas multiples de 2 ou 3. Soit un nombre m dont l’indice est dans la table M, donc sous la forme 6j + 1. Alors :
m + (6i + 1) = 6j + 6i + 2 multiple de 2 m + 2.(6i + 1) = 6j + 12i + 3 multiple de 3 m + 3.(6i + 1) = 6j + 18i + 4 multiple de 2
m + 4.(6i + 1) = 6j + 24i + 5 d’indice j + 4i + 1 dans la table L m + 5.(6i + 1) = 6j + 30i + 6 multiple de 6
m + 6.(6i + 1) = 6j + 36i + 7 d’indice j + 6i + 1 = j + p dans la table M Donc, dans la table M, on doit affecter la valeur False à M[i(p + 1)], et aux variables suivantes avec un pas de p.
Dans la table L, on démarre à L[i(p + 1) + 4i + 1], et on procède de même avec un pas de p (ce qui revient à ajouter 6.(6i + 1)).
D’où la portion de code manquante : if M[i] :
p = 6*i+1 q = i*(p+1)
M[q::p] = [False]*((n-q)//p+1) q = q + 4*i + 1
L[q::p] = [False]*((n-q)//p+1)
Cela n’était pas demandé, mais voici la justification de la partie du code relative à L[i] :
Si p = 6i – 1 est premier, alors p2 = 36i² – 12i + 1 a pour indice 6i2 – 2i = i(6i – 2) = i(p – 1) dans la table M.
Soit un nombre m dont l’indice est dans la table M, donc sous la forme 6j + 1. Alors :
m + (6i – 1) = 6j + 6i multiple de 6
m + 2.(6i – 1) = 6j + 12i – 1 d’indice j + 2i dans la table L m + 3.(6i – 1) = 6j + 18i – 2 multiple de 2
m + 4.(6i – 1) = 6j + 24i – 3 multiple de 3 m + 5.(6i – 1) = 6j + 30i – 4 multiple de 2
m + 6.(6i – 1) = 6j + 36i – 5 d’indice j + 6i – 1 = j + p dans la table M Donc, dans la table M, on doit affecter la valeur False à M[i(p – 1)], et aux variables suivantes avec un pas de p.
Dans la table L, on démarre à L[i(p – 1) + 2i], et on procède de même avec un pas de p.
Partie D – Applications 1 – Constante de Mertens
Après avoir transformé la fonction eratosthene3() en générateur comme indiqué dans la partie compléments, on utilise le programme suivant :
from numpy import log N = int(input("Entrer N : ")) s = 0
for i in eratosthene3(N) : s = s + 1/i
print(s-log(log(N)))
Avec n = 1 000 000, on obtient 0.261537415604, et avec n = 100 000 000, on obtient 0.261501248964. On retrouve bien M ≈ 0,261.
2 – Conjecture de Goldbach (formulée en 1742 qui reste à démontrer) On reprend la version de la fonction eratosthene3() retournant la liste P des nombres premiers.
def goldbach(N,P) :
for n in range(4,N+1,2) : gb = False
for p in P : if n-p in P :
print(p,' + ',n-p,' = ',n) gb = True
break if not(gb) :
print(" Conjecture de Goldbach non vérifiée pour ",n) N = int(input("Entrer N : "))
P = eratosthene3(N) goldbach(N,P)
A propos des listes en compréhension :
noprimes = [j for i in range(2, 8) for j in range(i*2, 100, i)]
primes = [x for x in range(2, 100) if x not in noprimes]
print(primes)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
Beaucoup de doublons dans noprimes… on les évite en utilisant des ensembles en compréhension ({} au lieu de []).
from math import sqrt def primes(n):
if n == 0:
return []
elif n == 1:
return [1]
else:
p = primes(int(sqrt(n)))
no_p = {j for i in p for j in range(i*2, n, i)}
p = {x for x in range(2, n) if x not in no_p}
return p print(primes(40))