Vous êtes sur la page 1sur 11

230 Programmation en Python pour les mathématiques

Exercice 58. Donner la version itérative d’une procédure de calcul des termes de la suite de
F IBONACCI.
Solution. Le premier programme est la version générique, applicable à tout langage de pro-
grammation ; la version itérative à la mode « Python » met en œuvre l’affectation parallèle, ce
qui économise l’emploi d’une variable locale.

figIterGen.py fibIterPy.py
def fibo(n): def fibo(n):
a = 1 a = b = 1
b = 1 for i in range(n):
for i in range(n): a, b = a + b, a
t = a return b
a += b
b = t for n in range(12):

8155
return b print(fibo(n))

6368
for n in range(12):
print(fibo(n))

5:16
6.15
1.3 Complexité
25.8

1.3.1 Motivations : ce qui est attendu d’un algorithme


195.

En algorithmique comme en programmation, on aborde les questions suivantes afin d’appré-


cier la validité et la qualité d’un algorithme ou d’un programme.
:
6811

– preuve de terminaison (l’exécution s’achève et l’algorithme se termine en un nombre


fini d’étapes) ;
8892

– efficience (l’algorithme réalise bien ce pourquoi il est conçu) ;


– spécification complète : de même que les hypothèses d’un énoncé ou le domaine de
166:

définition d’une fonction sont bien établis, les étapes et instructions qui composent
l’algorithme sont toutes parfaitement déterminées, par leur nature et par les données
0833

sur lesquelles elles s’appliquent.


I:211

1.3.2 Que mesure-t-on ?


CES

Mesurer la complexité et l’efficacité d’un programme exige de se doter de protocoles et ou-


tils permettant d’établir des comparaisons, entre algorithmes d’une part, et entre différentes
com:

programmations d’un même algorithme d’autre part.


rvox.

1.3.3 Mesures expérimentales sur un programme réel


On peut effectuer des mesures lors du déroulement d’un calcul sur une machine. Cela n’est
la

pas toujours possible. Cette approche n’est pas souhaitable pour des programmes critiques,
scho

comme ceux qui gouvernent un calculateur embarqué pour la gestion d’un vol aéronautique,
ou le contrôle des équipements d’une centrale nucléaire.
univ.
Récursivité 231

Ces mesures, lorsqu’elles sont possibles, sont relatives à l’environnement matériel d’exécu-
tion du programme : architecture de l’ordinateur hôte, performances des processeurs, dispo-
nibilité de la mémoire en quantité suffisante...

Empreinte en mémoire. On étudie ici l’empreinte mémoire du programme pendant sa du-


rée de vie : « comment la consommation de mémoire évolue-t-elle entre le début et la
fin de l’exécution du programme ? » est une question à examiner si l’on souhaite éviter
de saturer un calculateur et provoquer un arrêt brutal.
Coût en temps de calcul. Il s’agit dans ce cas d’étudier le temps nécessaire à l’exécution d’un
programme. Évaluer la complexité en temps d’un algorithme est une prévision théo-
rique, que l’on peut ensuite décliner pour un programme et une architecture. Afin d’éli-
miner la question du coût d’une instruction qui est variable selon l’environnement lo-
giciel, on pourra évaluer la complexité en termes d’instructions élémentaires.
Complexité de conception. Le programmeur est conduit à choisir les algorithmes et struc-

8155
tures de données de la façon la plus pertinente pour l’algorithme qu’il doit mettre en
œuvre sous forme de programme. De fait, le seul outil dont dispose le programmeur est

6368
son propre raisonnement.

5:16
1.3.4 Avec quels outils ?

6.15
Les calculs sur ordinateur sont souvent l’occasion des « nombres réels machine », dont la maî-
trise des arrondis dans les calculs approchés doit être envisagée d’emblée avec précaution.
25.8

Néanmoins, nous nous limiterons ici à l’évocation de la complexité temporelle que nous no-
terons t (n).
195.

t (n) est une fonction de N dans R+ pour laquelle nous devons préciser la nature de n :
:
6811

– dans le cas du tri d’une liste, n peut désigner la longueur de la liste ;


– dans le cas de la factorisation d’un entier, n pourra être la valeur cet entier ;
8892

– n peut désigner le nombre d’instructions élémentaires...


166:

1.3.5 Un exemple : coût en temps d’une fonction itérative


0833

10
Nous reprendrons l’exemple de la somme i.
P
i =1
I:211

somme.py
def somme(n): # (0)
CES

s = 0 # (1)
i = 1 # (2)
com:

while i <= n: # (3)


s = s + i # (4)
i = i + 1 # (5)
rvox.

return s # (6)
la

print(somme(10)) # (7)
scho

Chacune des instructions (i ) possède un coût d’exécution en temps que nous noterons c i
(c i ∈ R+ ) :
univ.
232 Programmation en Python pour les mathématiques

– c 1 = c 2 si on suppose que toutes les affectations élémentaires ont la même durée ;


– le coût c 3 se décompose en un coût fixe pour le test, puis selon le cas en un coût de
branchement après la fin de la boucle, ou en n fois le coût du corps de la boucle ;
– c 4 = c 5 = c + + c 1 sont les coûts d’instructions composées d’une addition et d’une affec-
tation, et c + désigne le coût d’une instruction d’addition entre deux nombres ;
– c 6 est le coût du retour de fonction ;
– c 7 est le coût composé du coût d’appel de fonction (empilement des paramètres, bran-
chement) et du coût d’affichage de la valeur calculée (dépilement).

Selon que l’on considère que certaines opérations sont élémentaires ou non, on influe sur la
granularité et l’estimation du coût.
Nous pouvons maintenant effectuer notre calcul du coût d’exécution C(n) :

C(n) = c 1 + c 2 + c 3 + n × (c 4 + c 5 ) + c 6 + c 7 (5.9)

8155
= 2c 1 + c 3 + 2n (c 1 + c + ) + c 6 + c 7 (5.10)
= (2 (c 1 + c + )) n + (2c 1 + c 3 + c 6 + c 7 ) (5.11)

6368
= K1 n + K2

5:16
Nous constatons un coût affine en n. Selon la notation de L ANDAU, nous avons : C(n) = O(n).
Cette égalité exprime (nous sommes en présence de fonctions positives) :
6.15
∃K ∈ R+ , ∃n 0 ∈ N, ∀n ⩾ n 0 , C(n) ⩽ K × n.
25.8

Le coût d’exécution de notre fonction est majoré par une fonction affine de n, elle-même
majorée par une fonction linéaire. Le programmeur, par l’étude mathématique du problème,
195.

et l’existence d’une formule élégante y répondant, gagnera énormément à remplacer le calcul


de la somme ni=1 i par celui de
:

P
6811

Xn n(n + 1)
i=
2
8892

i =1
car le programme correspondant est lui en O(1), c’est-à-dire en temps constant, quelle que
soit la valeur de n.
166:
0833

1.4 Complexité des algorithmes de tri


Dans ce paragraphe, on étudie la complexité des algorithmes de tri présentés dans la sec-
I:211

tion 13 en fin du premier chapitre.


CES

a) Complexité du tri par sélection :


La relative simplicité de cet algorithme fait qu’on lui attribue le qualificatif d’« algo-
com:

rithme naïf ». Cela signifie que même si l’algorithme est correct, il est trop naïf pour être
réellement efficace.
rvox.

À la première itération, sont effectuées (n −1) comparaisons. À la p-ième itération, sont


effectuées (n−p) comparaisons. Soit une complexité en O(n 2 ) dont on peut obtenir une
représentation en donnant un coût unitaire aux opérations atomiques :
la
scho

n−1 1
(n − p) = (n 2 − n).
X
2
univ.

p=1
Récursivité 233

b) Complexité du tri par insertion :


Soit un tableau de n éléments. Pour mettre en œuvre le tri par insertion, il faut insérer
un à un (n − 1) éléments dans un tableau préalablement trié. Dans le pire des cas, c’est-
à-dire quand la liste à trier de manière croissante est classée de manière décroissante,
pour insérer le pe élément, il faut décaler les (p − 1) éléments qui le précédent. Soit un
coût total, dans le pire des cas, donnée par la formule suivante :
n 1
(p − 1) = (n 2 − n).
X
p=2 2

Cependant, en moyenne, seule la moitié des éléments est décalée. Soit un décalage de
(p − 1)/2 éléments et donc une complexité en moyenne qui se calcule ainsi :
n 1 1
(p − 1) = (n 2 − n).
X

8155
p=2 2 4

Il s’agit d’une complexité en O(n 2 ) analogue à la complexité du tri par sélection. Cepen-

6368
dant, le tri par insertion présente un avantage certain : il peut être facilement mis en
œuvre pour insérer de nouvelles données dans un tableau déjà trié ou pour trier des va-

5:16
leurs au fur et à mesure de leur apparition (cas des algorithmes en temps réel où il faut
parfois exploiter une série de valeurs triées qui vient s’enrichir, au fil du temps, de nou-
6.15
velles valeurs). C’est l’un des seuls algorithmes de tri dont la complexité est meilleure
25.8

quand le tableau initial possède un « certain ordre ».


c) Complexité du tri rapide :
195.

La complexité du tri rapide pour trier une liste de n éléments est égale à la complexité
pour le tri de p et de q éléments où p + q + 1 = n. Soit C(n) = C(p) + C(q) + const ant e.
:
6811

Dans le meilleur des cas, p = q = n/2, soit C(n) = 2C(q). On peut alors montrer que la
complexité en moyenne est en O(n · ln(n)).
8892

2 Spirale de pentagones
166:

Exercice 59. La spirale régulière ci-dessous est constituée de 50 pentagones (la longueur du
0833

plus grand côté étant 370 pixels), emboîtés de sorte qu’entre deux pentagones consécutifs
inscrits l’un dans l’autre, le côté du plus petit est une réduction de 10 % du côté du plus grand.
I:211

Programmer le dessin de cette spirale.


CES
com:
rvox.
la
scho
univ.
234 Programmation en Python pour les mathématiques

Solution.
pentagones.py
from math import *
from turtle import *

def pentagone(cote):
for n in range(5):
forward(cote)
left(72)

def spirale(nombre_triangles, cote):


coeff = 0.1
a = cote
d = coeff * a
for i in range(nombre_triangles):

8155
pentagone(a)
d = coeff * a

6368
forward(d)
a_prime = sqrt((a-d)**2 + d**2 -2*(a-d)*d*(1-sqrt(5))/4)
angle = (180/pi) * acos((a_prime**2 + a**2 - 2*a*d)/(2*a_prime*(a-d)))

5:16
left(angle)
a = a_prime
6.15
25.8
up()
goto(-190,-210)
down()
195.

spirale(50, 370)
up()
:
6811
8892

3 Courbe du dragon
166:

La courbe du dragon est une autre application de la récursivité.


0833

dragon1.py
I:211

from math import sqrt


from turtle import *
CES

def dragon_gauche(taille, niveau):


color("red")
com:

if niveau == 0:
forward(taille)
rvox.

else:
left(45)
la

dragon_gauche(taille/sqrt(2), niveau-1)
scho

right(90)
dragon_droite(taille/sqrt(2), niveau-1)
univ.

left(45)
Récursivité 235

def dragon_droite(taille, niveau):


color("blue")
if niveau == 0:
forward(taille)
else:
right(45)
dragon_gauche(taille/sqrt(2), niveau-1)
left(90)
dragon_droite(taille/sqrt(2), niveau-1)
right(45)

dragon_droite(400, 12)

La courbe du dragon pave le plan, comme le montre la figure suivante :

8155
6368
5:16
6.15
25.8
195. :
6811

dragon2.py
from math import sqrt
8892

from turtle import *

def dragon_gauche(taille, niveau):


166:

if niveau == 0:
forward(taille)
0833

else:
left(45)
I:211

dragon_gauche(taille/sqrt(2), niveau-1)
right(90)
dragon_droite(taille/sqrt(2), niveau-1)
CES

left(45)
com:

def dragon_droite(taille, niveau):


if niveau == 0:
rvox.

forward(taille)
else:
la

right(45)
scho

dragon_gauche(taille/sqrt(2), niveau-1)
left(90)
dragon_droite(taille/sqrt(2), niveau-1)
univ.
236 Programmation en Python pour les mathématiques

right(45)

def figure():
color("red")
dragon_gauche(200, 10)
up()
goto(0, 0)
right(90)
down()
color("blue")
dragon_gauche(200, 10)
up()
goto(0, 0)
right(90)
down()

8155
color("green")
dragon_gauche(200, 10)

6368
up()
goto(0, 0)
right(90)

5:16
down()
color("gray")
dragon_gauche(200, 10) 6.15
up()
25.8

figure()
195. :
6811

4 Triangle de Sierpińsky
8892

4.1 Construction récursive


Les deux dessins ci-dessous sont obtenus respectivement par les programmes sierpinsky1.py
166:

et sierpinsky2.py :
0833
I:211
CES
com:
rvox.
la
scho

sierpinsky1.py
from turtle import *
univ.
Récursivité 237

def drapeau(longueur, niveau):


if niveau == 0:
forward(longueur)
else:
drapeau(longueur/2, niveau-1)
left(120)
drapeau(longueur/2, niveau-1)
left(120)
drapeau(longueur/2, niveau-1)
left(120)
forward(longueur)

def triangle(longueur, niveau):


for i in range(3):
drapeau(longueur, niveau)

8155
left(120)

6368
triangle(600, 5)

Avec un peu de couleur :

5:16
sierpinsky2.py
from turtle import *
6.15
def drapeau(longueur, niveau):
25.8

if niveau == 0:
down()
195.

cap = heading()
if cap == 0 or cap == 180:
:
6811

color("red")
if cap == 60 or cap == 240:
8892

color("green")
if cap == 120 or cap == 300:
color("blue")
166:

forward(longueur)
up()
0833

else:
drapeau(longueur/2, niveau-1)
I:211

left(120)
drapeau(longueur/2, niveau-1)
left(120)
CES

drapeau(longueur/2, niveau-1)
left(120)
com:

forward(longueur)
rvox.

def triangle(longueur, niveau):


for i in range(3):
la

drapeau(longueur, niveau)
scho

left(120)
univ.

triangle(600, 6)
238 Programmation en Python pour les mathématiques

8155
6368
5:16
4.2 Construction aléatoire 6.15
25.8

On peut obtenir aléatoirement le triangle de S IERPI ŃSKI par un algorithme très simple :
195.

ABC est un triangle, M est un point quelconque.


a) Choisir au hasard l’un des trois points A, B et C.
:
6811

b) Construire le point M1 milieu du segment liant le point précé-


dent au point M.
8892

c) Recommencer à la première étape en faisant jouer au point M1


le rôle du point M.
166:

Bien évidemment, il faut poursuivre l’algorithme assez longtemps sur le papier pour observer
0833

quelque chose, sans compter les segments [Mi Mi +1 ] uniquement utiles à la construction mais
qui encombrent le dessin. . .
I:211

Cet algorithme donne la figure suivante, réalisée avec le programme sierpinsky3.py. Ce


programme fait appel à un nouveau type objet défini par le programmeur pour représenter
CES

un objet « point du plan », repéré par ses coordonnées cartésiennes et doté d’une couleur.
sierpinsky3.py
com:

from math import sqrt


import random
rvox.

from turtle import *


la

delta = -300
scho

loupe = 600
univ.

class point(object):
Récursivité 239

""" Point du plan """

def __init__(self, x = 0, y = 0, couleur = "black"):


self.x = x
self.y = y
self.couleur = couleur

def milieu(self, p, couleur = "black"):


self.x = (self.x + p.x)/2
self.y = (self.y + p.y)/2
self.couleur = couleur

def rouge(self):
self.couleur = "red"

8155
def vert(self):
self.couleur = "green"

6368
def bleu(self):
self.couleur = "blue"

5:16
def croix(self):
down() 6.15
for i in range(4):
25.8

forward(3)
backward(3)
195.

left(90)
up()
:
6811

def imprime(self):
goto(self.x*loupe + delta, self.y*loupe + delta)
8892

color(self.couleur)
self.croix()
166:

def figure(points):
0833

a = point(0.0, 0.0, "red")


b = point(1.0, 0.0, "green")
I:211

c = point(0.5, sqrt(3)/2, "blue")

# premier point m, i.e. m(0) choisi ici comme isobarycentre


CES

m = point(0.5, sqrt(3)/6)
com:

if points <= 0:
points = 200
rvox.

for i in range(points):
# obtention aléatoire du point suivant:
la
scho

# m(i+1) = milieu [m(i), a ou b ou c]


alea = random.randrange(3)
if alea == 0:
univ.
240 Programmation en Python pour les mathématiques

m.milieu(a)
if alea == 1:
m.milieu(b)
if alea == 2:
m.milieu(c)
# obtention aléatoire de la couleur du point
alea = random.randrange(3)
if alea == 0:
m.rouge()
if alea == 1:
m.vert()
if alea == 2:
m.bleu()
m.imprime()

8155
up()
speed("fastest")

6368
figure(2000)

Le programme donne une figure comme la suivante :

5:16
6.15
25.8
195. :
6811
8892
166:

5 Sommes de termes d’une suite géométrique


0833

On laisse en exercice l’écriture d’un programme pour l’obtention des sommes partielles de la
+∞
P ¡ 1 ¢i
I:211

série 4 .
i =1
La tortue LOGO du module standard « turtle » permet de donner aisément une interpréta-
CES

tion géométrique de la réponse :


com:
rvox.
la
scho
univ.

Vous aimerez peut-être aussi