xxx
Cette page fait partie du cours de polytech PeiP1 et 2 Bio
2. Mise en pratique : nombres premiers
2.1. Introduction
Dans ce TP, on s'intéresse aux nombres premiers, à leur identification et leur recherche.
On rappelle la définition d'un nombre premier : il s'agit d'un entier naturel
qui possède deux diviseurs distincts : 1 et lui-même.
En conséquence le nombre 1 n'est pas premier car il ne possède qu'un seul
diviseur.
La liste des premiers nombres premiers est : 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, 101, 103, 107, 109, 113, 127, 131, 137, 139, ...
2.2. Déterminer si un nombre est premier ou non
Ecrire une fonction qui permet de déterminer si un nombre est premier ou non.
2.2.1. Première version naïve et lente
Dans un premier temps, on détermine si un nombre est premier ou non en comptant son nombre de diviseurs.
- si un nombre $n$ ne possède que deux diviseurs alors il est premier car ses seuls diviseurs sont
1 et $n$
Cliquez sur la zone orangée pour voir apparaître les informations
complémentaires liées à la fonction ci-dessous.
fonction est_premier(n) |
Entrée |
n (entier) : nombre pour lequel on doit déterminer
s'il est premier ou non
|
Sortie |
booléen, vrai si n est premier, faux sinon
|
Variableslocales |
nbr_diviseurs (entier) : compte le nombre de diviseurs de n
|
Description |
On calcule le nombre de diviseurs de n,
s'il en existe deux alors le nombre est premier
|
fonction est_premier(n est un entier) : booléen
// initialement on n'a aucun diviseur
nbr_diviseurs est un entier = 0
// on recherche les diviseurs potentiels
pour i de 1 à n faire
si i est_un_diviseur_de n alors
nbr_diviseurs = nbr_diviseurs + 1
fin si
fin pour
// en fonction du nombre de diviseurs on indique
// si le nombre est premier ou non
si nbr_diviseurs == 2 alors
retourner vrai
sinon
retourner faux
fin si
fin fonction
|
La seule difficulté rencontrée consiste à déterminer si
$i$ est un diviseur de $n$ :
- si $i$ est un diviseur de $n$ cela signifie que $n = i × p$.
- dans le cas contraire, cela implique que $n = i × p + r$ ;
en d'autres termes, si on divise $n$ par $i$, on aura un reste $0 < r < i$.
Pour trouver le reste de la division on utilise l'opération modulo :
$n\ %\ i$
L'implantation en Python est assez simple (est_premier_compte_diviseurs.py).
Cliquez sur la première ligne du code pour faire apparaître
l'ensemble des instructions.
def est_premier(n):
"""
est_premier(n)
Détermine si un nombre est premier ou non.
Cette version compte le nombre de diviseurs.
Parameters
----------
n : int
nombre pour lequel on veut déterminer s'il est premier ou non
Returns:
True si n est premier, False sinon
"""
nbr_diviseurs = 0
# pour chaque nombre entre 1 et n
for i in range(1, n+1):
# si i est un diviseur de n alors
if n % i == 0:
# augmenter le compteur
nbr_diviseurs = nbr_diviseurs + 1
if nbr_diviseurs == 2:
return True
else:
return False
2.2.2. Deuxième version améliorée et efficace
La version précédente n'est pas efficace pour plusieurs raisons :
- on évalue tous les diviseurs ce qui implique beaucoup de calculs si $n$
est grand
- il suffit de trouver un diviseur qui soit différent de 1 et de $n$
pour déterminer que $n$ n'est pas premier
- il n'est pas nécessaire de tester tous les diviseurs pairs à partir
du moment ou $n$ est divisible par 2
- il faut limiter la recherche des diviseurs à $√(n)+1$
Prenons un exemple ($n = 37$) pour comprendre pourquoi il ne faut pas aller
au-delà de $√(37)+1$
1 |
37 |
0 |
2 |
18 |
1 |
3 |
12 |
1 |
4 |
9 |
1 |
5 |
7 |
2 |
6 |
6 |
1 |
7 |
5 |
2 |
8 |
4 |
5 |
9 |
4 |
1 |
10 |
3 |
7 |
etc |
etc |
etc |
Recherche des diviseurs de 37
Au-delà de 7 les quotients seront nomarlement inférieurs à 7, or on a déjà testé les diviseurs de 1 à 7.
Voici l'algorithme de la fonction est_premier basée sur le calcul du nombre
de divisieurs. Cliquez sur la zone orangée pour voir apparaître les informations
complémentaires.
Fonction est_premier(n) |
Entrée |
n (entier) : nombre pour lequel on doit déterminer
s'il est premier ou non
|
Sortie |
booléen, vrai si n est premier, faux sinon
|
Description |
On cherche les diviseurs impairs de 3 à $√(n)+1$
|
fonction est_premier(n est un entier) : booléen
i est un entier
si n <= 1 alors
retourner faux
sinon si 2 <= n et n <= 3 alors
retourner vrai
sinon si n est_disible_par 2 alors
retourner faux
sinon
// rechercher des diviseurs
pour i de 3 à sqrt(n)+1 par pas de 2 faire
si n est_divisible_par i alors
retourne faux
fin si
fin pour
fin si
// aucun diviseur trouvé, le nombre est premier
retourner vrai
fin fonction
|
L'implantation en Python pose un seul problème lié à la racine carrée car elle
fait partie des calculs sur les nombres réels :
- il est nécessaire de faire appel à la fonction sqrt (square root) de
la librairie mathématique (math)
- il faut convertir le résultat en entier
Voici le script correspondant (est_premier_amelioree.py).
Cliquez sur la première ligne du code pour faire apparaître
l'ensemble des instructions.
import math
def est_premier(n):
if n <= 1:
return False
if n <= 3:
return True
if n % 2 == 0:
return False
for i in range(3, int(math.sqrt(n)+1), 2):
if n % i == 0:
return False
return True
2.3. Trouver les cinquante premiers nombres premiers
Programme |
Description |
Calcule les 50 premiers nombres premiers en les stockant dans
une liste
|
// liste des nombres premiers
liste est une liste entiers = vide
// nombre courant
n est un entier = 1
tant que taille liste != 50 faire
si est_premier(n) alors
ajouter n à liste
fin si
n = n + 1
fin tant que
|
La traduction en Python est la suivante
(nombre_premier_cinquante_premiers_v1.py) :
import est_premier_amelioree as premier
# ou import est_premier_compte_diviseurs as premier
# liste vide
liste = []
# on commence la recherche avec n = 1
n = 1
# tant qu'on n'a pas 50 nombres premiers on continue la recherche
while len(liste) != 50:
if premier.est_premier(n):
liste.append(n)
n = n + 1
print(liste)
On peut également utiliser une fonctionnalité de Python qui consiste à générer une
liste en indiquant comment générer chaque élément :
(nombre_premier_cinquante_premiers_v2.py) :
import est_premier_amelioree as premier
# ou import est_premier_compte_diviseurs as premier
""" deuxième méthode
autre manière d'obtenir le même résulat en
remplissant une liste mais on ne peut contrôler
la longueur de la liste
"""
liste = [ x for x in range(1,300) if premier.est_premier(x) ]
# sélection des cinquante premiers de l'indice 0 à l'indice 49
liste = liste[0:50]
print(liste)
2.4. Trouver les nombres premiers dans un intervalle
Une fois que l'on est en mesure de déterminer si un nombre est premier ou
non, on peut commencer à rechercher les nombres premiers.
2.4.1. Utiliser la fonction est_premier
Voici un exemple de programme qui permet de rechercher les nombres premiers
dans un intervalle donné. On passe en paramètre du programme
import math
import sys, getopt
"""
programme qui trouve les nombres premiers dans un intervalle
"""
def est_premier(n):
"""
est_premier(n)
Détermine si un nombre est premier ou non.
Cette version compte le nombre de diviseurs.
Parameters
----------
n : int
nombre pour lequel on veut déterminer s'il est premier ou non
Returns:
True si n est premier, False sinon
"""
if n <= 1:
return False
if n <= 3:
return True
if n % 2 == 0:
return False
for i in range(3, int(math.sqrt(n)+1), 2):
if n % i == 0:
return False
return True
def trouve_nombres_premiers(maximum):
"""
trouve_premiers(n)
Trouve tous les nombres premiers dans un
intervalle donné
Parameters
----------
maximum : int
borne supérieure de l'intervalle de recherche
Returns:
liste des nombres premiers
"""
# liste des nombres premiers
nombres_premiers = []
# recherche des nombres premiers entre 1 et 100
for n in range(1,maximum+1):
if est_premier(n):
nombres_premiers.append(n)
return nombres_premiers
def main():
"""
fonction principale
on peut passer en argument du programme la borne supérieure
de l'intervalle de recherche en utilisant l'argument '-m val'
ou '--maximum=val'
"""
maximum = 100000
try:
opts, args = getopt.getopt(sys.argv[1:], "hm:", ["help", "maximum="])
except getopt.GetoptError as err:
# print help information and exit:
print(err) # will print something like "option -a not recognized"
usage()
sys.exit(2)
for o, a in opts:
if o == "-m":
maximum = int(a)
elif o in ("-h", "--help"):
usage()
else:
assert False, "unhandled option"
l = trouve_nombres_premiers(maximum)
print( l )
print( "dans l'intervalle [1..{}]".format(maximum) )
print( "il y a ", len(l), " nombres premiers")
print( "leur somme est égale à ", sum(l) )
if __name__ == "__main__":
main()
Voici quelques résultats qui permettent de comparer la méthode naïve avec
la méthode améliorée :
naïve |
4m13s |
454396537 |
améliorée |
0,1 |
454396537 |
Recherche des nombres premiers entre 1 et 10000 sur Intel i7-4790 CPU @ 3.60GHz
2.4.2. Crible d'Erathostène
Une autre solution, plutôt que de déterminer si un nombre $n$ est premier,
consiste à utiliser un tableau de booléens tableau_nombres_premiers
où chaque tableau_nombres_premiers[i] est vrai si $i$ est un nombre
premier.
Pour remplir le tableau, on procède ainsi :
- on indique qu'initialement tous les nombres sont premiers excepté 0 et 1
- on commence avec l'indice $i=2$ qui sera incrémenté
- pour tout tableau_nombres_premiers[i] affecté à vrai, on affecte
tous ses multiples à faux
fonction |
Description |
remplir un tableau et éliminer les multiples
|
// créer et remplir le tableau
// si on recherche les 3000 premiers nombres premiers
// il faut prend maximum = 27449
variable t est un tableau de [1 à maximum] booléens
pour i de 2 à maximum faire
t[i] = vrai
fin pour
t[0] = faux
t[1] = faux
// éliminer les multiples
pour i de 2 à maximum faire
si t[i] == vrai alors
pour j de 2*i à maximum par pas de i faire
t[j] = faux
fin pour
fin si
fin pour
// créer et retourner la liste des nombres premiers
variable l est une liste entiers = vide
pour i de 1 à maximum faire
si t[i] == vrai alors
ajouter i à l
fin si
fin pour
retourner l
|
Exercice 2.1
Ecrire un programme Python très simple qui implante l'algorithme du crible.
On utilisera une liste de nombres entiers.
Voici un exemple qui utilise la librairie numpy (trouve_premiers_crible.py) mais on peut écrire quelque chose de beaucoup plus simple.