Récursivité
Algorithmique & Programmation
Cours1 : Introduction à la récursivité
Présentation
La récursivité est un concept général qui peut être illustré dans
(quasiment) tous les langages de programmation, et qui peut être utile
dans de nombreuses situations.
La définition la plus simple d'une fonction récursive est la suivante :
C’est une fonction qui s'appelle elle-même.
Si dans le corps (le contenu) de la fonction, vous l'utilisez elle-même,
alors elle est récursive.
2.
Récursivité
Premier exemple :fonction factorielle
La factorielle de n notée n! est le produit de tous les entiers de 1 à n. Une
méthode vue en première année consiste à programmer cette fonction de
manière itérative. Voici ce que donne le programme écrit en langage Python :
def fac_iterative(n):
res=1
for k in range(n):
res=res*(k+1)
return res
>>> fac_iterative(10)
3628800
Pour montrer l'intérêt de la récursivité, nous allons maintenant coder cette
fonction en utilisant une propriété de la factorielle. On a effectivement :
n! = n.(n-1)! Cette propriété est très intéressante, car elle permet de calculer n! à
partir de (n-1)!. Or (n-1)! Peut être calculé de la même manière à partir de (n-2)!
Etc
3.
Récursivité
Cependant, si onne fait que répéter à l'infini cette méthode, le calcul ne donnera
jamais de résultat. Pour cela, il faut définir un cas pour lequel on obtient le résultat.
Dans notre exemple ce cas est : 1!=1
A partir de ce résultat, on peut calculer n! pour tout n≥1.
Ainsi, de manière
récursive :
def fac_recursive(n):
if n==0:
return 1
return(n*fac_recursive(n-1))
En pratique, l’appel fac_recursive(10) entraîne
l’appel fac_recursive(9), qui entraîne lui-même
l’appel fac_recursive(8) etc… et ainsi de suite,
jusqu’à l’appel fac_recursive(0) qui renvoie 1.
En conclusion de cette introduction, on dira qu’une
fonction f est récursive si son exécution peut
provoquer un ou plusieurs appels de f elle-même.
Récursivité
La récursivité plusen détails
Pourquoi écrire une fonction récursive ?
Un programmeur doit écrire une fonction
récursive quand c'est la solution la plus
adaptée à son problème. La question peut
donc se reformuler ainsi : à quels problèmes
les fonctions récursives sont-elles adaptées ?
Les fonctions récursives sont des fonctions qui
s'appellent elles-mêmes. Elles doivent donc
résoudre des problèmes qui "s'appellent eux-
mêmes".
Exemples :
On dispose d'une liste de joueurs d'un jeu à deux joueurs (échecs, ping-pong,
etc.), et je veux créer une liste de matches, de telle sorte que chaque joueur joue
contre tous les autres joueurs une seule fois (pas de phase retour).
On peut remarquer que si l’on a une liste de 4 joueurs, on peut résoudre le problème en
connaissant une liste de matchs pour les 3 premiers joueurs seulement : on prend cette
liste, et on y ajoute un match entre le quatrième joueur et chacun des trois autres. Ainsi,
on peut ramener le problème (obtenir une liste des matchs entre tous les joueurs) à un
sous-problème plus simple : obtenir une liste des matchs entre tous les joueurs, sauf un.
30.
Récursivité
def matches(joueurs):
""" Fonctionrécursive qui renvoie une liste de matches à partir d'une liste de joueurs
"""
#s'il n'y a qu'un seul joueur, on n'organise aucun match
if len(joueurs)==1:
return [ ]
#on enleve le dernier joueur de la liste, et on demande les matchs sans lui
dernier_joueur = joueurs.pop()
vs=matches(joueurs) #on rajoute un match entre lui et tous les autres joueurs
for j in joueurs:
vs.append([j,dernier_joueur]) #on le remet dans la liste des joueurs, et on
renvoie la liste des match
joueurs.append(dernier_joueur)
return vs
Comprendre à la
maison+ questions
31.
Récursivité
Méthode structurelle etprincipe fondamental
Une fois qu'on a repéré que le problème que l'on doit résoudre se prête bien à
l'utilisation d'une fonction récursive, il faut écrire la fonction.
D'abord, on gère le cas simple, c'est-à-dire celui qui ne nécessite pas de rappeler
récursivement la fonction. Pour la factorielle, c'est le cas où n vaut 1. Pour la liste
des joueurs, c'est le cas où il y a un seul joueur (car il n'y a aucun match à
organiser).
Ensuite, on gère le ou les sous-problèmes récursifs, en rappelant la fonction
récursive pour chaque sous-problème à résoudre.
On peut énoncer le principe fondamental suivant, concernant les fonctions récursives :
Comme une fonction récursive fait appel à elle-même, il est indispensable de s’assurer
que le nombre d’appels à cette fonction sera fini.
nombre maximal
d’appels récursifs (de
l’ordre de 1000)
>>> fac_recursive_erreur(10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "E:/Exemples/Factorielle.py", line 21, in
fac_recursive_erreur
return(n*fac_recursive_erreur(n-1))
. . .
RuntimeError: maximum recursion depth exceeded
32.
Récursivité
Suites numériques etcomparaison entre itératif et récursif
Définition explicite
Une suite numérique peut dans certains cas être définie de manière explicite : u0=f(n).
La détermination du nième
terme est alors aisée. Il suffit d’évaluer f(n).
Exemple 1 : Puissances de 2
Problème : évaluer le nombre 2 à la puissance n de manière explicite, n.
On définit de manière explicite la suite : un = 2n
par le
code Python suivant :
def Pow2_explicite(n):
return 2**n
33.
Récursivité
Relation de récurrence
Dansune suite définie de manière récurrente, il est possible de calculer le terme un
de la suite en connaissant les termes précédents.
Les égalités de la forme un = f(un-1), un = f(un-1, un-2), etc. s’appellent des relations de
récurrence. Les égalités qui définissent les premiers termes d’une suite sont
appelées des conditions de départ. Une suite récursive est donc définie par une
relation de récurrence et une(des) condition(s) de départ.
Premier exemple
On reprend l’Exemple 1 : puissances de 2.
On définit de manière récursive la suite :
0
1
1
2.
n n
u
u u
On demande de définir un algorithme récursif
avec le langage Python (Noté Pow2_recursive).
def Pow2_recursive(n):
if n==0:
return 1
return 2*Pow2_recursive(n-1)
34.
Récursivité
Il est aussipossible de proposer un algorithme itératif permettant d’aboutir au
même résultat…
def Pow2_iterative(n):
res=1
for k in range(n):
res=res*2
return res
35.
Récursivité
Deuxième exemple Lasuite de Fibonacci.
Cette suite est définie de la façon suivante :
0
1
2 1
0
1
n n n
F
F
F F F
On se propose de définir une
procédure récursive FibR, qui à partir
d’un seul argument d’entrée (un
entier n) retourne la valeur de Fn en
sortie :
def FibR(n):
if n<=1:
return n
return FibR(n-1)+FibR(n-2)
36.
Récursivité
On propose maintenantune version itérative, que l’on nommera FibI :
def FibI(n):
if n==0:
return 0
a=0; b=1 #Premiers termes
for k in range(1,n):
a,b=b,a+b
return b
0
1
2 1
0
1
n n n
F
F
F F F
Que peut-on conclure si on compare ces deux
fonctions ? On voit que si on demande un n
très grand, le nombre d’appels de FibR devient
vite très important
Comparaison des méthodes récursives et itératives
Fib(1) est calculé 5 fois
def FibR(n):
if n<=1:
return n
return FibR(n-1)+FibR(n-2)
37.
Récursivité
Analyse des programmesrécursifs
Lorsque nous aurons à écrire ou analyser des
programmes récursifs, nous serons amenés à
nous poser un certain nombre de questions : Le programme se termine-t-il ?
Est-il conforme à sa spécification ?
Quelle est sa complexité (temps et
mémoire) ?
Complexité d’une fonction récursive
Prenons par exemple la suite (un) qui permet de calculer une approximation de 3
0
1
1
2
1 3
( )
2
n n
n
u
u u
u
def u(n):
if n==0:
return 2.
else:
x=u(n-1)
return 0.5*(x+3./x)
38.
Récursivité
def u(n):
if n==0:
return2.
else:
x=u(n-1)
return 0.5*(x+3./x)
Si n désigne la valeur de son argument, on
note C(n) ce nombre d’opérations. En suivant la
définition de la fonction u, on obtient les deux
équations suivantes : C(0)=0
C(n)= C(n-1)+3
En effet, dans le cas n=0, on ne fait aucune opération
arithmétique. Et dans le cas n>0, on fait d’une part un appel
récursif sur la valeur n-1, d’où C(n-1) opérations, puis trois
opérations arithmétiques (une multiplication , une addition et
une division). Il s’agit d’une suite arithmétique de raison 3,
dont le terme général est :
C(n)=3n
Le nombre d’opérations arithmétiques effectuées par la fonction u est donc
proportionnel à n .
39.
Récursivité
Si en revanche,on avait écrit la fonction u de manière plus naïve, avec deux appels
récursifs u(n-1) :
def u(n):
if n==0:
return 2.
else:
return 0.5*(u(n-1)+3./u(n-1))
Alors les équations définissant C(n) seraient les suivantes :
C(0)=0
C(n)= C(n-1)+ C(n-1)+3
En effet, il convient de prendre en compte le coût C(n-1) des deux appels à u(n-1). Il s’agit
maintenant d’une suite arithmético-géométrique, dont le terme général est :
C(n)=3(2n
-1)
40.
Récursivité
Récursivité imbriquée etcroisée
Récursivité imbriquée
La récursivité imbriquée consiste à faire un appel récursif à l'intérieur d'un autre
appel récursif. Un exemple permettant de bien illustrer le concept est la suite
d’Ackermann, définie de la manière suivante sur
def Ackermann(m,n):
if m==0:
return n+1
elif n==0:
return Ackermann(m-1,1)
else:
return Ackermann(m-1, Ackermann(m,n-1))
Question : Que vaut Ackermann(2,2) ?
Réponse : 7
41.
Récursivité
Récursivité croisée oumutuelle
La possibilité de déclarer une fonction sans la définir prend tout son intérêt à propos de la
récursivité croisée. En effet, une fonction ne peut être utilisée qu’à condition qu’elle ait été
définie (ou déclarée) or, dans le cas de la récursivité croisée, on ne pourrait pas s’en sortir
juste à l’aide des définitions. La récursivité croisée consiste à écrire des fonctions qui
s’appellent l’une l’autre.
Prenons l’exemple simple suivant qui consiste à connaître la parité d’un entier naturel.
def estPair(n):
"""Cette fonction renvoie True si l'entier n est pair False sinon (on suppose
que n>=0)"""
if n==0:
return True
return estImpair(n-1)
def estImpair(n):
"""Cette fonction renvoie True si l'entier n est pair False sinon (on suppose
que n>=0)"""
if n==0:
return False
return estPair(n-1)
42.
Récursivité
Récursivité terminale etnon terminale
Une fonction récursive est dite non terminale si le résultat de l'appel récursif est utilisé
pour réaliser un traitement (en plus du retour d'une valeur).
Reprenons la forme récursive non
terminale de la fonction factorielle,
que l’on notera facNT.
def facNT(n):
if n==0:
return 1
return(n*facNT(n-1))
43.
Récursivité
Une fonction récursiveest dite terminale si aucun traitement n'est effectué à la remontée
d'un appel récursif (sauf le retour d'une valeur). Concrètement il n’y a pas de calcul entre
l’appel récursif et l’instruction return.
def facT(n,acc=1):
if n==0:
return acc
return facT(n-1,n*acc)
Cette fois-ci, les calculs se font à la descente,
comme c’est illustré ci-dessous, pour facT(3):
facT(0,6)
facT(2,3)
facT(1,6)
facT(3,1)
44.
Récursivité
Exemple de lasuite de Syracuse
La suite de Syracuse d’un nombre entier N est définie par récurrence de la façon
suivante :
def syracuse(n):
if n == 1:
print 1 # on affiche 1 et on ne fait rien d ’ autre
else :
print n
if n % 2 == 0:
syracuse(n/2)
else :
syracuse(3 n + 1)
∗
def syracuse_tail_recursive(n, t=1):
if n == 1:
return t - 1
return Syracuse(3*n+1 if n%2 else n/2, t+1)
n=3
while n!=1:
print(n)
if n%2==0:
n=n//2
else:
n=3*n+1
print(1)