On peut comparer cela à la [mise en abyme](https://fr.wikipedia.org/wiki/Mise_en_abyme), procédé littéraire qui décrit une œuvre incrustée dans elle-même.
Tout comme les boucles, il faut créer une instruction permettant d'arrêter l'exécution du code, en récursif nous l'appelons **le cas de base** (ou cas d'arrêt). Il permettra d'éviter les appels **infinis**.
Lorsqu'une fonction récursive s'exécute, Python utilise une structure de données appelée **pile d'appels** (ou *call stack*) pour gérer les différents appels.
#### Principe de fonctionnement
À chaque appel de fonction :
1. Python **empile** un nouveau *contexte d'exécution* contenant les paramètres et variables locales
2. Lorsque le cas de base est atteint, les appels sont **dépilés** un par un
3. Chaque résultat remonte vers l'appel précédent
#### Visualisation avec somme(3)
```
EMPILEMENT DÉPILEMENT
(descente) (remontée)
┌─────────────┐ ┌─────────────┐
│ somme(0) │ ← cas de base │ somme(0) │ → retourne 0
> Cette limite existe pour protéger la mémoire de l'ordinateur. On peut la modifier avec `sys.setrecursionlimit()`, mais ce n'est généralement pas recommandé.
-----------
### Terminaison d'une fonction récursive
Une question essentielle se pose : **comment être sûr qu'une fonction récursive s'arrête ?**
#### Le variant de boucle
Pour prouver qu'une fonction récursive termine, on utilise un **variant** : une quantité entière qui :
- est **positive ou nulle** à chaque appel
- **décroît strictement** à chaque appel récursif
Quand le variant atteint 0 (ou une valeur minimale), on est dans le cas de base.
#### Exemple avec somme(n)
Pour la fonction `somme(n)` :
- **Variant** : la valeur de `n`
- À chaque appel récursif, on appelle `somme(n-1)`, donc `n` décroît de 1
- Quand `n = 0`, on atteint le cas de base
| Appel | Valeur du variant (n) |
|-------|----------------------|
| somme(3) | 3 |
| somme(2) | 2 |
| somme(1) | 1 |
| somme(0) | 0 → cas de base |
Le variant décroît strictement et atteint 0 : **la fonction termine**.
#### Attention aux erreurs
```python
def mauvaise_somme(n):
if n == 0:
return 0
else:
return n + mauvaise_somme(n + 1) # n augmente !
```
Ici, le paramètre **augmente** au lieu de diminuer : la fonction ne terminera jamais (jusqu'à la `RecursionError`).
-----------
### Écrire une fonction récursive : méthodologie
Pour écrire correctement une fonction récursive, il faut identifier **trois éléments** :
| Élément | Question à se poser | Exemple avec `somme(n)` |
> En pratique, on choisit la récursivité quand elle rend le code **plus clair** ou quand le problème est **naturellement récursif** (parcours d'arbres, fractales, etc.).
Ces différents types de récursivité sont quelque peu différents de ce que l'on vient de voir. Mais nous pourrions les rencontrer dans la suite du programme.
À la différence de la récursivité dite **simple**, il y aura ici **plusieurs appels de fonctions** dans une même instruction.
> En mathématiques, la suite de Fibonacci est une suite de nombres entiers dont chaque terme successif représente la somme des deux termes précédents, et qui commence par 0 puis 1. Ainsi, les dix premiers termes qui la composent sont 0, 1, 1, 2, 3, 5, 8, 13, 21 et 34.
On remarque que `fibonacci(2)` est calculé **deux fois**, et `fibonacci(1)`**trois fois**. Cette redondance rend l'algorithme très inefficace pour de grandes valeurs de `n`.
> Ce problème sera résolu grâce à la **programmation dynamique**, que nous verrons dans un prochain chapitre.
-----------------
### À retenir
- Une fonction **récursive** s'appelle elle-même
- Elle doit avoir un **cas de base** pour s'arrêter
- On prouve la **terminaison** avec un **variant** (quantité qui décroît)
- Les appels s'empilent dans la **pile d'appels**
- Python limite le nombre d'appels récursifs (~1000)
- Récursif ≠ toujours meilleur : choisir selon le problème
<arel="license"href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><imgalt="Licence Creative Commons"style="border-width:0"src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png"/></a><br/>Ce cours est mis à disposition selon les termes de la <arel="license"href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.