Pile d'appels
Pour illustrer les exemples de cette partie, nous allons utiliser la fonction rectangle() définie comme suit :
from turtle import *
def rectangle(n):
""" n - int, entier strictement positif """color('white', 'blue')
begin_fill()for _ in range(2):
forward(10)
left(90)
forward(n)
left(90)
end_fill()forward(10)
if __name__ == "__main__":
TurtleScreen._RUNNING = True
hideturtle()speed(0)
rectangle(50)
exitonclick()Ordre des appels récursifs
Dans une fonction récursive, l'ordre d'exécution des instructions dépend des appels récursifs à la fonction. En d'autres termes, certaines instructions peuvent être exécutées immédiatement tandis que d'autres sont « mises en attente » dans une pile d'appels.
Exemple : Exemple 1
Simulation :
La correction est en bas de la page, mais on devrait obtenir, pour l'appel fct1(50), le dessin suivant :

Exemple : Exemple 2
Simulation :
La correction est en bas de la page, mais on devrait obtenir, pour l'appel fct2(50), le dessin suivant :

Fondamental :
Lorsqu'on exécute fct2(50) :
On exécute
fct2(40)donc on met en attenterectangle(50);On exécute
fct2(30)donc on met en attenterectangle(40)en priorité devantrectangle(50)carfct2(40)doit être exécuté avantrectangle(50);On exécute
fct2(20)donc on met en attenterectangle(30)en priorité devantrectangle(40)en priorité devantrectangle(50);On exécute
fct2(10)donc on met en attenterectangle(20)en priorité devantrectangle(30)en priorité devantrectangle(40)en priorité devantrectangle(50);10n'est pas strictement supérieur à10, les appels se terminent.On dépile par ordre de priorité en traçant dans l'ordre
rectangle(20),rectangle(30),rectangle(40)puisrectangle(50)
Rappel : Complexité
La complexité d'un algorithme se décompose généralement sous deux formes :
La complexité en temps : combien de temps prend un algorithme pour s'exécuter ?
On ramène généralement cette question au décompte du nombre d'opérations élémentaires nécessaires pour le déroulement de l'algorithme.
La complexité en mémoire : quel espace en mémoire est nécessaire au déroulement de l'algorithme ?
On notera généralement n le nombre de données à traiter. La complexité est ensuite donnée comme un « ordre de grandeur » de n. Cet ordre de grandeur est souvent noté O (se lit « grand O ») :
Classe de complexité | Type de complexité |
|---|---|
O(1) | constante |
O(log(n)) | logarithmique |
O(n) | linéaire |
O(n × log(n)) | quasi-linéaire |
O(n²) | quadratique |
O(2n) | exponentielle |
O(n!) | factorielle |
Pour les fonctions récursives envisagées ci-dessous, on admet que la complexité en temps est proportionnelle au nombre d'appels récursifs.
Exemple : Exemple 1
On considère la fonction récursive suivante :
def descente(n):
""" n - int """if n > 0:
descente(n-1)
Combien d'appels à la fonction
descente()sont réalisés lorsqu'on exécute l'instructiondescente(5)?Combien d'appels à la fonction
descente()sont réalisés lorsqu'on exécute l'instructiondescente(8)?Donnez, en fonction de
n, le nombre d'appels à la fonctiondescente()réalisés lorsqu'on exécute l'instructiondescente(n).En déduire la complexité de cette fonction.
Des élèments de réponses sont en fin de page...
Exemple : Exemple 2
On considère la fonction récursive suivante :
def piano(n):
""" n - int """if n > 0:
piano(n-1)
piano(n-1)
Combien d'appels à la fonction
piano()sont réalisés lorsqu'on exécute l'instructionpiano(3)?Combien d'appels à la fonction
piano()sont réalisés lorsqu'on exécute l'instructionpiano(5)?Donnez, en fonction de
n, le nombre d'appels à la fonctionpiano()réalisés lorsqu'on exécute l'instructionpiano(n).En déduire la complexité de cette fonction.
Éléments de réponses...
Voici les éléments de réponses des exemples données ci-dessus.
Ordre des appels récursifs
Simulation : Exemple 1 : fct1()
Le programme complet :
from turtle import *
def rectangle(n):
""" n - int, entier strictement positif """color('white', 'blue')
begin_fill()for _ in range(2):
forward(10)
left(90)
forward(n)
left(90)
end_fill()forward(10)
def fct1(n):
""" n - int """if n > 10:
rectangle(n)
fct1(n-10)
if __name__ == "__main__":
TurtleScreen._RUNNING = True
hideturtle()speed(0)
fct1(50)
exitonclick()Le rectangle est tracé avant l'appel récursif suivant :
Simulation : Exemple 2 : fct2()
Le programme complet :
from turtle import *
def rectangle(n):
""" n - int, entier strictement positif """color('white', 'blue')
begin_fill()for _ in range(2):
forward(10)
left(90)
forward(n)
left(90)
end_fill()forward(10)
def fct2(n):
""" n - int """if n > 10:
fct2(n-10)
rectangle(n)
if __name__ == "__main__":
TurtleScreen._RUNNING = True
hideturtle()speed(0)
fct2(50)
exitonclick()Lorsqu'on exécute fct2(50) :
On exécute
fct2(40)donc on met en attenterectangle(50);On exécute
fct2(30)donc on met en attenterectangle(40)en priorité devantrectangle(50)carfct2(40)doit être exécuté avantrectangle(50);On exécute
fct2(20)donc on met en attenterectangle(30)en priorité devantrectangle(40)en priorité devantrectangle(50);On exécute
fct2(10)donc on met en attenterectangle(20)en priorité devantrectangle(30)en priorité devantrectangle(40)en priorité devantrectangle(50);10n'est pas strictement supérieur à10, les appels se terminent.On dépile par ordre de priorité en traçant dans l'ordre
rectangle(20),rectangle(30),rectangle(40)puisrectangle(50)
L'illustration ci-dessous permet de mieux visualiser l'empilement de ces appels :
Complexité
Simulation : Exemple 1
La flèche « → » modélise l'appel suivant.
descente(5)→descente(4)→descente(3)→descente(2)→descente(1)→descente(0)(0 n'est pas strictement supérieur à 0, c'est le dernier appel récursif).Lorsqu'on exécute l'instruction
descente(5), on effectue 6 appels à la fonctiondescente().De la même manière, lorsqu'on exécute l'instruction
descente(8), on effectue 9 appels à la fonctiondescente().Soit
nest négatif ou nul, alors le testn > 0n'est pas validé et il n'y a pas d'appel récursif à la fonctiondescente().Soit
nest strictement positif. Les appels se déroulent alors ainsi :descente(n)→descente(n-1)→ ... →descente(1)→descente(0).On effectue donc
n+1appels récursifs à la fonctiondescente().Ce nombre d'appels est un multiple de
n: la complexité est linéaire.
Simulation : Exemple 2
piano(3)appelle deux foispiano(2)Chaque appel de
piano(2)appelle deux foispiano(1).Chaque appel de
piano(1)appelle deux foispiano(0).Chaque appel de
piano(0)termine car 0 n'est pas strictement supérieur à 0.Le nombre total d'appels générés par
piano(3)est donc \(1+2+4+8=2⁴−1\)De la même manière, lorsqu'on exécute l'instruction
piano(5), on effectue \(2⁶−1\) appels à la fonctionpiano().Soit
nest négatif ou nul, alors le testn > 0n'est pas validé et il n'y a pas d'appel récursif à la fonctionpiano().Soit
nest strictement positif. On note \(T(n)\) le nombre d'appels engendrés par l'exécution depiano(n). Dès lors, \(T(n)=2T(n−1)=2×2T(n−2)=⋯=2^n T(0)\) appels.On effectue donc \(2^n\) appels
piano(0). Le nombre total d'appels engendrés parpiano(n)est \(1+2^1+2^2+...+2^n=2^{n+1}−1\)Ce nombre d'appels est, à une constante près, une puissance n-ième de 2 donc la complexité est exponentielle.



