Récursivité Lycée Berthollet, MP/MP* 2016-17 I Structures récursives Définition 1 Une structure est dite récursive lorsque la description de cette structure fait référence à la structure elle-même. Exemple 2 En calcul des propositions (logique), on dit qu’une expression est une proposition si : — soit c’est une variable logique (A, B...) ; — soit elle est du type non(P), où P est une proposition ; — soit elle est du type (P et Q), (P ou Q) ou (P =⇒ Q), où P est Q sont deux propositions. Exemple 3 Les fractals sont des objets mathématiques “autosimilaires” qui peuvent être décrit de manière récursive. Voir un exemple dans la suite. Exemple 4 La notion de “mise en abyme” en littérature ou au cinéma est une sorte de récursi- vité. Voir par exemple la publicité de la “vache-qui-rit”. Enfin, ce qui nous préoccupe ici est la notion d’algorithme récursif : Définition 5 Dans un langage informatique, une fonction est dite récursive, si elle s’appelle elle-même soit directement, soit par le biais d’une autre fonction. Beaucoup de langages modernes permettent de gérer les fonctions récursives. C’est le cas du langage Python. Cette gestion nécessite de gérer les paramètres, variables locales et points de retour de chaque appel de la fonction dans une ou plusieurs piles, comme nous le verrons dans un exemple simple. Voici un exemple de fonction récursive en pseudo-code, qui calcule le √ n-ième terme de la suite (un )n∈N définie par u0 = 0 et la relation un+1 = 1 + un : fonction u(n): si n==0, retourner 0 sinon, retourner sqrt(1+u(n-1)) 1 II Fonctions récursives en Python Voir le fichier recusivite.py en annexe 1 Factorielle On programme dans un premier temps la factorielle de manière récursive et on observe la pile des paramètres ainsi que la pile des appels. On remarque que le nombre d’appels imbriqués est limité en Python (par défaut à 1000). On programme aussi cette factorielle de manière non récursive et on remarque que l’ordre de complexité est inchangé (linéaire dans les deux cas), mais que la fonction non récursive est environ 2 fois plus rapide. 2 Puissances On programme le calcul des puissances entières positives d’un nombre de manière récursive (linéaire en l’exposant n), puis de manière récursive et rapide (logarithmique en l’exposant n). 3 PGCD L’algorithme d’Euclide se prête très bien à la récursivité avec une complexité dont on pourrait montrer qu’elle est au pire logarithmique. 4 Fibonacci Le calcul naïf de la suite de Fibonacci de manière récursive est d’une complexité catastrophique, alors qu’un algorithme itératif est très facile à écrire et efficace (linéaire). On montre comment faire un calcul récursif linéaire, mais cela reste sensiblement plus lent que le calcul itératif précédent. On représentera au tableau l’arbre des appels pour avoir une idée de la complexité de l’algorithme naïf. 5 Permutations de séquences On montre comment créer la liste de toutes les permutations d’une séquence donnée (liste ou chaîne de caractères) de manière récursive (ce qui est beaucoup plus délicat de manière non récursive). On mettra la complexité en lien avec l’arbre des appels. 6 Flocon de neige Il est possible de dessiner assez facilement certaines courbes fractales à l’aide de la récursivité et du module graphique turtle. On donne l’exemple archi-classique du flocon de neige de Van Koch. 2 III Correction et terminaison Sur certains des exemples précédents, on voit que la récurrence (normale ou forte) est l’instrument adapté aux preuves de correction dans le cas récursif, tandis que la terminaison repose sur la stricte décroissance de la taille du ou des paramètres des appels successifs. IV Conclusion Au vu des exemples ci-dessus, on tire quelques enseignements. Quand utiliser la récursivité ? — Lorsqu’on ne sait pas faire autrement ; — Lorsqu’on travaille sur des structures elle-même récursives (listes, arbres, fractales,...) ; — Lorsque l’algorithme récursif est très élégant, et ne change pas l’ordre de grandeur de la complexité par rapport à un algorithme itératif. Attention cependant à la limite du nombre d’appels imbriqués ! Quand ne pas utiliser la récursivité ? — Lorsqu’il existe un algorithme non-récursif raisonnablement simple et élégant faisant la même chose ; — Lorsque l’arbre des appels est potentiellement très profond ; — S’il y a des paramètres ou des variables locales “volumineux” dans la fonction. 3 V Annexe : code # -----------------------------------------------------------------------# Mesure du temps d’execution d’une fonction # -----------------------------------------------------------------------import time def teste(appel): print(appel+’ ---> ’,end=’’) tInit = time.clock() res = eval(appel) tFin = time.clock() print(res,tFin-tInit) # -----------------------------------------------------------------------# Factorielle # -----------------------------------------------------------------------def facto(n): if n==0: return 1 else: return n*facto(n-1) print(facto(10)) # produit une erreur: # print(facto(1000)) # Visualisation de la pile des variables locales et des appels from inspect import * def factoVisu(n): print(locals()) if n==0: pile = stack() for i in range(len(pile)): print(getframeinfo(pile[i][0])) return 1 else: res = n*factoVisu(n-1) print(locals()) return res print(factoVisu(10)) 4 # Comparaison avec la version non recursive def factoIter(n): res = 1 for i in range(2,n+1): res *= i return res teste(’factoIter(900)’) teste(’facto(900)’) print(factoIter(10000)) # -----------------------------------------------------------------------# Puissance # -----------------------------------------------------------------------def puiss(x,n): """ Calcule x**n de facon recursive """ if n==0: return 1 else: return x*puiss(x,n-1) print(puiss(2,32)) def puissRapide(x,n): """ Calcule rapidement x**n de facon recursive """ if n==0: return 1 else: y =puissRapide(x,n//2) if (n%2==0): return y*y else: return x*y*y teste(’puissRapide(3,900)’) teste(’puiss(3,900)’) 5 # -----------------------------------------------------------------------# PGCD # -----------------------------------------------------------------------def pgcd(a,b): if b==0: return a else: return pgcd(b,a%b) print(pgcd(10,6)) print(pgcd(1235674567543467567,908765345432342453243234254254233212356453456)) # -----------------------------------------------------------------------# Fibonacci # -----------------------------------------------------------------------def fibo(n): if n<2: return n else: return fibo(n-1)+fibo(n-2) for i in range(10): print(fibo(i),end=’ ’) print(fibo(30)) print(fibo(35)) def fiboIter(n): if n<2: return n else: Fnm1,Fn = 0,1 for i in range(n-1): Fnm1,Fn = Fn,Fnm1+Fn return Fn for i in range(10): print(fiboIter(i),end=’ ’) print(fiboIter(35)) print(fiboIter(100000) def fiboRecBis(n): """ Retourne F(n-1) et Fn """ if n==0: return 1,0 6 elif n==1: return 0,1 else: Fnm2,Fnm1 = fiboRecBis(n-1) return Fnm1,Fnm2+Fnm1 print(fiboRecBis(35)[1]) # Comparaison recursif/iteratif teste(’fiboIter(900)’) teste(’fiboRecBis(900)[1]’) # -----------------------------------------------------------------------# Permutations # -----------------------------------------------------------------------def permChaine(ch): if len(ch)==1: return [ch] else: res = [] for i,car in enumerate(ch): res += [car+s for s in permChaine(ch[:i]+ch[i+1:])] return res print(permChaine(’abc’)) print(permChaine(’abricot’)) def permListe(liste): if len(liste)==1: return [liste] else: res = [] for i,item in enumerate(liste): res += [[item]+s for s in permListe(liste[:i]+liste[i+1:])] return res print(permListe(list(’abc’))) 7 # -----------------------------------------------------------------------# Flocon # -----------------------------------------------------------------------from math import * from turtle import * def fractTurtle(n): if n==0: forward(1) else: fractTurtle(n-1) right(60) fractTurtle(n-1) left(120) fractTurtle(n-1) right(60) fractTurtle(n-1) def floconTurtle(n): clearscreen() speed(’fastest’) # hideturtle() getpen() setheading(240) fractTurtle(n) left(120) fractTurtle(n) left(120) fractTurtle(n) floconTurtle(5) exitonclick() # -----------------------------------------------------------------------# Arbres et tris seront vus plus tard # ------------------------------------------------------------------------ 8