+-
Vous devez m'envoyer en dépôt sur le cahier de texte d'Ecole Directe un fichier nommé NOM_Prenom_TP_recursivite.py avec NOM et prenom vos nom et prénom qui contiendra toutes les fonctions Python demandées dans le TP.
Les fonctions Python
Les fonctions Python présentes dans le fichier sont :
factorielle_iterative(n)
factorielle_recursive(n)
fibonacci_iterative(n)
fibonacci_recursive(n)
rendu_pieces_algorithme_glouton_recur(somme,S,pieces_rendues=[],ind=0)
dessin_recursif()
koch(longueur, n)
flocon(taille, etape)
Chaque fonction doit être documentée en utilisant les docstring
et incluant plusieurs jeux de tests utilisant la bibliothèque doctest
.
A faire vous même
Analysez puis testez le programme suivant :
def fctA():
print ("Début fonction fctA")
i=0
while i<5:
print("fctA",i)
i = i + 1
print ("Fin fonction fctA")
def fctB():
print ("Début fonction fctB")
i=0
while i<5:
if i==3:
fctA()
print ("Retour à la fonction fctB")
print ("fctB",i)
i = i + 1
print ("Fin fonction fctB")
fctB()
Vous devriez obtenir l'enchainement suivant :
Début fonction fctB
fctB 0
fctB 1
fctB 2
Début fonction fctA
fctA 0
fctA 1
fctA 2
fctA 3
fctA 4
Fin fonction fctA
Retour à la fonction fctB
fctB 3
fctB 4
Fin fonction fctB
Dans l'exemple ci-dessus, nous avons une fonction (fctB) qui appelle une autre fonction (fctA). La principale chose à retenir de cet exemple est que l'exécution de fctB est interrompue pendant l'exécution de fctA. Une fois l'exécution de fctA terminée, l'exécution de fctB reprendra là où elle avait été interrompue.
Pour gérer ces fonctions qui appellent d'autres fonctions, le système utilise une "pile d'exécution". Une pile d'exécution permet d'enregistrer des informations sur les fonctions en cours d'exécution dans un programme. On parle de pile, car les exécutions successives "s'empilent" les unes sur les autres. Si nous nous intéressons à la pile d'exécution du programme étudié ci-dessus, nous obtenons le schéma suivant :
Nous pouvons "découper" l'exécution de ce programme en 3 parties :
Il est important de bien comprendre que la fonction située au sommet de la pile d'exécution est en cours d'exécution. Toutes les fonctions situées "en dessous" sont mises en pause jusqu'au moment où elles se retrouveront au sommet de la pile. Quand une fonction termine son exécution, elle est automatiquement retirée du sommet de la pile (on dit que la fonction est dépilée).
La pile d'exécution permet de retenir la prochaine instruction à exécuter au moment où une fonction sera sortie de son ""état de pause" (qu'elle se retrouvera au sommet de la pile d'exécution) :
Évidemment l'explication donnée ci-dessus est quelque peu simpliste : c'est l'adresse mémoire de la prochaine instruction machine à exécuter qui est conservée dans la pile d'exécution.
Dans l'exemple ci-dessus, on retrouve une variable i dans les deux fonctions : fctA et fctB. La variable i présente dans la fonction fctA n'a rien à voir avec la variable i présente dans la fonction fctB (elles portent le même nom, mais elles représentent 2 adresses mémoires différentes). Il est très important de bien comprendre que les variables créées dans une fonction ne "sortent" pas de la fonction : chaque fonction possède sa propre liste de variable, comme déjà dit ci-dessus la variable i de la fonction fctB est différente de la variable i de la fonction fctA.
La pile d'exécution conserve une "trace" des valeurs des variables lorsqu'une autre fonction est exécutée. Par exemple la valeur de i (fctB) est conservée au moment de l'exécution de fctA. Quand l'exécution de fctA se termine est que l'exécution de fctB "reprend", la valeur référencée par i (fctB) a été "conservée" (voilà pourquoi on reprend l'exécution de fctB avec un "fctB 3").
Une fonction peut s'appeler elle-même, on parle alors de fonction récursive.
A faire vous même
Analysez puis testez le programme suivant :
def fctA():
print ("Hello")
fctA()
fctA()
Comme vous pouvez le constater, nous avons une erreur dans la console Python :
RecursionError: maximum recursion depth exceeded while calling a Python object
Dans le cas où une fonction s'appelle elle-même (fonction récursive), on retrouve le même système de pile d'exécution. Dans l'exemple traité ci-dessus, les appels s'enchainent sans rien pour mettre un terme à cet enchainement, la taille de la pile d'exécution augmente sans cesse (aucune fonction ne termine son exécution, nous n'avons pas de "dépilement" juste des "empilements"). Le système interrompt le programme en générant une erreur quand la pile d'exécution dépasse une certaine taille.
Quand on écrit une fonction récursive, il est donc nécessaire de bien penser à mettre en place une structure qui à un moment ou à un autre mettra fin à ces appels récursifs.
Dans le cas de fonctions récursives, il est, comme pour n'importe quelle fonction, possible d'utiliser des paramètres :
A faire vous même
Essayez de prévoir le résultat de l'exécution du programme ci-dessus. Vérifiez votre hypothèse en exécutant le programme.
def fonct(n):
if n>0:
fonct(n-1)
print(n)
fonct(3)
Essayons de comprendre en détail ce qui se passe dans le programme ci-dessus :
Voici un schéma expliquant le processus en termes de pile d'exécution :
Il ne faut jamais perdre de vu qu'à chaque nouvel appel de la fonction fonct le paramètre n est différent.
Voici un lien permettant de visualiser la gestion de la pile d'exécution pour ce programme récursif avec Python Tutor
Nous allons étudier le calcul de la factorielle grâce à une fonction récursive. D'après Wikipédia : "En mathématiques, la factorielle d'un entier naturel n est le produit des nombres entiers strictement positifs inférieurs ou égaux à n". Par exemple : la factorielle de 3 est : 3 x 2 x 1 = 6 ; la factorielle de 4 est 4 x 3 x 2 x 1 = 24 ; la factorielle de 5 est 5 x 4 x 3 x 2 x 1 = 120 ...
Si on note la factorielle de n par n!, on a :
Ecrire une fonction non récursive nommée factorielle_iterative(n)
qui calcule et retourne n! en utilisant une boucle.
Ecrire une fonction récursive nommée factorielle_recursive(n)
qui calcule et retourne n! sans utiliser une boucle.
Comme vous pouvez le constater, la fonction fact est structurée de la même manière que la définition mathématique vu ci-dessus :
L'utilisation des fonctions récursives est souvent liée à la notion de récurrence en mathématiques :
En mathématiques une suite définie par récurrence est une suite définie par son premier terme et par une relation de récurrence, qui définit chaque terme à partir du précédent ou des précédents lorsqu'ils existent.
La fonction récursivefactorielle_recursive()
est dite une fonction simplementrécursive car la formule de récurrence ne fait intervenir que le terme précédent.
Il existe des suites définies en mathématiques avec une récurrence dite double, ou triple (ou plus généralement multiple).
Un exemple très connu de suite définie par une récurrence double est la suite de Fibonacci.
Cette suite nommée u
est définie comme suit :
Ce qui nous donne pour les 6 premiers termes de la suite de Fibonacci :
Ecrire deux fonctions fibonacci_iterative(n)
et fibonacci_recursive(n)
qui calculent et retournent le terme de rang n de la suite de Fibonacci.
Etude de la comparaison de l'efficacité des deux fonctions
En terme de durée d'exécution
En utilisant la méthode time
du module time
qui permet de calculer la durée d'exécution d'une partie d'un programme, comparer les durées
d'exécution des fonctions fibonacci_iterative(n)
et fibonacci_recursive(n)
pour des valeurs de n définies par n = 2i pour i allant de 0 à 8.
Voici un exemple d'utilisation de time
timestamp1 = time.time()
val = fibonacci_iteratif(1024)
timestamp2 = time.time() - timestamp1
En terme de nombre de boucles ou bien de nombre d'appels récursifs
Modifiez les deux fonctions en introduisant un compteur de boucle pour la fonction fibonacci_iterative(n)
et un compteur du nombre d'appels à la fonction fibonacci_recursive(n)
.
Donner une explication sur les différences observées.
Vous donnerez notamment l'ordre de grandeur de la complexité algorithmique des deux versions itérative et récursive du calcul d'un terme de la suite de Fibonacci.
Vous pouvez vous aider de la ressource suivante : ici.
Nous allons reprendre l'exemple de l'algorithme de rendu de pièces avec une méthode de résolution dite gloutonne et dans une version itérative.
def rendu_pieces_algorithme_glouton(somme,S):
ind = 0
pieces_rendues = []
while somme != 0:
while S[ind] <= somme:
pieces_rendues.append(S[ind])
somme -= S[ind]
if somme != 0:
if ind == len(S)-1:
return -1
else:
ind += 1
return pieces_rendues
Voici une version possible de la même fonction dans une version récursive :
def rendu_pieces_algorithme_glouton_recur(somme,S,pieces_rendues=[],ind=0):
if somme == 0:
return pieces_rendues
elif S[ind] <= somme:
return rendu_pieces_algorithme_glouton_recur(...., S, ...., ind)
elif ind == len(S)-1:
return -1
else:
return rendu_pieces_algorithme_glouton_recur(...., S, ...., ....)
Explications :
Les paramètres de la fonction rendu_pieces_algorithme_glouton_recur
sont :
somme
: la somme à rendreS
: un tuple du système monétaire des pièces possibles rangées par ordre de valeur décroissantepieces_rendues
: La liste des pièces rendues pour la somme à rendre
Cette liste est renseignée au fil des appels récursifs de la fonction.
ind
: indice courant dans le tuple S
.
Cet indice est incrémenté (ou pas) au fil des appels récursifs de la fonction.
Remplacez les ..... par les paramètres d'appel récursif qui conviennent.
Et tester (avec doctest
) votre programme avec les systèmes monétaires suivants :
L'Euro (en centimes) S = (500,200,100,50,20,10,5,2,1)
Le dollar américain (en cents) S = (100,50,10,5,1)
Turtle
qui permet de dessinerA faire vous même
Étudiez le Wikibook consacré au module Turtle (wikibook Turtle) afin d'acquérir les bases de ce module.
A faire vous même
Essayez de prévoir le résultat de l'exécution du programme ci-dessus. Vérifiez votre hypothèse en exécutant le programme.
import turtle as t
t.forward(100)
t.left(120)
t.forward(100)
t.left(120)
t.forward(100)
Testez le programme ci-dessous et écrire une version récursive de la fonction dessin()
nommée dessin_recursif()
.
from turtle import *
couleurs = ['blue','green','yellow','orange','red','purple']
bgcolor('black')
def dessin():
for i in range(180):
color(couleurs[i%6])
forward(i)
right(59)
done()
dessin()
A faire vous même
Visionnez la vidéo consacrée au flocon de Koch :
A faire vous même
Voici un programme Python à compléter qui construit le flocon de Von Koch.
import turtle as t
def koch(longueur, n):
if n == 0:
t.forward(longueur)
else:
koch(...., n-1)
t.left(..)
koch(...., n-1)
t.right(..)
koch(...., n-1)
t.left(..)
koch(...., n-1)
def flocon(taille, etape):
koch(taille, etape)
t.right(..)
koch(taille, etape)
t.right(...)
koch(taille, etape)
flocon(100, 3)
A partir des informations obtenues sur la construction du flocon de Von-Koch, remplacez les pointillés par ce qu'il faut pour générer la figure fractale.
Vous pouvez consulter cette ressource sur des animations avec des figures fractales : ici.
Essayez de résoudre ce jeu avec 4 disques (ou plus), à partir de cette animation.
Auteur : Hugues Malherbe (basé sur un document de David Roche)