Vous êtes sur la page 1sur 44

Algorithmique & Programmation

Cours 1 : 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.

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) >>> fac_iterative(10)
return res 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

Récursivité
Cependant, si on ne 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: En pratique, l’appel fac_recursive(10) entraîne
return 1 l’appel fac_recursive(9), qui entraîne lui-même
return(n*fac_recursive(n-1)) 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é
Qu’est-ce qui se passe?

Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
Récursivité
La récursivité plus en 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-
Exemples : mêmes".
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.
Récursivité
def matches(joueurs):
""" Fonction ré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

Récursivité
Méthode structurelle et principe 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.

>>> fac_recursive_erreur(10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
nombre maximal
File "E:/Exemples/Factorielle.py", line 21, in
d’appels récursifs (de fac_recursive_erreur
l’ordre de 1000) return(n*fac_recursive_erreur(n-1))
...
RuntimeError: maximum recursion depth exceeded
Récursivité
Suites numériques et comparaison 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

Récursivité
Relation de récurrence

Dans une 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.  u0  1


On définit de manière récursive la suite : 
un  2.un 1
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)

Récursivité
Il est aussi possible 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

Récursivité
Deuxième exemple La suite de Fibonacci.

 F0  0

Cette suite est définie de la façon suivante :  F1  1
F  F  F
 n2 n 1 n

On se propose de définir une


def FibR(n):
procédure récursive FibR, qui à partir
if n<=1:
d’un seul argument d’entrée (un
return n
entier n) retourne la valeur de Fn en
sortie : return FibR(n-1)+FibR(n-2)

Récursivité
On propose maintenant une version itérative, que l’on nommera FibI :  F0  0

 F1  1
def FibI(n):
F  F  F
if n==0:  n2 n 1 n

return 0
a=0; b=1 #Premiers termes Que peut-on conclure si on compare ces deux
for k in range(1,n): fonctions ? On voit que si on demande un n
a,b=b,a+b très grand, le nombre d’appels de FibR devient
return b 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)

Récursivité
Analyse des programmes ré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
u0  2 def u(n):
 if n==0:
 1 3 
un  2  (un 1 )  u 
return 2.
  n 1 
else:
x=u(n-1)
return 0.5*(x+3./x)

Récursivité
def u(n):
if n==0:
return 2. Si n désigne la valeur de son argument, on
else: note C(n) ce nombre d’opérations. En suivant
x=u(n-1) la définition de la fonction u, on obtient les
return 0.5*(x+3./x) 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 .

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)

Récursivité
Récursivité imbriquée et croisé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

Récursivité
Récursivité croisée ou mutuelle
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)
Récursivité
Récursivité terminale et non 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))

Récursivité
Une fonction récursive est 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 Cette fois-ci, les calculs se font à la descente,
return facT(n-1,n*acc) comme c’est illustré ci-dessous, pour facT(3):

facT(3,1)

facT(2,3)

facT(1,6)

facT(0,6)

Récursivité
Exemple de la suite de Syracuse
La suite de Syracuse d’un nombre entier N est définie par récurrence de la façon
suivante :
n=3
while n!=1:
print(n)
if n%2==0:
def syracuse(n): n=n//2
if n == 1: else:
print 1 # on affiche 1 et on ne fait rien d ’ autre n=3*n+1
else :
print n print(1)
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)

Récursivité

Vous aimerez peut-être aussi