Récursivité en Python : cours et exercices corrigés de prépa
Fonctions récursives : cas de base et appel récursif, pile d'appels et arbre des appels, terminaison, récursivité et itération, exemples classiques (factorielle, puissance rapide, dichotomie, Hanoï).
- 21méthodes
- 37exercices corrigés
- 4problèmes
- 20pièges
- 47figures
Une fonction récursive est une fonction qui s'appelle elle-même sur une donnée plus petite, jusqu'à un cas de base résolu sans appel. Elle sert à traduire directement une définition par récurrence et à écrire les algorithmes qui coupent en deux : dichotomie, puissance rapide, tours de Hanoï, parcours de structures imbriquées. Le chapitre est au programme d'informatique de première année en MPSI, PCSI, PTSI, TSI et TPC, au Maroc et en France ; les sujets du CNC, de CCINP, de Mines-Ponts et de X-ENS posent des questions de récursivité, reprises dans les exercices. Tu y trouves la récursivité en Python écrite en trois temps, la pile d'appels, la terminaison et la complexité d'une fonction récursive.
Ce que tu sauras faire
Une fonction récursive résout un problème en s'appelant elle-même sur un problème plus petit. Cette façon de penser simplifie tous les problèmes qui contiennent des copies plus petites d'eux-mêmes : une palmeraie divisée en parcelles, une recherche dans une liste triée, un arbre, une figure qui se répète. À la fin de ce chapitre, tu dois savoir :
- écrire une fonction récursive en trois temps : la variable sur laquelle on récurse, le cas de base, le cas récursif ;
- suivre l'exécution d'un appel récursif : pile d'appels, arbre des appels, ordre des affichages ;
- justifier qu'une fonction récursive s'arrête, et prouver par récurrence qu'elle renvoie le bon résultat ;
- reconnaître une récursivité simple, double ou mutuelle ;
- écrire une fonction récursive sur une liste, une chaîne ou une structure imbriquée, sans coût caché ;
- écrire la recherche dichotomique et la puissance rapide en récursif, et compter leurs appels ;
- compter les appels et la profondeur d'une fonction récursive, connaître la limite de la pile de Python, et choisir entre récursif et itératif.
L'essentiel : Écrire une fonction récursive Extrait gratuit
- La variable sur laquelle on récurse, qui diminue d'un appel à l'autre.
- Le cas de base : la plus petite valeur, résolue sans appel.
- Le cas récursif : on suppose les appels plus petits justes, et l'on en déduit la réponse.
Pour une fonction qui renvoie une valeur, le résultat de l'appel se renvoie : return n * fact(n - 1), jamais l'appel seul. Une variable locale est recréée à chaque appel.
def f(n):
if n == 0: # cas de base
return ...
return ... f(n - 1) # cas récursif
Une méthode du chapitre Extrait gratuit
L'énoncé demande une fonction « récursive », ou le problème contient une copie plus petite de lui-même : une parcelle formée de parcelles, une liste privée de son premier élément, un entier divisé par 10.
Une fonction récursive a un ou plusieurs cas de base, résolus sans appel, et un cas récursif, qui se ramène à des données plus petites par des appels à elle-même. Dans le cas récursif, on suppose que les appels plus petits renvoient le bon résultat.
- Choisir la variable sur laquelle on récurse et dire en quoi elle diminue d'un appel à l'autre.
- Écrire le cas de base : la plus petite valeur, et sa réponse.
- Écrire le cas récursif : « si je connais la réponse pour la donnée plus petite, comment en déduire la réponse ? »
- Vérifier à la main le cas de base, puis la valeur juste au-dessus.
def f(n):
if n == 0: # cas de base
return ...
return ... f(n - 1) ... # cas récursif
« On récurse sur . Cas de base : pour , [réponse]. Cas récursif : pour , [relation], et l'appel f(n - 1) porte sur un entier plus petit. »
Calculer le produit , pour entier naturel, par des additions : et .
def produit(a, b):
# a : nombre ; b : entier naturel
# renvoie a * b, calculé par additions
if b == 0: # cas de base
return 0
return a + produit(a, b - 1) # cas récursif
print(produit(4, 3), produit(7, 0))
Le programme affiche :
12 0
On récurse sur : chaque appel retire 1 à , et le cas de base est atteint après appels.
- Commencer par le cas récursif et oublier le cas de base. Les correcteurs du concours Mines-Ponts (rapport 2020, informatique commune) écrivent, pour une fonction récursive à écrire : « Le cas de base a souvent été oublié ou mal traité. »
- Faire porter l'appel sur la même donnée (
produit(a, b)dans son propre corps) : la fonction s'appelle sans fin. - Écrire une boucle dans la fonction alors que l'énoncé demande la récursion.
Exercices 1, 3, 9, 10, 18 et 26, problèmes 1 et 4.
Un exercice corrigé Extrait gratuit
Pour chacune des quatre fonctions suivantes, dire sur quelle variable porte la récursion, écrire le cas de base et le cas récursif, puis écrire la fonction en Python.
somme_entiers(n)renvoie pour un entier naturel .somme_carres_rec(n)renvoie . Que vautsomme_carres_rec(5)?compte_zeros(n)renvoie le nombre de chiffres 0 dans l'écriture décimale de l'entier naturel . Par exemple,compte_zeros(102030)vaut 3 etcompte_zeros(0)vaut 1.symetrique(L, i, j)renvoieTruesi la portionL[i], ..., L[j]d'une liste se lit de la même façon dans les deux sens (comme un palindrome),Falsesinon, sans construire de tranche ; on l'appelle avec etlen(L) - 1. Tester sur[1, 2, 3, 2, 1],[4, 7, 7, 4]et[1, 2].
- Méthode 1 : la variable de la récursion est l'entier ; on suppose le problème résolu pour un entier plus petit.
- Méthode 2 : et .
- Question 3 : le dernier chiffre de est
n % 10, et les autres chiffres forment l'entiern // 10. - Question 4, méthode 12 : la variable de la récursion est la longueur de la portion ; on compare les deux bouts, puis on rapproche et .
Analyse. On remplit pour chaque fonction les trois temps de la méthode 1.
| fonction | variable | cas de base | cas récursif |
|---|---|---|---|
somme_entiers |
: 0 | + somme_entiers(n - 1) |
|
somme_carres_rec |
: 0 | + somme_carres_rec(n - 1) |
|
compte_zeros |
: 1 si , 0 sinon | (1 si n % 10 vaut 0) + compte_zeros(n // 10) |
|
symetrique |
: True |
L[i] égal à L[j], et symetrique(L, i + 1, j - 1) |
Pour compte_zeros, le cas de base est un nombre à un seul chiffre ; on ne peut pas s'arrêter à , sinon compte_zeros(5) appellerait compte_zeros(0) et compterait un zéro qui n'est pas écrit.
def somme_entiers(n):
# n : entier naturel ; renvoie 0 + 1 + ... + n
if n == 0: # cas de base
return 0
return n + somme_entiers(n - 1)
def somme_carres_rec(n):
# n : entier naturel ; renvoie 0² + 1² + ... + n²
if n == 0:
return 0
return n * n + somme_carres_rec(n - 1)
def compte_zeros(n):
# n : entier naturel ; renvoie le nombre de
# chiffres 0 dans l'écriture décimale de n
if n < 10: # un seul chiffre
if n == 0:
return 1
return 0
if n % 10 == 0:
return 1 + compte_zeros(n // 10)
return compte_zeros(n // 10)
def symetrique(L, i, j):
# L : liste ; i, j : indices
# renvoie True si L[i], ..., L[j] se lit de la même
# façon dans les deux sens (True si i >= j)
if i >= j: # zéro ou un élément
return True
if L[i] != L[j]:
return False
return symetrique(L, i + 1, j - 1)
print(somme_entiers(100), somme_carres_rec(5))
print(compte_zeros(102030), compte_zeros(7))
print(compte_zeros(0))
for L in [[1, 2, 3, 2, 1], [4, 7, 7, 4], [1, 2]]:
print(symetrique(L, 0, len(L) - 1))
Le programme affiche :
5050 55
3 0
1
True
True
False
Chaque fonction s'arrête : dans les deux premières, est un entier naturel qui diminue de 1 à chaque appel jusqu'à 0 ; dans la troisième, pour , on a n // 10 , et l'entier atteint un nombre à un seul chiffre ; dans la quatrième, la longueur diminue de 2 à chaque appel, jusqu'à 0 ou 1 (cas de base , qui couvre les longueurs paire et impaire).
Interprétation : et ; les deux premières fonctions retrouvent les formules connues. compte_zeros(102030) fait 6 appels, un par chiffre.
Avant d'écrire, on remplit les trois temps : variable, cas de base, cas récursif. Le cas de base se choisit sur la plus petite entrée qui a un sens (un nombre à un chiffre, une portion de liste d'au plus un élément). Sur une liste, deux indices qui se rapprochent évitent toute copie. Voir les méthodes 1, 2 et 12.
Le cours détaillé, toute la fiche « L'essentiel », 21 méthodes, 20 pièges corrigés, la carte mentale, 37 exercices corrigés, 4 problèmes de concours, les fiches à mémoriser et l'assistant qui répond à tes questions. Ce chapitre est offert : il s’ouvre en entier avec un compte d’essai gratuit.
Ouvrir l'essai gratuitUne question ? WhatsAppPlan du cours
- 1. Fonctions récursives
- 1.1 Un problème qui contient des copies plus petites de lui-même
- 1.2 Définition
- 1.3 Écrire une fonction récursive en trois temps
- 1.4 Le résultat de l'appel se renvoie
- 2. L'exécution d'un appel récursif
- 2.1 Chaque appel a ses propres variables
- 2.2 Descente et remontée : la pile d'appels
- 2.3 L'arbre des appels
- 2.4 Afficher avant ou après l'appel
- 3. Un appel récursif doit s'arrêter
- 3.1 La récursion infinie
- 3.2 La règle d'arrêt
- 3.3 Protéger les entrées
- 4. Prouver qu'une fonction récursive est juste
- 4.1 Correction par récurrence
- 4.2 Récurrence forte
- 5. Simple, double, mutuelle
- 5.1 Récursivité simple
- 5.2 Récursivité double
- 5.3 Récursivité mutuelle
- 5.4 Deux remarques de vocabulaire
- 6. Récursivité sur les listes, les chaînes et les structures imbriquées
- 6.1 Avec un indice
- 6.2 Avec une tranche : un coût caché
- 6.3 Récursivité sur les chaînes
- 6.4 Structures imbriquées
- 7. Couper en deux
- 7.1 La recherche dichotomique
- 7.2 La puissance rapide
- 7.3 Diviser pour régner
- 8. Coût et limites d'une fonction récursive
- 8.1 Nombre d'appels et profondeur
- 8.2 Relations de récurrence
- 8.3 Garder les résultats déjà calculés
- 8.4 La limite de la pile de Python
- 8.5 Récursif ou itératif
- 9. Problèmes naturellement récursifs
- 9.1 Les tours de Hanoï
- 9.2 Les fractales
- 9.3 Énumérer les sous-listes et les permutations
- 10. Compléments du programme marocain
- 10.1 Des bornes par défaut
- 10.2 Compter les appels avec une variable globale
- 10.3 Listes imbriquées quelconques
Les méthodes du chapitre
Chaque méthode est une fiche : quand l'utiliser, le rappel de cours, les étapes, la rédaction attendue sur la copie, un exemple traité, les erreurs à éviter.
- Écrire une fonction récursive en trois temps
- Traduire une définition par récurrence en fonction récursive
- Choisir et vérifier les cas de base
- Construire le résultat avec la valeur renvoyée par l'appel
- Respecter une signature imposée avec une fonction auxiliaire
- Prévoir le résultat d'un appel : descente, puis remontée
- Dresser le tableau des états de la pile d'appels
- Dessiner l'arbre des appels et compter les appels
- Prévoir l'ordre des affichages
- Justifier la terminaison
- Prouver la correction par récurrence
- Récurser sur une liste ou une chaîne : indice ou tranche
- Parcourir une structure imbriquée
- Écrire une dichotomie récursive à deux bornes
- Écrire la puissance rapide et compter ses multiplications
- Compter les appels et les opérations d'une fonction récursive
- Éviter la limite de la pile
- Passer de l'itératif au récursif, et inversement
- Énumérer toutes les sous-listes ou toutes les permutations
- Fixer des bornes par défaut et compter les appels avec une variable globale
- Parcourir une liste imbriquée quelconque avec
isinstance
Les exercices corrigés
Chaque exercice a ses indications, sa correction rédigée et ce qu'il faut en retenir.
Application directe (15)
- Quatre fonctions en trois temps
- Avant ou après l'appel
- Un sablier d'étoiles
- La pile de somme_chiffres(483)
- Une fonction mystère
- Le cas de base manquant
- Le résultat perdu
- Deux suites définies l'une par l'autre
- Compléter par des zéros
- Une chaîne qui représente un entier
- Puissance naïve, puissance rapide
- Une dichotomie à la main
- Les sous-listes à k éléments
- Une racine par dichotomie
- Compter les appels avec global
Entraînement (16)
- Le coût caché des tranches
- Le maximum d'une liste
- Horner, de la boucle à la récursion
- La courbe et le flocon de Koch
- Supprimer les doublons
- Somme d'une séquence imbriquée
- Les feuilles d'un arbre de suffixes
- Une dichotomie à signature imposée
- Vigenère en récursif
- PGCD et coefficients de Bézout
- L'exponentiation rapide d'un sujet
- Composer un polynôme avec lui-même
- Insérer dans une liste triée
- Ramasser des graines
- Les permutations par insertion
- Aplatir une liste imbriquée
Approfondissement (6)
- Le parenthésage d'une chaîne de matrices
- La plus grande région d'une couleur
- Deux fonctions qui coupent en deux
- La fonction 91
- Payer une somme avec des pièces de 1, 2 et 5
- Une liste imbriquée pondérée par la profondeur
Les problèmes de concours
Des extraits de sujets réels, corrigés en entier, avec la durée conseillée et les méthodes mobilisées.
- Un arbre généalogique codé par la liste des pères
- La plus longue sous-séquence commune
- Le chargement optimal par récurrence
- Placer n reines par retour arrière
Questions fréquentes
Comment écrire une fonction récursive en Python ?
En trois temps. On choisit la variable sur laquelle on récurse, qui diminue d'un appel à l'autre. On écrit le cas de base, la plus petite valeur, résolue sans appel. On écrit le cas récursif en supposant les appels plus petits justes. Pour une fonction qui renvoie une valeur, on renvoie le résultat de l'appel : return n * fact(n - 1), jamais l'appel seul.
Qu'est-ce que la pile d'appels en récursivité ?
Chaque appel a ses propres variables locales, et l'appelant attend la valeur renvoyée. Les appels en attente sont empilés : le dernier commencé est le premier terminé. L'exécution descend jusqu'au cas de base, puis les valeurs remontent. Un print placé avant l'appel récursif s'exécute à la descente ; placé après, il s'exécute à la remontée, dans l'ordre inverse.
Que veut dire RecursionError: maximum recursion depth exceeded ?
Python a dépassé le nombre d'appels en attente qu'il accepte : sys.getrecursionlimit() vaut 1000. Cela arrive quand le cas de base n'est jamais atteint, parce qu'il manque ou que la variable ne diminue pas. Si la fonction est juste mais demande de l'ordre de 1000 appels en attente ou plus, on écrit une boucle à la place ; une copie de concours n'emploie pas sys.setrecursionlimit.
Comment prouver qu'une fonction récursive termine ?
On exhibe un variant : pour toute entrée permise, chaque appel porte sur une quantité entière, positive ou nulle, strictement plus petite, et les cas de base couvrent les plus petites valeurs. La correction se prouve ensuite par récurrence sur cette quantité : l'initialisation correspond au cas de base, l'hérédité au cas récursif. On utilise une récurrence forte quand l'appel porte sur ou sur une sous-structure.
Quelle est la complexité d'une fonction récursive ?
On compte le nombre d'appels, qui mesure le travail, et la profondeur, qui mesure la place dans la pile. donne un coût linéaire, comme fact ; un coût logarithmique, comme la puissance rapide ou la dichotomie ; un coût exponentiel, comme Hanoï. fib(30) fait 2 692 537 appels, mais au plus 30 en attente.
Quelle différence entre récursivité simple, double et mutuelle ?
Une récursivité simple fait au plus un appel récursif par exécution, comme fact ou la puissance rapide. Une récursivité double, ou multiple, fait deux appels ou plus, comme fib ou hanoi. Une récursivité mutuelle, ou croisée, met en jeu deux fonctions qui s'appellent l'une l'autre, comme est_pair et est_impair.
Comment y accéder
Ce chapitre fait partie des chapitres offerts : il s'ouvre en entier avec un compte d'essai gratuit. Créer un compte d'essai ou se connecter. Pour l'accès complet, les tarifs ou une question, écris-nous sur WhatsApp.