4SI
Objectifs
À la fin de ce chapitre, l’élève doit être capable de :
- reconnaître un module récursif et y identifier le cas d’arrêt et l’appel récursif ;
- passer d’une formulation itérative à une formulation récursive, et inversement ;
- exécuter manuellement une fonction récursive et en déduire son rôle ;
- écrire une fonction récursive portant sur un entier, une chaîne de caractères ou un tableau ;
- traduire une fonction récursive en Python.
Plan du chapitre
- Introduction
- Notion de récursivité
- Exécution d’une fonction récursive
- Du récursif à l’itératif
- Rôle d’une fonction récursive
- Les erreurs les plus fréquentes
- Retenons
1. Introduction
Activité 1
On veut calculer la puissance xⁿ d’un entier x, pour un entier n positif ou nul.
Questions
- Écrire l’algorithme d’une fonction
Puissance(x, n)qui calcule xⁿ à l’aide d’une boucle. - Compléter les égalités ci-dessous en faisant apparaître, dans chaque ligne, le résultat de la ligne précédente.
- Exprimer xⁿ en fonction de xⁿ⁻¹. Pour quelle valeur de
nle résultat est-il connu sans aucun calcul ?
5² = 5 * 5 = 25
5³ = 5 * 5 * 5 = 5 * … = 125
5⁴ = 5 * 5 * 5 * 5 = 5 * … = 625
5⁵ = 5 * 5 * 5 * 5 * 5 = 5 * … = 3125
Réponse
1. On multiplie n fois la valeur x : le nombre de répétitions est connu, la boucle est une boucle Pour.
Fonction Puissance (x, n : Entier) : Entier
DEBUT
P ← 1
Pour i de 1 à n Faire
P ← P * x
Fin Pour
Retourner P
FIN
TDOL
| Objet | Type / Nature |
|---|---|
P, i | Entier |
2. Chaque puissance contient la précédente :
5² = 5 * 5 = 25
5³ = 5 * (5 * 5) = 5 * 5² = 125
5⁴ = 5 * (5 * 5 * 5) = 5 * 5³ = 625
5⁵ = 5 * (5 * 5 * 5 * 5) = 5 * 5⁴ = 3125
3. Pour tout n ≥ 1, xⁿ = x * xⁿ⁻¹. Le résultat est connu sans calcul pour n = 0 : x⁰ = 1.
La fonction Puissance peut donc s’écrire sans boucle : pour calculer Puissance(x, n), il suffit de calculer Puissance(x, n - 1) et de multiplier le résultat par x, sauf lorsque n = 0, où le résultat vaut 1.
Fonction Puissance (x, n : Entier) : Entier
DEBUT
Si n = 0 Alors
Retourner 1
Sinon
Retourner x * Puissance(x, n - 1)
FinSi
FIN
La fonction Puissance s’appelle elle-même, sur un exposant plus petit. On dit qu’elle est récursive.
En détail
Les deux versions calculent le même résultat. La première répète une multiplication à l’aide d’une boucle : c’est un traitement itératif. La seconde obtient la répétition en s’appelant elle-même : c’est un traitement récursif.
2. Notion de récursivité
2.1 Définition
Définition. Un module est récursif lorsqu’il s’appelle lui-même dans son propre corps. Chaque appel porte sur des données plus simples que celles de l’appel qui le précède.
Un module récursif comporte toujours au moins :
- un cas d’arrêt, dans lequel le résultat est obtenu directement, sans nouvel appel ;
- un appel récursif, qui ramène le problème au même problème sur des données plus simples, plus proches du cas d’arrêt.
Dans la fonction Puissance, le cas d’arrêt est n = 0, et l’appel récursif est Puissance(x, n - 1).
Forme générale d’une fonction récursive
Fonction Nom (paramètres) : Type_résultat
DEBUT
Si Condition_arrêt Alors
Retourner Valeur_de_base
Sinon
Retourner Nom(paramètres_modifiés)
FinSi
FIN
Remarque. Cette forme est la plus simple. Une fonction récursive peut avoir plusieurs cas d’arrêt et plusieurs appels récursifs, placés dans les branches d’un
Si … Sinon Si … Sinon: c’est le cas dePGCD,PalindromeetDichotomique, plus loin. L’appel récursif peut aussi faire partie d’un calcul, commex * Puissance(x, n - 1).
Remarque importante. Dans une fonction récursive, l’instruction
Retournerfigure dans chacune des branches duSi. C’est la seule situation où une fonction contient plusieursRetourner. L’instructionRetournerne se place jamais à l’intérieur d’une boucle.
2.2 Trois questions pour écrire une fonction récursive
Pour écrire une fonction récursive, on répond à trois questions.
| Question | Pour Puissance(x, n) |
|---|---|
| Quelle donnée évolue d’un appel à l’autre, vers le cas d’arrêt ? | l’exposant n, qui diminue de 1 |
| Quel est le cas d’arrêt, et que vaut le résultat ? | n = 0 : le résultat vaut 1 |
Comment passe-t-on du cas n - 1 au cas n ? | on multiplie Puissance(x, n - 1) par x |
Les réponses donnent directement l’algorithme : la deuxième fournit la branche Alors, la troisième la branche Sinon. La première garantit que les appels successifs finissent par atteindre le cas d’arrêt.
La donnée qui évolue ne diminue pas toujours : elle peut aussi augmenter, comme un indice qui avance d’une case à chaque appel jusqu’à la fin d’une chaîne. Ce qui compte, c’est qu’à chaque appel elle se rapproche du cas d’arrêt.
2.3 Itératif et récursif
| Version itérative | Version récursive | |
|---|---|---|
| Répétition | assurée par une boucle | assurée par les appels successifs |
| Arrêt | condition de la boucle | cas d’arrêt |
| Résultat | construit dans une variable, P | construit par les valeurs retournées |
| Variables locales | P et i | aucune |
Remarque importante. La version récursive de
Puissancen’a ni variable locale ni module appelé autre qu’elle-même : conformément aux conventions, elle n’a pas de TDOL. Le module qui s’appelle lui-même ne figure pas dans son propre TDOL.
En Python
def Puissance(x, n):
if n == 0:
return 1
else:
return x * Puissance(x, n - 1)
3. Exécution d’une fonction récursive
3.1 La pile d’exécution
Lorsqu’un module en appelle un autre, l’ordinateur suspend le module appelant, exécute le module appelé, puis reprend le module appelant là où il s’était arrêté. Pour pouvoir reprendre, il mémorise, pour chaque appel en cours, les valeurs de ses paramètres, de ses variables locales et l’endroit où reprendre.
Définition. La pile d’exécution est la zone de la mémoire où sont rangés les appels en cours. Le dernier appel rangé est le premier terminé : on dit que la pile fonctionne selon le principe dernier entré, premier sorti.
Dans un module récursif, chaque appel ajoute un étage à la pile. Les appels s’empilent jusqu’au cas d’arrêt ; puis les appels se terminent l’un après l’autre, du dernier au premier, et chacun rend sa valeur à celui qui l’a appelé.
3.2 Exécution manuelle de l’appel Puissance(2, 4)
L’exécution se fait en deux temps.
La descente. Tant que le cas d’arrêt n’est pas atteint, chaque appel lance l’appel suivant et reste en attente de sa valeur.
La remontée. L’appel Puissance(2, 0) retourne 1 sans nouvel appel. Chaque appel en attente reçoit alors la valeur de l’appel suivant, effectue sa multiplication et retourne son propre résultat.
| Appel | n = 0 ? | Instruction exécutée | Valeur retournée |
|---|---|---|---|
Puissance(2, 4) | Faux | Retourner 2 * Puissance(2, 3) | 16 |
Puissance(2, 3) | Faux | Retourner 2 * Puissance(2, 2) | 8 |
Puissance(2, 2) | Faux | Retourner 2 * Puissance(2, 1) | 4 |
Puissance(2, 1) | Faux | Retourner 2 * Puissance(2, 0) | 2 |
Puissance(2, 0) | Vrai | Retourner 1 | 1 |
Remarque importante. Les trois premières colonnes se remplissent de haut en bas, pendant la descente. La dernière se remplit de bas en haut, pendant la remontée : aucune valeur n’est connue avant que le cas d’arrêt soit atteint.
| ↑ | Puissance(2, 0) = 1 | 1 ↓ |
| ↑ | Puissance(2, 1) = 2 * Puissance(2, 0) | 2 ↓ |
| ↑ | Puissance(2, 2) = 2 * Puissance(2, 1) | 4 ↓ |
| ↑ | Puissance(2, 3) = 2 * Puissance(2, 2) | 8 ↓ |
| ↑ | Puissance(2, 4) = 2 * Puissance(2, 3) | 16 |
| appel = en attente | valeur |
Le schéma représente la pile au moment où elle est la plus haute. L’empilement, flèche de gauche, se lit de bas en haut : chaque appel pose un nouvel étage au-dessus du précédent. Le dépilement, flèche de droite, se lit de haut en bas : l’étage du sommet se termine le premier, retourne sa valeur à l’étage du dessous, et disparaît.
On peut aussi écrire le calcul sous forme développée :
Puissance(2, 4) = 2 * Puissance(2, 3)
= 2 * 2 * Puissance(2, 2)
= 2 * 2 * 2 * Puissance(2, 1)
= 2 * 2 * 2 * 2 * Puissance(2, 0)
= 2 * 2 * 2 * 2 * 1
= 16
L’appel Puissance(2, 4) provoque 5 appels en tout : un pour chaque valeur de n, de 4 à 0. Au moment où Puissance(2, 0) s’exécute, les cinq appels sont présents en même temps dans la pile.
En détail
Pour un exposant n, la fonction provoque n + 1 appels et effectue n multiplications, autant que la version itérative. Mais chaque appel occupe un étage de la pile jusqu’à la fin de la remontée : la version récursive consomme davantage de mémoire.
3.3 La terminaison
Un module récursif ne se termine que si ses appels atteignent le cas d’arrêt. Deux erreurs l’en empêchent :
- l’absence de cas d’arrêt : chaque appel lance un nouvel appel, sans fin ;
- un appel qui ne rapproche pas du cas d’arrêt.
Exemple. L’appel Puissance(2, -1) provoque les appels Puissance(2, -2), Puissance(2, -3), … : l’exposant s’éloigne de 0 et le cas d’arrêt n’est jamais atteint. Chaque appel ajoute un étage à la pile, dont la taille est limitée : l’exécution s’interrompt par un débordement de pile.
En Python, l’exécution s’interrompt après un millier d’appels environ, avec le message :
RecursionError: maximum recursion depth exceeded
Remarque importante. La fonction
Puissancen’est correcte que pourn ≥ 0. Toute fonction récursive suppose une condition sur ses paramètres, qui garantit que le cas d’arrêt sera atteint. C’est au module appelant de la respecter, par exemple par une saisie contrôlée.
Application 1 — Factorielle et PGCD
On considère les trois fonctions itératives suivantes, vues en 3ᵉ année.
Fonction Factorielle (n : Entier) : Entier
DEBUT
F ← 1
Pour i de 2 à n Faire
F ← F * i
Fin Pour
Retourner F
FIN
Fonction PGCD (a, b : Entier) : Entier
DEBUT
Tant que a ≠ b Faire
Si a > b Alors
a ← a - b
Sinon
b ← b - a
FinSi
Fin Tant que
Retourner a
FIN
Fonction PGCD_Euclide (a, b : Entier) : Entier
DEBUT
Tant que b ≠ 0 Faire
r ← a Mod b
a ← b
b ← r
Fin Tant que
Retourner a
FIN
La factorielle d’un entier n, notée n!, est le produit des entiers de 1 à n, avec 0! = 1. La fonction PGCD calcule le PGCD de deux entiers par la méthode des différences, la fonction PGCD_Euclide par la méthode d’Euclide.
Travail demandé
- Exprimer
n!en fonction de(n - 1)!. Pour quelle valeur denle résultat est-il connu sans calcul ? - En déduire l’algorithme de la fonction récursive
Factorielle(n). - Donner l’exécution manuelle de l’appel
Factorielle(4): la suite des appels et les valeurs retournées. - Écrire l’algorithme de la fonction récursive
PGCD(a, b), selon la méthode des différences. - Écrire l’algorithme de la fonction récursive
PGCD_Euclide(a, b), selon la méthode d’Euclide. - Donner l’exécution manuelle des appels
PGCD(84, 36)etPGCD_Euclide(84, 36). Comparer le nombre d’appels. - Donner l’exécution manuelle de l’appel
PGCD_Euclide(18, 48). Que fait le premier appel ?
N.B. L’entier n est positif ou nul. Pour PGCD, les entiers a et b sont strictement positifs. Pour PGCD_Euclide, ils sont positifs ou nuls, et ne sont pas nuls tous les deux.
Voir le corrigé
1. Relation de récurrence
4! = 4 * 3 * 2 * 1 = 4 * (3 * 2 * 1) = 4 * 3!
De même, pour tout n ≥ 1 : n! = n * (n - 1)!. Le résultat est connu sans calcul pour n = 0 : 0! = 1.
| Question | Pour Factorielle(n) |
|---|---|
| Quelle donnée évolue d’un appel à l’autre, vers le cas d’arrêt ? | l’entier n, qui diminue de 1 |
| Quel est le cas d’arrêt, et que vaut le résultat ? | n = 0 : le résultat vaut 1 |
Comment passe-t-on du cas n - 1 au cas n ? | on multiplie Factorielle(n - 1) par n |
2. Factorielle
Fonction Factorielle (n : Entier) : Entier
DEBUT
Si n = 0 Alors
Retourner 1
Sinon
Retourner n * Factorielle(n - 1)
FinSi
FIN
Les variables F et i de la version itérative ont disparu.
En détail
Le cas d’arrêt n ≤ 1 est également correct, puisque 1! = 0! = 1 : il économise un appel. La forme n = 0 est retenue ici parce qu’elle reprend exactement la définition, 0! = 1.
En Python
def Factorielle(n):
if n == 0:
return 1
else:
return n * Factorielle(n - 1)
3. Exécution manuelle de Factorielle(4)
| Appel | n = 0 ? | Instruction exécutée | Valeur retournée |
|---|---|---|---|
Factorielle(4) | Faux | Retourner 4 * Factorielle(3) | 24 |
Factorielle(3) | Faux | Retourner 3 * Factorielle(2) | 6 |
Factorielle(2) | Faux | Retourner 2 * Factorielle(1) | 2 |
Factorielle(1) | Faux | Retourner 1 * Factorielle(0) | 1 |
Factorielle(0) | Vrai | Retourner 1 | 1 |
| ↑ | Factorielle(0) = 1 | 1 ↓ |
| ↑ | Factorielle(1) = 1 * Factorielle(0) | 1 ↓ |
| ↑ | Factorielle(2) = 2 * Factorielle(1) | 2 ↓ |
| ↑ | Factorielle(3) = 3 * Factorielle(2) | 6 ↓ |
| ↑ | Factorielle(4) = 4 * Factorielle(3) | 24 |
| appel = en attente | valeur |
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
L’appel Factorielle(4) provoque 5 appels et retourne 24.
4. PGCD, méthode des différences
À chaque tour, la boucle remplace le plus grand des deux nombres par leur différence, et s’arrête lorsque les deux nombres sont égaux. La version récursive exprime la même chose : si a = b, le PGCD vaut a ; sinon, il est égal au PGCD du plus petit nombre et de la différence.
| Question | Pour PGCD(a, b) |
|---|---|
| Quelle donnée évolue d’un appel à l’autre, vers le cas d’arrêt ? | le plus grand des deux nombres, remplacé par la différence |
| Quel est le cas d’arrêt, et que vaut le résultat ? | a = b : le résultat vaut a |
| Comment passe-t-on d’un appel au suivant ? | PGCD(a - b, b) si a > b, PGCD(a, b - a) sinon |
Fonction PGCD (a, b : Entier) : Entier
DEBUT
Si a = b Alors
Retourner a
Sinon Si a > b Alors
Retourner PGCD(a - b, b)
Sinon
Retourner PGCD(a, b - a)
FinSi
FIN
La fonction comporte un cas d’arrêt et deux appels récursifs, placés dans deux branches différentes : à chaque exécution, un seul des deux appels est effectué.
Remarque importante. Si l’un des deux nombres est nul, la différence ne le modifie jamais :
PGCD(5, 0)appellePGCD(5, 0)indéfiniment, jusqu’au débordement de pile. C’est pourquoi la méthode des différences exigeaetbstrictement positifs.
En Python
def PGCD(a, b):
if a == b:
return a
elif a > b:
return PGCD(a - b, b)
else:
return PGCD(a, b - a)
5. PGCD_Euclide
La boucle remplace, à chaque tour, le couple (a, b) par le couple (b, a Mod b), et s’arrête lorsque b = 0 ; le résultat est alors a.
| Question | Pour PGCD_Euclide(a, b) |
|---|---|
| Quelle donnée évolue d’un appel à l’autre, vers le cas d’arrêt ? | l’entier b, remplacé par a Mod b, qui est plus petit que b |
| Quel est le cas d’arrêt, et que vaut le résultat ? | b = 0 : le résultat vaut a |
| Comment passe-t-on d’un appel au suivant ? | PGCD_Euclide(a, b) = PGCD_Euclide(b, a Mod b) |
Fonction PGCD_Euclide (a, b : Entier) : Entier
DEBUT
Si b = 0 Alors
Retourner a
Sinon
Retourner PGCD_Euclide(b, a Mod b)
FinSi
FIN
La variable r de la version itérative a disparu, puisque a Mod b est calculé directement dans l’appel.
En détail
Dans les deux fonctions PGCD, contrairement à Puissance et Factorielle, l’appel récursif n’est suivi d’aucun calcul : la valeur retournée par le dernier appel remonte telle quelle jusqu’au premier.
En Python
def PGCD_Euclide(a, b):
if b == 0:
return a
else:
return PGCD_Euclide(b, a % b)
6. Exécution manuelle de PGCD(84, 36) et de PGCD_Euclide(84, 36)
| Appel | a = b ? | Instruction exécutée | Valeur retournée |
|---|---|---|---|
PGCD(84, 36) | Faux | 84 > 36 : Retourner PGCD(48, 36) | 12 |
PGCD(48, 36) | Faux | 48 > 36 : Retourner PGCD(12, 36) | 12 |
PGCD(12, 36) | Faux | 12 < 36 : Retourner PGCD(12, 24) | 12 |
PGCD(12, 24) | Faux | 12 < 24 : Retourner PGCD(12, 12) | 12 |
PGCD(12, 12) | Vrai | Retourner 12 | 12 |
| Appel | b = 0 ? | a Mod b | Instruction exécutée | Valeur retournée |
|---|---|---|---|---|
PGCD_Euclide(84, 36) | Faux | 12 | Retourner PGCD_Euclide(36, 12) | 12 |
PGCD_Euclide(36, 12) | Faux | 0 | Retourner PGCD_Euclide(12, 0) | 12 |
PGCD_Euclide(12, 0) | Vrai | Retourner 12 | 12 |
Les deux fonctions retournent 12. La méthode des différences demande 5 appels, la méthode d’Euclide 3 : une seule division remplace plusieurs soustractions successives du même nombre, ici 84 - 36 - 36 = 12 = 84 Mod 36.
7. Exécution manuelle de PGCD_Euclide(18, 48)
| Appel | b = 0 ? | a Mod b | Instruction exécutée | Valeur retournée |
|---|---|---|---|---|
PGCD_Euclide(18, 48) | Faux | 18 | Retourner PGCD_Euclide(48, 18) | 6 |
PGCD_Euclide(48, 18) | Faux | 12 | Retourner PGCD_Euclide(18, 12) | 6 |
PGCD_Euclide(18, 12) | Faux | 6 | Retourner PGCD_Euclide(12, 6) | 6 |
PGCD_Euclide(12, 6) | Faux | 0 | Retourner PGCD_Euclide(6, 0) | 6 |
PGCD_Euclide(6, 0) | Vrai | Retourner 6 | 6 |
PGCD_Euclide(18, 48) retourne 6, après 5 appels. Lorsque a < b, on a a Mod b = a : le premier appel échange simplement les deux nombres. Il est donc inutile de tester lequel des deux est le plus grand avant d’appeler la fonction.
Application 2 — Palindrome
Un palindrome est un mot qui se lit de la même façon de gauche à droite et de droite à gauche, comme "RADAR" ou "ELLE". La chaîne inversée d’une chaîne ch est la chaîne ch écrite de droite à gauche : l’inverse de "BAC" est "CAB".
Travail demandé
- Pour quelles chaînes sait-on, sans aucune comparaison, qu’elles sont des palindromes ? Pour une chaîne plus longue, à quelle condition est-elle un palindrome ?
- En déduire l’algorithme de la fonction récursive
Palindrome(ch)qui vérifie si la chaînechest un palindrome. - Donner l’exécution manuelle des appels
Palindrome("RADAR")etPalindrome("RESTER"). - Écrire l’algorithme de la fonction récursive
Inverser(ch)qui retourne la chaîne inversée dech. - Donner l’exécution manuelle de l’appel
Inverser("BAC").
N.B. Les chaînes sont écrites en majuscules.
Voir le corrigé
1. Relation récursive
Une chaîne vide ou d’un seul caractère est un palindrome. Une chaîne plus longue est un palindrome si ses deux caractères extrêmes sont égaux et si la chaîne privée de ces deux caractères est elle-même un palindrome.
| Question | Pour Palindrome(ch) |
|---|---|
| Quelle donnée évolue d’un appel à l’autre, vers le cas d’arrêt ? | la longueur de ch, qui perd deux caractères |
| Quel est le cas d’arrêt, et que vaut le résultat ? | Long(ch) < 2 : le résultat vaut Vrai |
| Comment passe-t-on d’un appel au suivant ? | si les extrêmes sont différents : Faux ; sinon, on vérifie la chaîne privée de ses extrêmes |
2. Palindrome
Fonction Palindrome (ch : Chaîne de caractères) : Booléen
DEBUT
Si Long(ch) < 2 Alors
Retourner Vrai
Sinon Si ch[0] ≠ ch[Long(ch) - 1] Alors
Retourner Faux
Sinon
Retourner Palindrome(Sous_chaine(ch, 1, Long(ch) - 1))
FinSi
FIN
La fonction comporte deux cas d’arrêt : la chaîne est trop courte, et le résultat vaut Vrai ; les extrêmes sont différents, et le résultat vaut Faux sans aller plus loin.
Remarque importante. La borne de fin de
Sous_chaineest exclue :Sous_chaine(ch, 1, Long(ch) - 1)retire le premier et le dernier caractère. Avec la borneLong(ch), seul le premier serait retiré, et la chaîne ne diminuerait que d’un caractère à chaque appel.
En détail
Le cas d’arrêt s’écrit Long(ch) < 2, et non Long(ch) = 1 : une chaîne de longueur paire, comme "ELLE", perd deux caractères à chaque appel et aboutit à la chaîne vide, jamais à une chaîne d’un caractère.
En Python
def Palindrome(ch):
if len(ch) < 2:
return True
elif ch[0] != ch[len(ch) - 1]:
return False
else:
return Palindrome(ch[1:len(ch) - 1])
3. Exécution manuelle de Palindrome(« RADAR ») et de Palindrome(« RESTER »)
| Appel | Long(ch) < 2 ? | Extrêmes | Instruction exécutée | Valeur retournée |
|---|---|---|---|---|
Palindrome("RADAR") | Faux | R, R | Retourner Palindrome("ADA") | Vrai |
Palindrome("ADA") | Faux | A, A | Retourner Palindrome("D") | Vrai |
Palindrome("D") | Vrai | Retourner Vrai | Vrai |
| Appel | Long(ch) < 2 ? | Extrêmes | Instruction exécutée | Valeur retournée |
|---|---|---|---|---|
Palindrome("RESTER") | Faux | R, R | Retourner Palindrome("ESTE") | Faux |
Palindrome("ESTE") | Faux | E, E | Retourner Palindrome("ST") | Faux |
Palindrome("ST") | Faux | S, T | Retourner Faux | Faux |
"RADAR" est un palindrome : les appels atteignent la chaîne "D". "RESTER" n’en est pas un : le troisième appel s’arrête sur les extrêmes différents S et T, sans nouvel appel.
4. Inverser
La chaîne inversée commence par le dernier caractère de ch, suivi de l’inverse du reste de la chaîne.
Inverser("BAC") = "C" + Inverser("BA")
| Question | Pour Inverser(ch) |
|---|---|
| Quelle donnée évolue d’un appel à l’autre, vers le cas d’arrêt ? | la longueur de ch, qui perd son dernier caractère |
| Quel est le cas d’arrêt, et que vaut le résultat ? | ch = "" : le résultat vaut "" |
| Comment passe-t-on d’un appel au suivant ? | on place le dernier caractère devant l’inverse du reste |
Fonction Inverser (ch : Chaîne de caractères) : Chaîne de caractères
DEBUT
Si ch = "" Alors
Retourner ""
Sinon
Retourner ch[Long(ch) - 1] + Inverser(Sous_chaine(ch, 0, Long(ch) - 1))
FinSi
FIN
En détail
On peut aussi retirer le premier caractère et le placer à la fin : Retourner Inverser(Sous_chaine(ch, 1, Long(ch))) + ch[0]. Les deux formes sont correctes ; l’ordre de la concaténation est alors inversé.
En Python
def Inverser(ch):
if ch == "":
return ""
else:
return ch[len(ch) - 1] + Inverser(ch[0:len(ch) - 1])
5. Exécution manuelle de Inverser(« BAC »)
| Appel | ch = "" ? | Instruction exécutée | Valeur retournée |
|---|---|---|---|
Inverser("BAC") | Faux | Retourner "C" + Inverser("BA") | "CAB" |
Inverser("BA") | Faux | Retourner "A" + Inverser("B") | "AB" |
Inverser("B") | Faux | Retourner "B" + Inverser("") | "B" |
Inverser("") | Vrai | Retourner "" | "" |
| ↑ | Inverser("") = "" | "" ↓ |
| ↑ | Inverser("B") = "B" + Inverser("") | "B" ↓ |
| ↑ | Inverser("BA") = "A" + Inverser("B") | "AB" ↓ |
| ↑ | Inverser("BAC") = "C" + Inverser("BA") | "CAB" |
| appel = en attente | valeur |
L’appel Inverser("BAC") provoque 4 appels, un de plus que le nombre de caractères, et retourne **"CAB"**. Les concaténations se font pendant la remontée.
Application 3 — Notes d’un devoir
Les notes, entières, obtenues par les élèves d’un groupe à un devoir de l’année 2027 sont rangées dans un tableau T de n entiers, déclaré ainsi :
| Nouveau type |
|---|
Tab = Tableau de 30 Entiers |
Exemple : n = 6.
| T | 12 | 15 | 9 | 15 | 18 | 11 |
| 0 | 1 | 2 | 3 | 4 | 5 |
Travail demandé
- Écrire l’algorithme de la fonction récursive
Somme(T, n)qui retourne la somme desnpremiers éléments deT. - Donner l’exécution manuelle de l’appel
Somme(T, 6)sur l’exemple. - Écrire l’algorithme de la fonction récursive
Maximum(T, n)qui retourne la plus grande note du tableau, puis donner l’exécution manuelle de l’appelMaximum(T, 6). - Écrire l’algorithme de la fonction récursive
Recherche_Seq(T, n, x)qui vérifie si la notexfigure parmi lesnpremiers éléments deT. - Écrire l’algorithme de la fonction récursive
Occurrences(T, n, x)qui retourne le nombre d’élèves ayant obtenu la notex. - Le tableau est maintenant trié par ordre croissant. Écrire l’algorithme de la fonction récursive
Dichotomique(T, d, f, x)qui vérifie, par une recherche dichotomique, si la notexfigure entre les indicesdetfdu tableauT. Donner l’exécution manuelle de la recherche de 15, puis de celle de 16.
N.B. Le tableau T est déjà rempli, et n ≥ 1.
Voir le corrigé
L’idée commune. Dans la version itérative, une variable i parcourt le tableau. Dans la version récursive, il n’y a plus de variable de parcours : c’est le paramètre n, le nombre d’éléments considérés, qui diminue d’un appel à l’autre. Traiter les n premiers éléments, c’est traiter le dernier d’entre eux, T[n - 1], puis les n - 1 premiers.
Remarque importante. Le tableau
Tne change jamais : il est transmis tel quel à chaque appel. Seulndiminue. On ne « raccourcit » pas un tableau comme on raccourcit une chaîne.
1. Somme
| Question | Pour Somme(T, n) |
|---|---|
| Quelle donnée évolue d’un appel à l’autre, vers le cas d’arrêt ? | le nombre d’éléments n, qui diminue de 1 |
| Quel est le cas d’arrêt, et que vaut le résultat ? | n = 0 : aucun élément, la somme vaut 0 |
Comment passe-t-on du cas n - 1 au cas n ? | on ajoute T[n - 1] à Somme(T, n - 1) |
Fonction Somme (T : Tab, n : Entier) : Entier
DEBUT
Si n = 0 Alors
Retourner 0
Sinon
Retourner T[n - 1] + Somme(T, n - 1)
FinSi
FIN
En Python
def Somme(T, n):
if n == 0:
return 0
else:
return T[n - 1] + Somme(T, n - 1)
2. Exécution manuelle de Somme(T, 6)
| Appel | n = 0 ? | Instruction exécutée | Valeur retournée |
|---|---|---|---|
Somme(T, 6) | Faux | Retourner 11 + Somme(T, 5) | 80 |
Somme(T, 5) | Faux | Retourner 18 + Somme(T, 4) | 69 |
Somme(T, 4) | Faux | Retourner 15 + Somme(T, 3) | 51 |
Somme(T, 3) | Faux | Retourner 9 + Somme(T, 2) | 36 |
Somme(T, 2) | Faux | Retourner 15 + Somme(T, 1) | 27 |
Somme(T, 1) | Faux | Retourner 12 + Somme(T, 0) | 12 |
Somme(T, 0) | Vrai | Retourner 0 | 0 |
| ↑ | Somme(T, 0) = 0 | 0 ↓ |
| ↑ | Somme(T, 1) = 12 + Somme(T, 0) | 12 ↓ |
| ↑ | Somme(T, 2) = 15 + Somme(T, 1) | 27 ↓ |
| ↑ | Somme(T, 3) = 9 + Somme(T, 2) | 36 ↓ |
| ↑ | Somme(T, 4) = 15 + Somme(T, 3) | 51 ↓ |
| ↑ | Somme(T, 5) = 18 + Somme(T, 4) | 69 ↓ |
| ↑ | Somme(T, 6) = 11 + Somme(T, 5) | 80 |
| appel = en attente | valeur |
L’appel Somme(T, 6) provoque 7 appels et retourne 80. Les éléments sont ajoutés pendant la remontée, du premier, T[0] = 12, au dernier, T[5] = 11.
3. Maximum
Le cas d’arrêt n’est pas le tableau vide, qui n’a pas de maximum, mais le tableau d’un élément : son maximum est T[0].
| Question | Pour Maximum(T, n) |
|---|---|
| Quelle donnée évolue d’un appel à l’autre, vers le cas d’arrêt ? | le nombre d’éléments n, qui diminue de 1 |
| Quel est le cas d’arrêt, et que vaut le résultat ? | n = 1 : le résultat vaut T[0] |
Comment passe-t-on du cas n - 1 au cas n ? | on compare T[n - 1] au maximum des n - 1 premiers, et l’on garde le plus grand |
Fonction Maximum (T : Tab, n : Entier) : Entier
DEBUT
Si n = 1 Alors
Retourner T[0]
Sinon
m ← Maximum(T, n - 1)
Si T[n - 1] > m Alors
Retourner T[n - 1]
Sinon
Retourner m
FinSi
FinSi
FIN
TDOL
| Objet | Type / Nature |
|---|---|
m | Entier |
La variable locale m conserve la valeur retournée par l’appel récursif, qui sert deux fois : dans la comparaison, puis éventuellement comme résultat.
Exécution manuelle de Maximum(T, 6). La comparaison se fait pendant la remontée, lorsque m est connu.
| Appel | n = 1 ? | m reçu | T[n - 1] | Valeur retournée |
|---|---|---|---|---|
Maximum(T, 6) | Faux | 18 | 11 | 18 |
Maximum(T, 5) | Faux | 15 | 18 | 18 |
Maximum(T, 4) | Faux | 15 | 15 | 15 |
Maximum(T, 3) | Faux | 15 | 9 | 15 |
Maximum(T, 2) | Faux | 12 | 15 | 15 |
Maximum(T, 1) | Vrai | 12 |
L’appel Maximum(T, 6) provoque 6 appels et retourne 18.
Remarque importante. Il ne faut pas écrire
Si T[n - 1] > Maximum(T, n - 1) Alors … Sinon Retourner Maximum(T, n - 1): chaque appel en lancerait alors deux au lieu d’un. Le résultat reste juste, mais sur l’exemple le nombre d’appels passe de 6 à 25, et sur un tableau de 6 notes rangées par ordre décroissant, il atteint 63.
En Python
def Maximum(T, n):
if n == 1:
return T[0]
else:
m = Maximum(T, n - 1)
if T[n - 1] > m:
return T[n - 1]
else:
return m
4. Recherche_Seq
La recherche examine d’abord le dernier élément considéré, T[n - 1]. Elle comporte deux cas d’arrêt : plus aucun élément à examiner, ou l’élément trouvé.
Fonction Recherche_Seq (T : Tab, n, x : Entier) : Booléen
DEBUT
Si n = 0 Alors
Retourner Faux
Sinon Si T[n - 1] = x Alors
Retourner Vrai
Sinon
Retourner Recherche_Seq(T, n - 1, x)
FinSi
FIN
Sur l’exemple, Recherche_Seq(T, 6, 9) retourne Vrai après 4 appels, qui examinent 11, 18, 15, puis 9. Recherche_Seq(T, 6, 10) retourne Faux après 7 appels : le dernier, Recherche_Seq(T, 0, 10), n’a plus aucun élément à examiner.
En détail
Le parcours se fait de la fin vers le début du tableau, alors que la version itérative va du début vers la fin. Pour une recherche, l’ordre n’a pas d’importance : le résultat est le même.
En Python
def Recherche_Seq(T, n, x):
if n == 0:
return False
elif T[n - 1] == x:
return True
else:
return Recherche_Seq(T, n - 1, x)
5. Occurrences
Le nombre d’occurrences de x parmi les n premiers éléments est celui des n - 1 premiers, augmenté de 1 si T[n - 1] = x.
Fonction Occurrences (T : Tab, n, x : Entier) : Entier
DEBUT
Si n = 0 Alors
Retourner 0
Sinon Si T[n - 1] = x Alors
Retourner 1 + Occurrences(T, n - 1, x)
Sinon
Retourner Occurrences(T, n - 1, x)
FinSi
FIN
Sur l’exemple, Occurrences(T, 6, 15) retourne 2, après 7 appels : les deux 15, en T[3] et T[1], ajoutent chacun 1 pendant la remontée.
Remarque importante. Contrairement à
Recherche_Seq, la fonctionOccurrencesne s’arrête pas quand elle rencontrex: elle doit examiner tous les éléments. Son seul cas d’arrêt estn = 0.
En Python
def Occurrences(T, n, x):
if n == 0:
return 0
elif T[n - 1] == x:
return 1 + Occurrences(T, n - 1, x)
else:
return Occurrences(T, n - 1, x)
6. Dichotomique
Le tableau trié de l’exemple :
| T | 9 | 11 | 12 | 15 | 15 | 18 |
| 0 | 1 | 2 | 3 | 4 | 5 |
La zone de recherche est délimitée par les indices d et f, qui deviennent des paramètres : chaque appel porte sur une moitié de la zone précédente. La zone est vide lorsque d > f.
| Question | Pour Dichotomique(T, d, f, x) |
|---|---|
| Quelle donnée évolue d’un appel à l’autre, vers le cas d’arrêt ? | la taille de la zone, de d à f, divisée par deux |
| Quel est le cas d’arrêt, et que vaut le résultat ? | d > f : zone vide, Faux ; ou T[milieu] = x : Vrai |
| Comment passe-t-on d’un appel au suivant ? | on poursuit dans la moitié gauche ou dans la moitié droite |
Fonction Dichotomique (T : Tab, d, f, x : Entier) : Booléen
DEBUT
Si d > f Alors
Retourner Faux
Sinon
milieu ← (d + f) Div 2
Si T[milieu] = x Alors
Retourner Vrai
Sinon Si T[milieu] > x Alors
Retourner Dichotomique(T, d, milieu - 1, x)
Sinon
Retourner Dichotomique(T, milieu + 1, f, x)
FinSi
FinSi
FIN
TDOL
| Objet | Type / Nature |
|---|---|
milieu | Entier |
Pour chercher dans tout le tableau, le premier appel est Dichotomique(T, 0, n - 1, x).
Recherche de 15
| Appel | d > f ? | milieu | T[milieu] | Décision | Valeur retournée |
|---|---|---|---|---|---|
Dichotomique(T, 0, 5, 15) | Faux | 2 | 12 | 12 < 15 : moitié droite | Vrai |
Dichotomique(T, 3, 5, 15) | Faux | 4 | 15 | trouvé | Vrai |
Recherche de 16
| Appel | d > f ? | milieu | T[milieu] | Décision | Valeur retournée |
|---|---|---|---|---|---|
Dichotomique(T, 0, 5, 16) | Faux | 2 | 12 | 12 < 16 : moitié droite | Faux |
Dichotomique(T, 3, 5, 16) | Faux | 4 | 15 | 15 < 16 : moitié droite | Faux |
Dichotomique(T, 5, 5, 16) | Faux | 5 | 18 | 18 > 16 : moitié gauche | Faux |
Dichotomique(T, 5, 4, 16) | Vrai | zone vide | Faux |
La recherche de 15 aboutit en 2 appels, celle de 16 échoue après 4 appels.
En détail
La version itérative utilise un drapeau Trouve et une boucle Tant que. La version récursive n’en a pas besoin : les deux conditions d’arrêt de la boucle, d > f et T[milieu] = x, sont devenues les deux cas d’arrêt de la fonction.
En Python
def Dichotomique(T, d, f, x):
if d > f:
return False
else:
milieu = (d + f) // 2
if T[milieu] == x:
return True
elif T[milieu] > x:
return Dichotomique(T, d, milieu - 1, x)
else:
return Dichotomique(T, milieu + 1, f, x)
4. Du récursif à l’itératif
4.1 Principe
Au Bac, on demande aussi le passage inverse : écrire la version itérative d’une fonction récursive donnée.
Il n’existe pas de règle unique : la méthode dépend de ce que fait la fonction après son appel récursif.
- Si l’appel est suivi d’un calcul, comme dans
Puissance,SommeouInverser, la boucle refait la remontée, dans le même ordre : c’est l’exemple 4.2. - Si l’appel n’est suivi d’aucun calcul, comme dans
PGCDouRecherche_Seq, les paramètres deviennent les variables de la boucle, qui continue tant qu’aucun cas d’arrêt n’est atteint : c’est l’exemple 4.3.
4.2 Exemple : Inverser
On part de la fonction récursive de l’application résolue 2.
Fonction Inverser (ch : Chaîne de caractères) : Chaîne de caractères
DEBUT
Si ch = "" Alors
Retourner ""
Sinon
Retourner ch[Long(ch) - 1] + Inverser(Sous_chaine(ch, 0, Long(ch) - 1))
FinSi
FIN
Pendant la remontée de l’appel Inverser("BAC"), les valeurs retournées sont successivement "", "B", "AB", puis "CAB" : à chaque étape, le caractère suivant de ch, dans l’ordre de lecture, est placé devant le résultat précédent.
- Le cas d’arrêt retourne
"": la variable résultatchinvest initialisée à"". - Chaque étape de la remontée place un caractère devant le résultat : l’instruction répétée est
chinv ← ch[i] + chinv. - Il y a autant d’étapes que de caractères, nombre connu d’avance : la boucle est une boucle
Pour.
Fonction Inverser (ch : Chaîne de caractères) : Chaîne de caractères
DEBUT
chinv ← ""
Pour i de 0 à Long(ch) - 1 Faire
chinv ← ch[i] + chinv
Fin Pour
Retourner chinv
FIN
TDOL
| Objet | Type / Nature |
|---|---|
chinv | Chaîne de caractères |
i | Entier |
i | ch[i] | chinv après l’instruction | Valeur de la remontée |
|---|---|---|---|
| avant la boucle | "" | Inverser("") | |
| 0 | B | "B" | Inverser("B") |
| 1 | A | "AB" | Inverser("BA") |
| 2 | C | "CAB" | Inverser("BAC") |
La boucle refait exactement la remontée : après chaque tour, chinv contient la valeur retournée par l’appel correspondant.
4.3 Exemple : Recherche_Seq
On part de la fonction récursive de l’application résolue 3.
Fonction Recherche_Seq (T : Tab, n, x : Entier) : Booléen
DEBUT
Si n = 0 Alors
Retourner Faux
Sinon Si T[n - 1] = x Alors
Retourner Vrai
Sinon
Retourner Recherche_Seq(T, n - 1, x)
FinSi
FIN
La fonction a deux cas d’arrêt, et le second peut survenir avant la fin du tableau : le nombre d’appels n’est pas connu d’avance, la boucle est une boucle Tant que. On continue tant qu’aucun des deux cas d’arrêt n’est atteint, c’est-à-dire tant que n > 0 et T[n - 1] ≠ x.
Fonction Recherche_Seq (T : Tab, n, x : Entier) : Booléen
DEBUT
Tant que (n > 0) Et (T[n - 1] ≠ x) Faire
n ← n - 1
Fin Tant que
Retourner n > 0
FIN
À la sortie de la boucle, n > 0 signifie que la boucle s’est arrêtée sur T[n - 1] = x : l’élément est trouvé. n = 0 signifie que tous les éléments ont été examinés sans succès.
Remarque importante. La condition de la boucle
Tant queest la négation de la disjonction des cas d’arrêt : on s’arrête sin = 0ouT[n - 1] = x, donc on continue sin > 0etT[n - 1] ≠ x.
En détail
Le paramètre n est passé par valeur : le décrémenter dans la boucle ne modifie pas la variable du programme appelant. Sur l’exemple 12 15 9 15 18 11, la recherche de 9 s’arrête avec n = 3, et celle de 10 avec n = 0.
5. Rôle d’une fonction récursive
5.1 Démarche
Pour déterminer le rôle d’une fonction récursive donnée, on l’exécute manuellement sur une valeur particulière, puis on généralise le résultat observé. La démarche comporte trois étapes.
- Exécuter manuellement la fonction sur la valeur donnée, sous forme de tableau : une ligne par appel, les valeurs retournées remplies de bas en haut.
- Observer ce que fait chaque appel : quelle partie de la donnée il traite, et ce qu’il ajoute au résultat de l’appel suivant.
- Généraliser, puis vérifier le rôle trouvé sur une seconde valeur choisie soi-même.
Remarque importante. Le rôle s’exprime par ce que la fonction calcule, pas par la façon dont elle le calcule. « Elle retourne la somme des chiffres de
n» est un rôle ; « elle ajouten Mod 10à l’appel suivant » n’en est pas un.
5.2 Exemple
Fonction Inconnue (n : Entier) : Entier
DEBUT
Si n < 10 Alors
Retourner n
Sinon
Retourner n Mod 10 + Inconnue(n Div 10)
FinSi
FIN
Travail demandé : donner l’exécution manuelle de l’appel Inconnue(2027), puis en déduire le rôle de la fonction Inconnue, sachant que n est un entier positif.
Exécution manuelle
| Appel | n < 10 ? | n Mod 10 | n Div 10 | Instruction exécutée | Valeur retournée |
|---|---|---|---|---|---|
Inconnue(2027) | Faux | 7 | 202 | Retourner 7 + Inconnue(202) | 11 |
Inconnue(202) | Faux | 2 | 20 | Retourner 2 + Inconnue(20) | 4 |
Inconnue(20) | Faux | 0 | 2 | Retourner 0 + Inconnue(2) | 2 |
Inconnue(2) | Vrai | Retourner 2 | 2 |
Observer. n Mod 10 est le chiffre des unités de n, et n Div 10 est le nombre n privé de ce chiffre. Chaque appel détache le dernier chiffre et l’ajoute au résultat de l’appel suivant ; le dernier appel retourne le chiffre qui reste. Le résultat est 7 + 2 + 0 + 2 = 11.
Généraliser et vérifier. La fonction Inconnue retourne la somme des chiffres de n. Vérification sur n = 48 : Inconnue(48) = 8 + Inconnue(4) = 8 + 4 = 12, et 4 + 8 = 12.
6. Les erreurs les plus fréquentes
Les erreurs suivantes sont relevées sur les fonctions du cours. Chaque conséquence a été obtenue en exécutant la fonction erronée.
| Instruction erronée | Instruction correcte | Conséquence |
|---|---|---|
Factorielle sans le Si : Retourner n * Factorielle(n - 1) seul | Si n = 0 Alors Retourner 1 Sinon … | aucun cas d’arrêt : débordement de pile |
Retourner n * Factorielle(n) | Retourner n * Factorielle(n - 1) | le paramètre ne change pas : débordement de pile |
Somme : Sinon T[n - 1] + Somme(T, n - 1) | Sinon Retourner T[n - 1] + Somme(T, n - 1) | la branche Sinon ne retourne rien : la fonction n’a pas de résultat |
Palindrome : Sous_chaine(ch, 1, Long(ch)) | Sous_chaine(ch, 1, Long(ch) - 1) | seul le premier caractère est retiré : "RADAR" et "ELLE" sont déclarés non palindromes |
Palindrome : Si Long(ch) = 1 Alors | Si Long(ch) < 2 Alors | "ELLE" aboutit à la chaîne vide, et ch[0] n’existe pas |
Dichotomique : Dichotomique(T, d, milieu, x) | Dichotomique(T, d, milieu - 1, x) | lorsque d = f, la zone ne diminue plus : la recherche de 16 provoque un débordement de pile |
Maximum avec deux appels : Si T[n - 1] > Maximum(T, n - 1) Alors … Sinon Retourner Maximum(T, n - 1) | un seul appel, rangé dans m | résultat juste, mais 25 appels au lieu de 6 sur l’exemple, 63 sur un tableau décroissant |
Remarque importante. Les deux premières erreurs, et celle de
Dichotomique, ont la même cause : le cas d’arrêt n’est jamais atteint. Devant toute fonction récursive, on vérifie que chaque appel rapproche la donnée du cas d’arrêt.
7. Retenons
Récursivité. Un module est récursif lorsqu’il s’appelle lui-même dans son propre corps, sur des données plus simples.
Deux parties. Un module récursif comporte au moins un cas d’arrêt, résolu sans appel, et un appel récursif qui rapproche la donnée du cas d’arrêt. Sans cela, l’exécution s’interrompt par un débordement de pile.
Trois questions. Quelle donnée évolue d’un appel à l’autre, vers le cas d’arrêt ? Quel est le cas d’arrêt, et que vaut le résultat ? Comment se ramène-t-on à l’appel suivant ?
Exécution. Les appels s’empilent jusqu’au cas d’arrêt, puis se dépilent en retournant leurs valeurs, du dernier appel au premier. Dans une trace, la colonne des valeurs retournées se remplit de bas en haut.
Teste tes connaissances
Un quiz corrigé question par question, puis des flashcards pour mémoriser les notions du chapitre.

