Cas de base
Situation suffisamment simple pour être résolue directement, sans nouvel appel récursif.
Comprendre comment une fonction peut s’appeler elle-même, suivre la pile des appels et savoir écrire, expliquer et corriger un algorithme récursif.
Résoudre un problème en le ramenant à une version plus petite de lui-même
Une fonction est récursive lorsqu’elle s’appelle elle-même, directement ou indirectement. Chaque appel travaille avec ses propres paramètres et variables locales. Pour qu’un algorithme récursif termine, il doit comporter un cas de base et les appels successifs doivent se rapprocher de ce cas.
Situation suffisamment simple pour être résolue directement, sans nouvel appel récursif.
La fonction transforme le problème et s’appelle sur une instance plus petite qui doit progresser vers le cas de base.
n! = n × (n−1) × … × 1 et 0! = 1
La définition mathématique est naturellement récursive : 0! = 1 et, pour n ≥ 1, n! = n × (n−1)!.
def factorielle(n):
if n == 0:
return 1
return n * factorielle(n - 1)
Pour factorielle(4), les appels descendent jusqu’au cas de base, puis les résultats remontent :
factorielle(4)
= 4 × factorielle(3)
= 4 × 3 × factorielle(2)
= 4 × 3 × 2 × factorielle(1)
= 4 × 3 × 2 × 1 × factorielle(0)
= 4 × 3 × 2 × 1 × 1
= 24
Chaque appel doit attendre le résultat du suivant
Lorsqu’une fonction appelle une autre fonction, le contexte de l’appel en cours est conservé dans la pile d’appels (call stack). Chaque appel récursif ajoute un cadre contenant notamment ses paramètres et variables locales. Quand un appel se termine, son cadre est dépilé.
RecursionError. Une solution récursive correcte mathématiquement n’est donc pas toujours la meilleure implémentation pratique.Un bon exemple pour comprendre récursivité et complexité
Avec la convention F₀ = 0, F₁ = 1 et Fₙ = Fₙ₋₁ + Fₙ₋₂, une traduction directe donne :
def fibonacci(n):
if n == 0:
return 0
if n == 1:
return 1
return fibonacci(n - 1) + fibonacci(n - 2)
Cette version est simple à comprendre mais recalcule plusieurs fois les mêmes valeurs. Par exemple fibonacci(5) calcule fibonacci(3) par plusieurs branches. Son nombre d’appels croît exponentiellement.
Très proche de la définition mathématique, mais beaucoup de sous-problèmes sont recalculés.
On mémorise les résultats déjà obtenus. Un sous-problème identique n’est alors pas recalculé, ce qui améliore fortement l’efficacité.
Deux manières différentes de décrire certains algorithmes
def factorielle_iterative(n):
resultat = 1
for k in range(2, n + 1):
resultat = resultat * k
return resultat
Les deux versions calculent la même fonction. Pour la factorielle, elles effectuent un nombre d’étapes proportionnel à n : O(n). La version récursive utilise toutefois une pile d’appels de profondeur O(n), alors que la version itérative peut fonctionner avec un espace supplémentaire constant.
Faire apparaître clairement cas de base et réduction du problème
FONCTION somme(n)
SI n = 0 ALORS
RENVOYER 0
SINON
RENVOYER n + somme(n - 1)
FIN SI
FIN FONCTION
Pour justifier la terminaison sur un entier naturel n, on remarque que chaque appel remplace n par n−1. La valeur diminue strictement et finit donc par atteindre 0, le cas de base.
Les appels continuent jusqu’à atteindre la limite de récursion.
f(n) qui rappelle f(n) avec le même argument ne se rapproche pas du cas d’arrêt.
Un appel récursif doit généralement renvoyer une valeur exploitable par l’appel précédent.
Une fonction prévue pour n ≥ 0 doit préciser ou contrôler cette précondition.
20 questions • score sur 20