Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Recursivite

Télécharger au format pdf ou txt
Télécharger au format pdf ou txt
Vous êtes sur la page 1sur 22

Récursivité PSI* 17-18

Récursivité

I. Introduction
I.1. Définition

Déf 1:
Une procédure récursive est une procédure qui s’appelle elle-même, une ou plusieurs fois.

Une procédure non récursive ne comporte donc que des appels à d’autres procédures.
La récursivité est un domaine très intéressant de l’informatique, un peu abstrait, mais très élégant ; elle permet
de résoudre certains problèmes d’une manière très rapide, alors que si on devait les résoudre de manière itérative,
il nous faudrait beaucoup plus de temps et de structures de données intermédiaires.
L’exemple le plus simple (même s’il n’est pas très intéressant) pour décrire le principe de la récursivité est celui
de la fonction factorielle.
Celle-ci est définie de façon directe par :

1
 si n = 0
n
n! = Y
 i si n ∈ N .


i=1

Cela correspond au programme itératif suivant :

Algorithme 1 : Factorielle itérative


Données : n : entier naturel
Résultat : n!
def fact(n) :
p ← 1;
pour i de 2 à n faire
p←p∗i
finpour
retourner p
fin

Mais on peut aussi définir n! par récurrence :


(
1 si n = 0
n! =
n · (n − 1)! si n ∈ N∗ .

Cela correspond au programme récursif suivant :

Algorithme 2 : Factorielle récursive


Données : n : entier naturel
Résultat : n!
def fact(n) :
si n=0 alors
retourner 1
sinon
retourner n*fact(n − 1 )
finsi
fin

Une procédure récursive doit toujours comporter un ou plusieurs cas ≪ de base ≫ qui sont définis de manière
non récursive (le cas n = 0 dans l’exemple ci-dessus).

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 1/22 4 décembre 2017


Récursivité PSI* 17-18

I.2. Éxécution d’une procédure récursive


La récursivité utilise toujours la pile du programme en cours.
On appelle ≪ pile ≫ une zone mémoire réservée à chaque programme ; sa taille peut être fixée manuellement
par l’utilisateur. Son rôle est de stocker les variables locales et les paramètres d’une procédure.
Dans une procédure récursive, toutes les variables locales ainsi que les adresses de retour des fonctions
sont stockées dans la pile, et empilées autant de fois qu’il y a d’appels récursifs. Donc la pile se remplit
progressivement, et si on ne fait pas attention on arrive à un ≪ débordement de pile ≫ (stack overflow). En
Python la taille de la pile est fixée à 1000 ; mais on peut la modifier par les instructions :
import sys
sys.setrecursionlimit(10000)
Lorsque la condition d’arrêt est remplie, les variables sont désempilées.
Le programme ci-dessous permet de visualiser la pile des variables locales et des appels lors de l’exécution de
la factorielle récursive :

1 from inspect import *


2

3 def factoVisu(n):
4 print(locals())
5 if n==0:
6 pile = stack()
7 for i in range(len(pile)):
8 print(getframeinfo(pile[i][0]))
9 return 1
10 else:
11 res = n*factoVisu(n-1)
12 print(locals())
13 return res
14

15 print(factoVisu(5))

{'n': 5}
{'n': 4}
{'n': 3}
{'n': 2}
{'n': 1}
{'n': 0}
Traceback(filename='pythontex-files-recursivite\\sympy_default_default.py', lineno=62, function='fa
Traceback(filename='pythontex-files-recursivite\\sympy_default_default.py', lineno=65, function='fa
Traceback(filename='pythontex-files-recursivite\\sympy_default_default.py', lineno=65, function='fa
Traceback(filename='pythontex-files-recursivite\\sympy_default_default.py', lineno=65, function='fa
Traceback(filename='pythontex-files-recursivite\\sympy_default_default.py', lineno=65, function='fa
Traceback(filename='pythontex-files-recursivite\\sympy_default_default.py', lineno=65, function='fa
Traceback(filename='pythontex-files-recursivite\\sympy_default_default.py', lineno=69, function='<m
{'n': 1, 'res': 1}
{'n': 2, 'res': 2}
{'n': 3, 'res': 6}
{'n': 4, 'res': 24}
{'n': 5, 'res': 120}
120

I.3. Efficacité
• Un programme récursif bien conçu est un peu plus long à l’exécution que sa version itérative corres-
pondante, d’autant plus que celle-ci est
√ simple, comme le montre l’exemple suivant où l’on a calculé les
termes de la suite définie par un+1 = 1 + un :

1 import timeit
2 from math import sqrt
3

4 import sys

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 2/22 4 décembre 2017


Récursivité PSI* 17-18

5 sys.setrecursionlimit(10000)
6

7 def iteratif(n):
8 u = 0
9 for i in range(n):
10 u = sqrt(1 + u)
11 return u
12

13 def recursif(n):
14 if n == 0:
15 return 1
16 else:
17 return sqrt(1 + recursif(n-1))
18

19 print(timeit.timeit(stmt = 'iteratif(1000)', setup = 'from __main__ import iteratif', \


20 number = 10000))
21

22 print(timeit.timeit(stmt = 'recursif(1000)', setup = 'from __main__ import recursif',\


23 number = 10000))
1.6906291449368072
3.617282511664553

• Cela peut être encore pire si l’on abuse de la récursivité. Considérons par exemple la suite de Fibonacci
définie par :
u0 = 0 , u1 = 1 et ∀ n ∈ N, un+2 = un+1 + un
En voici une version itérative :

Algorithme 3 : Suite de Fibonacci itérative


Données : n : entier naturel
Résultat : n e terme de la suite de Fibonacci
def fibo(n) :
si n = 0 alors
retourner 0
sinon
u0 ← 0 ;
u1 ← 1 ;
/* à l’étape i , u0 vaut ui et u1 vaut ui+1 */;
pour i de 1 à n − 1 faire
u0 , u1 ← u1 , u0 + u1
finpour
retourner u1
finsi
fin

et la version récursive :

Algorithme 4 : Suite de Fibonacci récursive


Données : n : entier naturel
Résultat : n e terme de la suite de Fibonacci
def fibo(n) :
si n = 0 alors
retourner 0
sinon si n = 1 alors
retourner 1
sinon
retourner fibo(n − 1) + fibo(n − 2)
finsi
fin

Voici un arbre qui représente l’exécution de ce programme pour n = 5 .

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 3/22 4 décembre 2017


Récursivité PSI* 17-18

u5

u4 u3

u3 u2 u2 u1

u2 u1 u1 u0 u1 u0

u1 u0

Pour calculer u5 , il faut d’abord calculeru4 et u3 . Or pour calculer u4 , il faut calculer u3 etc. . . Puisque
les appels récursifs sont indépendants, la valeur de u2 par exemple va être calculée 3 fois. Pour des valeurs
de n supérieures, on s’aperçoit qu’un grand nombre de valeurs des ui sont calculées plusieurs fois.
Plus précisément, notons A(n) le nombre d’additions effectuées par la version récursive. A(n) vérifie :

A(0) = A(1) = 0 et ∀ n > 2, A(n) = A(n − 1) + A(n − 2) + 1 .

La suite an de terme général an = A(n) + 1 vérifie donc les relations :

a0 = a1 = 1 et ∀ n > 2, an = an−1 + an−2 ,

ce qui permet de trouver facilement (récurrence linéaire d’ordre 2) :


 √ n+1  √ n+1 !
1 1+ 5 1− 5
∀ n ∈ N, A(n) = √ − +1.
5 2 2

 √ n+1 √
1 1+ 5 1+ 5
On voit donc que A(n) ∼ √
n→+∞ 5 2
(avec
2
≈ 1, 618 ), alors que le nombre d’additions
dans la version itérative est tout simplement égal à n − 1 ...
• Il existe cependant une implémentation du calcul du n-ième élément de la suite de Fibonacci qui contourne
cette difficulté.

Algorithme 5 : Suite de Fibonacci récursive améliorée


Données : n : entier naturel, a et b : valeurs des deux termes
précédents de la suite
Résultat : n e terme de la suite de Fibonacci
def fibo(n,a,b) :
si n = 0 alors
retourner a
sinon si n = 1 alors
retourner b
sinon
retourner fibo(n − 1, b, a + b)
fin

La fonction fibo(n, a, b) ci-dessus renvoie le n e élément de la suite un (a, b) définie par :



a
 si n = 0
un (a, b) = b si n = 1

un−1 (a, b) + un−2 (a, b) sinon .

Pour obtenir le n e terme de la suite de Fibonacci classique, il suffit donc d’appeler fibo(n, 0, 1). Dans
cette version, les valeurs utiles ne sont calculées qu’une seule fois. On a utilisé pour cela la relation :

∀ n > 1, un (a, b) = un−1 (b, a + b) .

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 4/22 4 décembre 2017


Récursivité PSI* 17-18

On remarque que dans ce dernier programme l’appel récursif est la dernière instruction exécutée : il n’y
a pas de traitement du résultat de l’appel récursif. Un tel programme est dit récursif terminal. Il y a
une différence fondamentale entre la version récursive et la version récursive terminale : dans la version
récursive simple (algorithme 4 ou algorithme 2) , l’appel récursif n’est pas la dernière opération réalisée
(il est utilisé dans un calcul), il faut donc que la machine garde en mémoire l’état de son calcul. Cela
conduit aux problèmes de mémoire et de temps de calcul cités précédemment. Dans la version récursive
terminale, l’appel récursif est la dernière action réalisée : l’interpréteur ou le compilateur sont capables
de détecter ces situations et traitent alors le programme comme un programme itératif, ce qui n’engendre
pas les problèmes cités précédemment.
• Une autre idée pour éviter les appels redondants est la mémoı̈sation. Cela consiste à mémoriser au fur et
à mesure, dans une variable globale, les valeurs prises par la fonction. Cela consomme malheureusement
beaucoup de place mémoire !
Dans la version ci-dessous, on a utilisé une liste pour stocker les valeurs calculées au fur et à mesure ; au
moment de calculer fibo(n), on commence par vérifier s’il existe dans la liste une valeur correspondant à
n ; si oui, on renvoie directement la valeur stockée, sinon on la calcule récursivement et on stocke cette
nouvelle valeur dans la liste.
Algorithme 6 : Suite de Fibonacci récursive avec
mémoı̈sation
Données : n : entier naturel, a et b : valeurs des deux termes
initiaux de la suite
Résultat : n e terme de la suite de Fibonacci
def fibo memo(n) :
liste = [N one] ∗ (n + 1);
liste[0] = 0 ;
liste[1] = 1 ;
def fibo(n) :
si liste[n] 6= None alors
retourner liste[n]
sinon
x = f ibo memo(n − 1) + f ibo memo(n − 2) ;
liste[n] = x ;
retourner x
finsi
fin
retourner fibo(n)
fin

II. Exemples d’algorithmes récursifs


II.1. Exponentiation rapide
Étant donné une opération ⋆ sur des objets (par exemple des réels, des matrices. . .), l’objectif est de calculer
xn = x ⋆ x ⋆ . . . ⋆ x en un nombre minimal d’opérations. On suppose que l’opération ⋆ est associative.
Un premier algorithme naı̈f donne un coût en nombre d’opérations en O (n).

Algorithme 7 : Exponentiation naı̈ve


Données : x : objet d’un certain type, et n : entier
naturel
Résultat : la valeur de xn
def puiss(x,n) :
si n=0 alors
retourner 1
sinon
y = x;
pour i de 2 à n faire
y =y⋆x
finpour
retourner y
finsi
fin

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 5/22 4 décembre 2017


Récursivité PSI* 17-18

II.1.1. Exponentiation rapide, 1ère version


En utilisant les relations :
x2n = (x ⋆ x)n et x2n+1 = x ⋆ (x ⋆ x)n
on peut écrire l’algorithme récursif suivant :

Algorithme 8 : Exponentiation rapide, récursive, 1re version


Données : x : objet d’un certain type, et n : entier naturel
Résultat : la valeur de xn
def puiss(x,n) :
si n=0 alors
retourner 1
sinon
si n est pair alors
retourner puiss(x ⋆ x, n//2)
sinon
retourner x ⋆ puiss(x ⋆ x, n//2)
finsi
finsi
fin

Cherchons le coût de cet algorithme en nombre d’appels à des opérations ≪ élémentaires ≫ .


Pour cela on démontre facilement par récurrence sur k la propriété :

Pk : si 2k 6 n < 2k+1 , il y a k + 1 appels récursifs

ce qui prouve que le nombre d’appels est égal à ⌊log2 (n)⌋ + 1 . Puisqu’il y a un test, une division entière et au
maximum 2 multiplications par appel, le nombre d’opération est un O (log2 (n)).
Pour écrire une version itérative de cet algorithme, nous allons relier son fonctionnement à la l’écriture en base
2 de l’exposant n.
Par exemple, si n = 89 , n s’écrit en base 2 : n = 1 + 8 + 16 + 64 = 1011001. Et l’on a :

x89 = x ⋆ x8 ⋆ x16 ⋆ x64 .

La suite x, x2 , x4 , x8 , x16 , x32 , x64 s’obtient par des élévations au carré successives ; le résultat final est le
produit de ceux des termes de cette suite qui correspondent à un chiffre 1 dans l’écriture en base 2 de n.
Cela donne l’algorithme itératif suivant :

Algorithme 9 : Exponentiation rapide, itérative


Données : x : objet d’un certain type, et n : entier naturel
Résultat : la valeur de xn
def puiss(x,n) :
p = 1;
tant que n > 0 faire
si n impair alors
p=x∗p
finsi
x = x ⋆ x;
n = n//2
fintq
retourner p
fin

II.1.2. Exponentiation rapide, 2ème version


En utilisant les relations :
x2n = xn ⋆ xn et x2n+1 = x ⋆ xn ⋆ xn
on peut écrire l’algorithme récursif suivant :

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 6/22 4 décembre 2017


Récursivité PSI* 17-18

Algorithme 10 : Exponentiation rapide, récursive, 2e version


Données : x : objet d’un certain type, et n : entier naturel
Résultat : la valeur de xn
def puiss(x,n) :
si n=0 alors
retourner 1
sinon
y = puiss(x, n//2);
si n est pair alors
retourner y ⋆ y
sinon
retourner x ⋆ y ⋆ y
finsi
finsi
fin

Une version itérative de cet algorithme est plus difficile. On utilise ici encore l’écriture binaire de n, mais cette
fois-ci on la lit de gauche à droite. Par exemple, dans le cas de n = 89 = 1 + 8 + 16 + 64 = 1011001, on écrira
x = x64 ⋆ x16 ⋆ x8 ⋆ x .
Dans cette écriture, x64 correspond à x élevé au carré 6 fois, 6 correspondant à la position du 1 de gauche,
puis x16 correspond à x élevé au carré 4 fois, 4 correspondant à la position du second 1 à partir de la gauche
etc. . . Cela donne le programme itératif suivant :

Algorithme 11 : Exponentiation rapide, itérative, 2e version


Données : x : objet d’un certain type, et n : entier naturel
Résultat : la valeur de xn
def puiss(x,n) :
/* on commence par chercher l’écriture de n en base 2 */;
liste = vide;
tant que n 6= 0 faire
si n pair alors
ajouter 0 à la fin de liste
sinon
ajouter 1 à la fin de liste
finsi
n = n//2 ;
fintq
p = 1;
tant que longueur(liste)> 0 faire
p = p ⋆ p;
b = liste.pop();
/* dernier élément de la liste, que l’on supprime */;
si b = 1 alors
p=p⋆x
finsi
fintq
fin

Voici les programmes Python correspondant avec les tests de durée.

1 from time import time


2

3 import sys
4 sys.setrecursionlimit(10000)
5

6 nmax = 2000 # pour les test de durée


7

8 # Versions récursives d'abord


9

10 def puiss_rec1(x, n):

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 7/22 4 décembre 2017


Récursivité PSI* 17-18

11 if n == 0:
12 return 1
13 else:
14 return x * puiss_rec1(x, n-1)
15

16 def puiss_rec2(x, n):


17 if n == 0:
18 return 1
19 else:
20 if n%2 ==0:
21 return puiss_rec2(x*x, n//2)
22 else:
23 return x * puiss_rec2(x*x, n//2)
24

25 def puiss_rec3(x, n):


26 if n == 0:
27 return 1
28 else:
29 y = puiss_rec3(x, n//2)
30 if n%2 == 0:
31 return y*y
32 else:
33 return x*y*y
34

35 print("Calcul des iˆj par diférentes méthodes:\n")


36 debut = time()
37 for i in range(1,20):
38 for j in range(0,nmax):
39 puiss_rec1(i,j)
40 print("Récursif primaire", "i<=20 et j<=",nmax, "--->", time() - debut)
41

42 debut = time()
43 for i in range(1,100):
44 for j in range(0,nmax):
45 puiss_rec2(i,j)
46 print("Récursif 1ère version", "i<=100 et j<=",nmax, "--->",time() - debut)
47

48 debut = time()
49 for i in range(1,100):
50 for j in range(0,nmax):
51 puiss_rec3(i,j)
52 print("Récursif 2ème version", "i<=100 et j<=",nmax, "--->",time() - debut)
53

54 # Versions itératives correspondantes


55

56 def puiss_iter1(x, n):


57 p = 1
58 for i in range(0,n):
59 p *= x
60 return p
61

62 def puiss_iter2(x, n):


63 res = 1
64 var = x
65 while n!= 0:
66 if n%2 != 0:
67 res *= var
68 var *= var
69 n = n//2
70 return res
71

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 8/22 4 décembre 2017


Récursivité PSI* 17-18

72 def puiss_iter3(x, n):


73 if n==0:
74 return 1
75 # écriture de n en base 2
76 liste_bin = []
77 while n!= 0:
78 liste_bin.append(n%2)
79 n = n//2
80 # calcul
81 liste_bin.pop()
82 # le premier chiffre est forcéement un 1
83 res = x
84 while len(liste_bin) !=0:
85 res *= res
86 if liste_bin.pop() == 1:
87 res *= x
88 return res
89

90 debut = time()
91 for i in range(1,20):
92 for j in range(0,nmax):
93 puiss_iter1(i,j)
94 print("Itératif primaire", "i<=20 et j<=",nmax, "--->",time() - debut)
95

96 debut = time()
97 for i in range(1,100):
98 for j in range(0,nmax):
99 puiss_iter2(i,j)
100 print("Itératif 1ère version", "i<=100 et j<=",nmax, "--->",time() - debut)
101

102 debut = time()


103 for i in range(1,100):
104 for j in range(0,nmax):
105 puiss_iter3(i,j)
106 print("Itératif 2ème version", "i<=100 et j<=",nmax, "--->",time() - debut)
Calcul des iˆj par diférentes méthodes:

Récursif primaire i<=20 et j<= 2000 ---> 15.54701852798462


Récursif 1ère version i<=100 et j<= 2000 ---> 5.559236288070679
Récursif 2ème version i<=100 et j<= 2000 ---> 2.73252010345459
Itératif primaire i<=20 et j<= 2000 ---> 6.501466751098633
Itératif 1ère version i<=100 et j<= 2000 ---> 5.050424814224243
Itératif 2ème version i<=100 et j<= 2000 ---> 2.6194374561309814

II.2. Permutations d’un ensemble


Soit E un ensemble.
On veut créer une liste formée de toutes les permutations de E , et pour cela on procède ainsi : pour chaque
élément x ∈ E , on crée la liste des permutations de E \ {x} , et on rajoute x à la fin de chacune d’elles.
Cela donne le programme Python suivant :

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 9/22 4 décembre 2017


Récursivité PSI* 17-18

1 def permutations(E):
2 n = len(E)
3 if n == 1:
4 return [E]
5 else:
6 liste_permus = []
7 for i in range(0, n):
8 C = E[:i] + E[i+1:]
9 L = permutations(C)
10 for perm in L:
11 perm.append(E[i])
12 liste_permus.append(perm)
13 return liste_permus
14

15 print(len(permutations([ 1, 2, 3, 4, 5, 6, 7, 8,9,10 ])))


16 print(permutations(['a','b','c','d']))
3628800
[['d', 'c', 'b', 'a'], ['c', 'd', 'b', 'a'], ['d', 'b', 'c', 'a'], ['b', 'd', 'c', 'a'], ['c', 'b',

II.3. Tours de Hanoı̈


On dispose d’une pile de n disques de tailles différentes et de trois tours positionnées l’une à côté de l’autre.
Les disques sont disposés sur la tour de gauche par taille décroissante et on veut les amener tous sur la tour de
droite dans le même ordre, en utilisant éventuellement la tour du milieu. Il faut cependant respecter une règle :
à chaque coup, tous les disques doivent se trouver sur une tour et un disque ne peut pas être posé sur un disque
de diamètre inférieur
On peut aborder le problème de la façon suivante : pour envoyer une tour de taille n de la place de gauche à
la place de droite :
– on envoie les n − 1 premiers disques sur la place du milieu, ce qui revient à résoudre le jeu pour n − 1
disques avec le départ à gauche et l’arrivée au milieu ;
– on envoie le disque n à droite ;
– on envoie les n − 1 premiers disques sur la place de droite, ce qui revient à résoudre le jeu pour n − 1
disques avec le départ au milieu et l’arrivée à droite.
Ainsi la résolution pour n disques fait appel à la résolution pour n − 1 disques, deux fois. Construire un
algorithme récursif est alors très simple, mais il nous manque notre condition d’arrèt : résoudre le problème à
0 disque consiste à ne rien faire. On peut donc proposer l’algorithme suivant :

Algorithme 12 : Tours de Hanoı̈ récursif


Données : n : entier indiquant le nombre de disques à déplacer,
dep, int, arr : chaines de caractères pour indiquer les tiges de départ,
d’arrivée et intermédiaire
Résultat : pas de sortie, on affiche les déplacements au fur et à
mesure
def Hanoi(n, dep, int, arr) :
si n=0 alors
retourner rien
sinon
Hanoi(n-1, dep, arr, int);
afficher ”déplacement d’un disque de ” dep ” vers ” arr ;
Hanoi(n-1, int, dep, arr)
finsi
fin

Par exemple, en appelant ce programme avec les arguments (3, “gauche”, “milieu”, “droite”) l’affichage produit
est le suivant : gauche → droite ; gauche → milieu ; droite → milieu ; gauche → droite ; milieu → gauche ;
milieu → droite ; gauche → droite.
Pour résoudre le problème à n tours, on a besoin de résoudre deux fois le problème à n − 1 tours, et d’effectuer
une opération supplémentaire. La relation de récurrence sur le nombre d’opérations Tn est donc :
Tn = 2Tn−1 + 1 soit Tn + 1 = 2(Tn−1 + 1)

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 10/22 4 décembre 2017


Récursivité PSI* 17-18

dont la solution, étant donné que T0 = 0 , vaut Tn = 2n − 1 .

II.4. Évaluation d’un nombre écrit en chiffres romains


Les chiffres romains sont :
M=1000 D=500 C=100 L=50 X=10 V =5 I=1
Les nombres inférieurs à 10 s’écrivent :
I =I 2 =II 3=II 4=IV 5=V 6=VI 7=VII 8= VIII 9=IX X=10
Tous les nombres finissant par 1, 2 ou 3 se terminent dans l’écriture romaine par I, II ou III. Idem pour 6, 7 ou
8.
Cela est valable aussi bien pour les unités que pour les dizaines et centaines ; par exemple, 30 s’écrit XXX, 300
s’écrit CCC.
Tous les nombres finissant par 4 se terminent par IV. Ceux finissant par 40 se terminent par XL etc. . .
Tous les nombres finissant par 9 se terminent par IX. Ceux finissant par 90 se terminent par XC etc. . .
Exemples :
47 = XLVII 97 = CXVII 149 = CXLIX 1990 = MCM 1999 = MCMXCIX 2016 = MMXVI
On veut écrire un algorithme permettant de donner la valeur d’un nombre écrit en chiffres romains. Le principe
est le suivant :
– si le nombre romain a un seul chiffre, sa valeur est celle correspondante ;
– sinon, si le 1er chiffre a une valeur inférieure au 2e, alors on soustrait sa valeur de tout le reste, sinon on
l’additionne.
À faire :
1. Écrire le programme récursif correspondant.
2. Écrire un programme qui réalise l’opération inverse (nombre en base 10 −→ nombre en chiffres romains).

II.5. La courbe du dragon


À partir d’un segment [AB], on construit le point C tel que le triangle ACB dans le sens direct soit isocèle et
rectangle en C , et on recommence le procédé pour les segments [AC] et [BC], etc. . .
La courbe du dragon à la n e étape est la réunion de courbe du dragon d’ordre n − 1 construite sur [AC] et de
celle d’ordre n − 1 construite sur [BC].
La figure ci-dessous représente la courbe obtenue pour n = 20 , A(0, 0) et B(1, 1).

Indications : On démontrera d’abord que si A a pour coordonnées (x, y) et B pour coordonnées (z, t) les
coordonnées de C sont (u, v) avec :
x−y+z+t x+y−z+t
u= et v= ·
2 2

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 11/22 4 décembre 2017


Récursivité PSI* 17-18

II.6. D’autres courbes


II.6.1. Bubbles
On donne une fonction circle([x, y], r, rempli) qui trace à l’écran un cercle (si rempli == False) ou un
disque (si rempli == True) de centre le point de coordonnées (x, y) et de rayon r .

import math
import matplotlib.pyplot as plt

def circle( coords, r, rempli=False):


X, Y = [], []
for t in range(101):
X.append(coords[0] + r*math.cos(t*math.pi/50))
Y.append(coords[1] + r*math.sin(t*math.pi/50))
if rempli:
plt.fill(X, Y, color='blue')
else:
plt.plot(X, Y, color='blue')

Écrire à partir de là deux fonctions récursives permettant d’obtenir les figures ci-dessous.

30
10

20
5

0
10

−5
0
−10

−10
−15

−20
−20

−25
−30
−10 −5 0 5 10 15 20 25 −20 −10 0 10 20

II.6.2. Triangle de Sierpinski


On donne une fonction polygone(liste) qui trace le polygone (plein ou non) dont la liste des sommets est
donnée.

import math
import matplotlib.pyplot as plt

def polygone(rempli=False, *args ):


X, Y = [], []
for point in args:
X.append(point[0])
Y.append(point[1])
X.append(args[0][0])
Y.append(args[0][1])

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 12/22 4 décembre 2017


Récursivité PSI* 17-18

if rempli:
plt.fill(X, Y, color='blue')
else:
plt.plot(X, Y, color='blue')

Écrire une fonction permettant de réaliser les graphiques ci-dessous (les triangles sont équilatéraux).

Sierpinsky(2) Sierpinsky(4, rempli=True) Sierpinsky(7)


1.0 1.0 1.0

0.8 0.8 0.8

0.6 0.6 0.6

0.4 0.4 0.4

0.2 0.2 0.2

0.0 0.0 0.0

0.0 0.2 0.4 0.6 0.8 1.0 0.0 0.2 0.4 0.6 0.8 1.0 0.0 0.2 0.4 0.6 0.8 1.0

III. La méthode Diviser Pour Régner


III.1. Généralités
Les algorithmes de type Diviser Pour Régner sont très classiques en informatique. Les meilleurs algorithmes de
tri fonctionnent par exemple sur le principe Diviser pour régner (tri rapide, tri fusion). De manière générale, ou
peut décrire un algorithme de type diviser pour régner de la façon suivante.

Déf 2:
Un algorithme est de type Diviser Pour Régner s’il comprend les étapes suivantes :
– condition d’arrêt ;
– découpage du problème en sous-problèmes ;
– traitement des sous-problèmes ;
– rassemblement des solutions aux sous-problèmes en une solution au problème principal.

Le tri fusion par exemple respecte cette philosophie : le problème principal est coupé exactement en deux, chaque
sous-liste est triée, puis on effectue une fusion des deux sous-listes triées pour obtenir la liste principale triée.
Dans le cas du tri fusion, le découpage ne coûte rien, mais la recombinaison est coûteuse. à l’inverse dans le tri
rapide, le découpage est coûteux (on sépare la liste autour d’un pivot), mais la recombinaison est immédiate.

III.2. Multiplication rapide de polynômes-1re version


Il s’agit d’écrire un algorithme pour multiplier deux polynômes de degré 6 n − 1 (n > 1 ). Un tel polynôme
n−1
X
P = ak X k sera représenté par la liste [a0 , a1 , . . . , an−1 ] de longueur n.
k=0
La méthode naı̈ve consistant à utiliser les formules :
n−1
X n−1
X
si P = ak X k et Q= bk X k
k=0 k=0

2n−2
X X
alors PQ = ck X k avec ck = ai b j
k=0 i+j=k

a une complexité en O (n2 ) : en effet, il faut faire 2n − 2 additions et 2 × (1 + 2 + . . . + (n − 1)) + n = n2


multiplications.

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 13/22 4 décembre 2017


Récursivité PSI* 17-18

Nous exposons ci-dessous l’algorithme de Karatsuba (1960), qui a une complexité en O (nlog 2 3
) ≈ O (n1,58 ).
On suppose dans un 1er temps que n = 2m est pair. Pour P, Q ∈ Kn−1 [X], on écrit :
(
P = P1 + X m P2
Q = Q1 + X m Q2

avec Pi , Qi ∈ Km−1 [X]. Soient :

R1 = P1 Q1 , R2 = P2 Q2 et R3 = (P1 + P2 )(Q1 + Q2 ) .

On a alors :
P Q = R1 + (R3 − R2 − R1 )X m + X 2m R2
On ne réalise ainsi que 3 multiplications de polynômes de tailles moitié (on ne compte pas les multiplications
par X m et X 2m qui reviennent simplement à décaler les indices) ; on a évidemment fait des additions
supplémentaires, mais l’on considère le coût d’une addition de polynômes comme négligeable devant celui d’une
multiplication (en effet, c’est en O (n) contre O (n2 )).
Puisque les additions et les multiplications par X m ou X 2m prennent un temps proportionnel à n, on peut
dire que le temps d’exécution T (n) vérifie une relation de la forme :
n
T (n) = 3T + an ·
2
On utilise alors le théorème général suivant.

Théorème 1:
n
Si T vérifie la relation de récurrence T (n) = aT + αnc , avec a, b et c des entiers positifs et α
b
un réel positif, alors :
– si c < logb (a) , on a T (n) = O (nlogb (a) ) ;
– si c = logb (a) , on a T (n) = O (nc logb (n)) ;
– c > logb (a) , on a T (n) = O (nc ) .

 Démonstration:
– Supposons dans un 1er temps que n est une puissance de b : n = bm .
On a alors T (bm ) = aT (bm−1 ) + αbmc .
T (bm ) T (bm−1 ) αbmc T (bm ) T (bm−1 ) αbmc
En divisant le tout par am (on suppose a 6= 0 ), on obtient m
= m−1
+ m , soit encore m
− m−1 = m .
a a a a a a
En sommant ces relations pour k 6 m , on a une somme télescopique dont le résultat est :
m
T (bm ) X bkc
m
− T (1) = α .
a ak
k=2

soit :
m 
bc k
X 
T (bm ) = am T (1) + αam × .
a
k=2
Trois cas se distinguent :
– Si c = logb (a) : dans ce cas bc = a et la somme vaut m − 1 et on a T (bm ) = am T (1) + α(m − 1)am , ce qui nous donne
puisque m = logb (n) :
T (n) = O (logb (n)alogb (n) ) = O (nc logb (n)).
– Si c < logb (a) : dans ce cas, la somme converge et on a T (bm ) = O (am ) soit encore T (n) = O (alogb (n) ) = Θ(nlogb (a) ) .
 bc m bc
– Si c > logb (a) : alors la somme diverge donc est équivalente quand m → +∞ à et on a T (bm ) = O (am ×( )m )
a a
soit encore T (n) = O (nc ) .
– Dans le cas général, on encadre n entre deux puissances successives de b et on obtient les mêmes résultats (car la fonction T
est croissante, comme on peut le vérifier par récurrence).

Ainsi dans le cas de l’algorithme de Karatsuba : a = 3 , b = 2 et c = 1 , et log2 3 ≈ 1, 58 > 1 , de sorte que

T (n) = O (nlog2 3 ) ≈ O (n1,58 )

L’algorithme est décrit ci-dessous.

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 14/22 4 décembre 2017


Récursivité PSI* 17-18

Algorithme 13 : Algorithme de Karatsuba


Données : Deux polynômes P et Q de degrés 6 n − 1 représentés
par des listes de longueur n
Résultat : Le produit P Q
def Karatsuba(P,Q) :
si n=1 alors
retourner P Q
sinon
m = n//2 ;
Décomposer P = P1 + X m P2 et Q = Q1 + X m Q2 ;
R1 =Karatsuba(P1 , Q1 );
R2 =Karatsuba(P2 , Q2 );
Former P3 = P1 + P2 et Q3 = Q1 + Q2 ;
R3 =Karatsuba(P3 , Q3 );
retourner R1 + X m (R3 − R2 − R1 ) + X 2m R2
finsi
fin

La figure ci-dessous illustre l’écart des performances ; on a également fait un tracé des temps en utilisant une
échelle logarithmique : si T (n) ≈ anα alors ln(T (n)) est une fonction affine de ln n, de pente α.
On remarque que les pentes calculées de ces droites sont très proches des valeurs théoriques (pour calculer ces
pentes, j’ai utilisé le module stats de scipy et la fonction linregress).

III.3. L’algorithme de Strassen


Une stratégie Diviser pour Régner peut ètre appliquée par exemple au produit de deux matrices comme le montre
l’algorithme de Strassen (1969). Lorsque l’on fait un produit matriciel classique, entre deux matrices de taille
n, il faut de l’ordre de n3 opérations élémentaires (multiplications, additions). Les formules mathématiques ne
laissent guère de possibilités.
L’idée de l’algorithme de Strassen est de faire ce produit par blocs, mais d’une façon bien  particulière.

A1,1 A1,2
Si on cherche par exemple à effectuer le produit des matrices de taille n (n pair) A = et
A2,1 A2,2
   
B1,1 B1,2 C1,1 C1,2
B= et que l’on note C = le produit, faire un produit par bloc classique demande
B2,1 B2,2 C2,1 C2,2
n
8 multiplications matricielles de matrices carrées de taille et 4 sommes, ce qui n’apporte aucun avantage en
2
termes de complexité :

C1,1 = A1,1 B1,1 + A1,2 B2,1 C1,2 = A1,1 B1,2 + A1,2 B2,2

C2,1 = A2,1 B1,1 + A2,2 B2,1 C2,2 = A2,1 B1,2 + A2,2 B2,2
Mais Strassen a eu l’idée de définir des matrices intermédiaires, au nombre de 7. L’idée principale est que la
somme de matrices demande un nombre d’opérations plus faible que la multiplication et qu’il faut donc réduire

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 15/22 4 décembre 2017


Récursivité PSI* 17-18

le nombre de multiplications, quitte à augmenter le nombre d’additions. Les matrices intermédiaires sont les
suivantes :

M1 = (A1,1 + A2,2 )(B1,1 + B2,2 ) M2 = (A2,1 + A2,2 )B1,1 M3 = A1,1 (B1,2 − B2,2 )

M4 = A2,2 (B2,1 − B1,1 ) M5 = (A1,1 + A1,2 )B2,2 M6 = (A2,1 − A1,1 )(B1,1 + B1,2 )
M7 = (A1,2 − A2,2 )(B2,1 + B2,2 )
On récupère alors la matrice C grâce aux relations :

C1,1 = M1 + M4 − M5 + M7

C2,1 = M2 + M4
C1,2 = M3 + M5
C2,2 = M1 − M2 + M3 + M6
n
On n’a donc besoin que de 7 multiplications matricielles de matrices carrées de taille et 18 sommes, ce qui
2
apporte un vrai plus en terme de complexité comme nous allons le voir.
Dans l’algorithme naı̈f de multiplication par blocs, si T (n) représente le nombre d’opérations, on a la relation
de récurrence suivante : n  n 2
T (n) = 8 ∗ T +4 .
2 2
Avec les notations du théorème précédent, on a donc α = 1, a = 8, b = 2 et c = 2 . D’où c < logb (a) et donc
T (n) = O (n3 ) .
Dans le cas de l’algorithme de Strassen, on trouve
 n 2
T (n) = 7T (n) + 18
2
9
soit α = , a = 7, b = 2 et c = 2 . Encore une fois, c < logb a et donc T (n) = O (nlog2 (7) ) = Θ(n2,807 ) ce qui est
2
mieux. . .
Implementation
On se contentera d’écrire l’algorithme de Strassen pour des matrices carrées dont la taille est une puissance de
2 (ce n’est pas vraiment une limitation car n’importe quelle matrice peut devenir de cette forme en complétant
les lignes et les colonnes par des 0.)
Pour les matrices de ≪ petite ≫ taille, on écrira une procédure de calcul du produit matriciel qui utilise les
formules habituelles, car l’algorithme de Strassen fait perdre du temps si n est petit.
Les matrices seront définies par des tableaux de NumPy. On rappelle ci-dessous quelques commandes sur ces
tableaux.

import numpy as np

A = np.random.random( (x,y) )
# renvoie une matrice de dimensions x*y avec des coefficients
# aléatoires entre 0 et 1
A.shape
# renvoie le tuple (x,y) correspondant à la taille de la matrice
A[i,j]
# élément d'indices i et j
# les indices commencent à 0
A[i:]
# renvoie la ième ligne de A
A[:,j]
# renvoie la jeme colonne de A
np.concatenate( (A,B), axis=0)
# concatène des matrices de tailles compatibles
# dans la direction verticale
np.concatenate( (A,B), axis=1)
# concatène des matrices de tailles compatibles
# dans la direction horizontale
np.dot(A,B)

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 16/22 4 décembre 2017


Récursivité PSI* 17-18

# calcule le produit matriciel AB

La figure ci-dessous illustre les performances de la méthode comparée à la multiplication matricielle classique,
mais on reste bien loin de la fonction implémentée dans NumPy !

III.4. Multiplication rapide de polynômes- 2e version


On présente ici une méthode efficace de multiplication de polynômes utilisant la transformée de Fourier.

Déf 3:
Si A est un polynôme de degré n dont les coefficients sont a0 , a1 , . . . , an et ω une racine primitive ℓ -ième
de l’unité, la transformée de Fourier de A est la donnée des valeurs (A(ω 0 ), A(ω 1 ), . . . , A(ω ℓ−1 ).

Pour représenter un polynôme informatiquement, on peut utiliser plusieurs méthodes : soit le polynôme est
représenté par ses coefficients sous la forme d’une liste, soit par sa valeur en certains points prédéfinis. Ces deux
représentations ont leur avantage. Par exemple la représentation par points/valeurs est très efficace pour le
calcul du produit de deux polynômes : si A et B sont deux polynômes de degré n dont les valeurs sont connues
en 2n + 1 points, alors on connait les valeurs du polynôme AB en ces 2n + 1 points par simple multiplication
dans les réels : il en faut 2n + 1 . Les résultats classiques d’interpolation nous permettent de dire que cela est
suffisant pour connaı̂tre exactement AB.
La représentation points/valeurs n’est cependant pas efficace pour l’évaluation d’un polynôme en d’autres points
que les points d’interpolation. Dans ce cas, c’est la représentation par coefficients qui est la plus utilisée, couplée
avec la méthode de Hörner pour l’évaluation.
Xn
Si l’on veut évaluer le polynôme A = ai X i , au point x, on écrit :
i=0

A(x) = a0 + x(a1 + x(a2 + x( . . . + x(an−2 + x(an−1 + xan )) . . .)))

Cette méthode nécessite de l’ordre de n additions et n multiplications, ce qui est mieux que la méthode naı̈ve
en n2 .
Mais concernant la multiplication, la représentation par coefficients est très peu efficace. Si on pose C = AB ,
i
X
les coefficients de A étant les ai , ceux de B les bi et ceux de C les ci , on a ci = ak bi−k . On comprend qu’il
k=0
faut alors de l’ordre de n2 multiplications et additions : c’est un ordre de grandeur supérieur à la représentation
points/valeurs. La représentation par coefficients étant plus répandue, il est légitime de se demander si on ne
pourrait pas utiliser la représentation points/valeurs pour la multiplication, en allant chercher par exemple des
propriétés d’interpolations.
La bonne idée est d’interpoler sur les racines de l’unité. Dans la suite, on considère que A et B sont deux
polynômes de degré 6 n − 1 .

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 17/22 4 décembre 2017


Récursivité PSI* 17-18

Commençons par la phase d’évaluation. Soit ω une racine primitive n e de l’unité, on cherche à calculer A(ω k )
pour 0 6 k 6 n − 1 :
n−1
X
A(ω k ) = ai ω ki
i=0
X X
= a2i ω 2ki + a2i+1 ω k(2i+1)
062i6n−1 062i+16n−1
X X
2ki
= a2i ω + ωk a2i+1 ω 2ki
062i6n−1 062i+16n−1
2k k 2k
= P (ω ) + ω I(ω ) .
Ainsi pour calculer l’évaluation sur une racine de l’unité, il suffit de calculer l’évaluation en ω 2 des polynômes
n
de degré composés des coefficients d’indice pair (polynôme P ) pour l’un et des coefficients d’indice impair
2
(polynôme I ) pour l’autre (si ω est une racine n e de l’unité, ω 2 en est une racine (n/2) e). Pour obtenir la
transformée de Fourier d’un polynôme A, il faudra faire cette évaluation sur toutes les racines n es de l’unité
pour obtenir la liste [A(ω 0 ), . . . , A(ω n−1 )], mais les polynômes P et I restent les mêmes.
n
On a ainsi divisé le problème de taille n en deux sous problèmes de taille qu’il faut traiter tous les deux.
2
Il faudra prévoir n opérations pour séparer les coefficients pairs et impairs et deux opérations à chaque fois
(une multiplication et une addition) pour construire la solution. La relation de récurrence vérifiée par le nombre
d’opérations est donc de la forme :
n n
T (n) = 2T + n + 2n = 2T + 3n
2 2
On se trouve dans les conditions du théorème avec α = 3, a = 2, b = 2 et c = 1 . On est donc dans le cas
d’égalité et T (n) = O (n log2 (n)) .
On effectue cette opération sur les deux polynômes A et B , puis on fait le produit terme à terme des deux
listes pour obtenir la transformée de Fourier du polynôme AB . Cette opération s’effectue en temps linéaire.
Reste à savoir comment retrouver, à partir de sa transformée de Fourier, les coefficients d’un polynôme.Soit P
π
un polynôme de degré 6 n−1 . On note γk le k e coefficient de sa transformée : γk = P (ω k ) avec ω = exp 2i .
n
On a pour un entier i 6 n − 1 donné :
n−1
X n−1
X n−1
X n−1
X n−1
X n−1
X 2n−1
X k
γk ω −ik = aj ω kj ω −ki = aj ω k(j−i) = aj ω (j−i)
k=0 k=0 j=0 j=0 k=0 j=0 k=0

La deuxième somme est nulle sauf si j = i : en effet le fait d’avoir choisi les racines n-ièmes de l’unité assure
que ω j−i 6= 1 sauf si i = j :
n−1
X
γk ω −ik = nai .
k=0
Ainsi pour retrouver ai il suffit de refaire une transformée de Fourier en prenant cette fois ω −1 comme élément
de base pour l’évaluation. Retrouver les coefficients se fera donc encore avec une complexité de l’ordre de
n log(n).
La stratégie pour multiplier deux polynômes est illustrée par le schéma ci-dessous.

À faire :
1. Écrire une procédure récursive permettant de calculer la transformée de Fourier d’un polynôme de degré
6 n − 1 donné par la liste de ses coefficients. On suppose que n est une puissance de 2 .
Pour cela on pourra utiliser le module cmath qui permet de manipuler des nombres complexes.

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 18/22 4 décembre 2017


Récursivité PSI* 17-18

import cmath as cm

# Un nombre complexe z est représenté par un couple (x,y) de nombres réels


# et est affiché sous la forme x + yj
z = 2+1j
# les parties réelle et imaginaire s'obtiennent par z.real et z.imag

# le module cmath contient entre autres les fonctions suivantes

abs(z)
# calcule le module de z
phase(z)
# calcule un argument de z entre -pi et pi
polar(z)
# retourne le couple (r, theta)
rect(r,theta)
# retourne le complexe de module r et d'argument theta

# la plupart des fonctions réelles présentes dans le module math


# possèdent leur équivalent complexe dans le module cmath:
# exp, sqrt, pi, ...

L’algorithme est expliqué ci-dessous (cette figure ainsi que la précédente sont issues de la ≪ bible ≫ :
Algorithmique, de Cormen aux Éditions Dunod (1200 pages !))

2. Écrire une procédure pour retrouver les coefficients d’un polynôme à partir de sa transformée de Fourier.
3. En déduire une procédure permettant de calculer le produit de deux polynômes A et B donnés par
leurs coefficients. On commencera pour cela par chercher la plus grande puissance de 2 supérieure à
deg A + deg B et on complètera la liste des coefficients par des 0 .

III.5. Médiane d’un ensemble


L’algorithme exposé ici permet de calculer le n e élément d’un ensemble de cardinal m (pour une certaine
relation d’ordre). Il a une complexité de O (m). Il s’applique en particulier à la recherche
j m k de la médiane d’un
ensemble fini (ou de l’élément qui en est le plus proche par excès), en prenant n = + 1 , ce que l’on ne
2
savait faire jusqu’à 1969 qu’en un temps de l’ordre de m log m.
On applique ici aussi le principe ≪ Diviser Pour Régner ≫ .
m
– On découpe l’ensemble de m éléments en sous-ensembles, chacun de cardinal 6 k . k est une constant
k
à choisir convenablement, k > 3 .
m
– On cherche les médianes des sous-ensembles, et la médiane de ces médianes, soit x.
k

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 19/22 4 décembre 2017


Récursivité PSI* 17-18

– On sépare l’ensemble initial en 2 sous-ensembles : celui des j éléments inférieurs à x et celui des m − j
éléments supérieurs à x, les éléments égaux à x étant mis alternativement d’un côté ou de l’autre.
Si j est supérieur à n, on est ramené à chercher le n e élément du 1er sous-ensemble ; s’il est inférieur à
n, on cherche le (n − j) e élément du second.
Lorsque la taille d’un ensemble est inférieure à k , on calculera directement la médiane ou le n e élément par une
procédure auxiliaire qui utilise la méthode sort de Python.
Complexité :
Notons T (m) le temps nécessaire pour trouver le n e élément d’une liste de cardinal m. On considèrera que si
m 6 k , T (m) = a = cste).
On :
T (m) = αm (temps de sépartion ne m/k sous-ensembles)
m
+ T (k) (médianes de chacun de ces sous-ensembles)
k 
m
+T (temps pour la médiane des médianes)
k
+ βm (temps de séparation en deux sous-ensembles)

+ T min(j, m − j)
 m m m
Puisque T (k) = a, que T min(j, m − j) 6 T et que T 6T car k > 3 on obtient :
2 k 3
 a   m   m 
T (m) 6 α + β + m+T +T .
k 2 3
Montrons alors par récurrence qu’il existe une constante C telle que T (m) 6 Cm pour tout m.
– Initialisation : on peut toujours trouver c telle que T (m) 6 cm pour les premières valeurs de m, et toute
valeur C > c convient aussi.
– Ensuite :
 a m m
T (m) 6 α + β + m+C +C
 k k  2
a C C
6 α+β+ + + m
k k 2
 
a 5C
6 α+β+ + m
k 6
 a
et il suffit donc de prendre C > 6 α + β + pour obtenir T (m) 6 Cm.
k
La complexité de l’algorithme est donc bien un O (m).
Comme nous n’aurons certainement pas le temps de faire tous les programmes de ce chapitre, je vous donne la
solution en Python ; j’y ai inclus un graphique pour comparer le temps d’exécution avec celui obtenu à l’aide d’un
simple programme de tri (qui est en O (m log m) au minimum, comme on le verra dans un prochain chapitre).

import random
from time import clock
import matplotlib.pyplot as plt

def nieme_element1(liste,n):
# n-ième élément d'une liste en utilisant le tri
liste.sort()
return liste[n-1]

def nieme_element(liste, n):


k = 5
m = len(liste)
if m <= k:
return nieme_element1(liste, n)
else:
liste_medianes = []
q, r = divmod(m, k)
for i in range(0, q, k):
li = liste[i:i+k]

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 20/22 4 décembre 2017


Récursivité PSI* 17-18

liste_medianes.append(nieme_element1(li, k//2 + 1))


if r != 0:
li = liste[q*k:]
liste_medianes.append(nieme_element1(li, r//2 + 1))
x = nieme_element(liste_medianes, len(liste_medianes)//2 + 1)
liste1 = []
liste2 = []
alternative = False
for i in range(0,m):
elt = liste[i]
if elt < x:
liste1.append(elt)
elif elt > x:
liste2.append(elt)
else:
if alternative:
liste1.append(elt)
else:
liste2.append(elt)
alternative = not alternative
j = len(liste1)
if j >= n:
return nieme_element(liste1, n)
else:
return nieme_element(liste2, n-j)

nb_elements = 10000000
nmax = 10000000
liste_complete = [random.randint(0, nmax) for i in range(nb_elements)]
N = []
T1 = []
T2 = []
for n in range(100000, nb_elements, 200000):
# print(n, sep=' ') juste pour suivre la progression
liste = liste_complete[0:n]
debut = clock()
x = nieme_element(liste, n//2 + 1)
T1.append(clock() - debut)
debut = clock()
y = nieme_element1(liste, n//2 + 1)
T2.append(clock() - debut)
if x != y:
print('oups! programme faux!')
N.append(n)

plt.plot(N, T1, color='blue', label='Algorithme récursif')


plt.plot(N, T2, color='red', linestyle='--', label='Avec tri')
plt.legend(loc = 'upper left')
plt.xlabel('Taille de la liste')
plt.ylabel('Temps')

plt.show()

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 21/22 4 décembre 2017


Récursivité PSI* 17-18

25
Algorithme récursif
Avec tri
20

15
Temps

10

0
0.0 0.5 1.0 1.5 2.0 2.5
Taille de la liste 1e7

⋆ ⋆ ⋆ ⋆
⋆ ⋆ ⋆
⋆ ⋆

Informatique PSI* – © T.LEGAY – Lycée d’Arsonval 22/22 4 décembre 2017

Vous aimerez peut-être aussi