Vous êtes sur la page 1sur 7

Rcursivit (1/3)

Un programme est dit rcursif s'il s'appelle lui mme (du latin recurrere = courir en arrire) Un programme rcursif est donc forcment une fonction ou une procdure (il doit pouvoir s'appeler) Exemple : la factorielle
version itrative : n! = 1 2 ... n
// cette fonction renvoie n! (n est suppos suprieur ou gal 1) fonction avec retour entier factorielle1(entier n) entier i, resultat; dbut resultat <- 1; pour (i allant de 2 n pas 1) faire resultat <- resultat*i; finpour retourne resultat; fin

Rcursivit (2/3)
version rcursive : 1! = 1 et, pour n > 1, n! = n * (n-1)!
// cette fonction renvoie n! (n est suppos suprieur ou gal 1) fonction avec retour entier factorielle2(entier n) dbut si (n = 1) alors retourne 1; sinon retourne n*factorielle2(n-1); finsi fin

Puisqu'une fonction rcursive s'appelle elle-mme, il est impratif qu'on prvoit une condition d'arrt la rcursion, sinon le programme ne s'arrte jamais! On doit toujours tester en premier la condition d'arrt, et ensuite, si la condition n'est pas vrifie lancer un appel rcursif

noitammargorP te euqimhtiroglA - 2 ertsemeS - euqitamrofnI ecneciL

noitammargorP te euqimhtiroglA - 2 ertsemeS - euqitamrofnI ecneciL

Rcursivit (3/3)
La rcursivit comme technique de programmation permet d'criture facilement et de faon trs lisible des programmes rptitifs compliqus
elle est trs puissante pour crire les algorithmes sur des structures complexes comme les arbres et les graphes elle est au coeur de certains langages, en particulier les langages fonctionnels (LISP, CAML, ...), la programmation logique (PROLOG)

La notion de rcursivit joue galement un grand rle en informatique thorique (thorie de la calculabilit, lambda-calcul, thorme de Gdel, ...) et en mathmatiques

Appels rcursifs (1/2)


factorielle2(3) dbut ... fin

retour de la valeur phase de remonte


4

appel rcursif

factorielle2(2) dbut ... fin

retour de la valeur

appel rcursif

factorielle2(1) dbut ... fin

Chaque appel de fonction provoque la sauvegarde d'un contexte d'excution dans la pile d'excution. Un contexte comprend :
l'adresse de l'instruction qui a appel la fonction les valeurs des paramtres d'appel l'adresse de la fonction appele

noitammargorP te euqimhtiroglA - 2 ertsemeS - euqitamrofnI ecneciL

noitammargorP te euqimhtiroglA - 2 ertsemeS - euqitamrofnI ecneciL

phase de descente

Appels rcursifs (2/2)


La gestion de la rcursivit suppose que la pile d'excution la permette (c'est le cas dans la plupart des langages de haut niveau) Dans les langages n'implmentant pas ce mcanisme (COBOL, FORTRAN, BASIC, ...) on peut programmer de faon rcursive mais condition de programmer en plus cette gestion de pile. L'empilement des appels ne doit pas excder la taille de la pile (si la pile est pleine et qu'un nouvel appel est lanc, il y a dbordement de pile (Stack Overflow)

Dbordement de pile
public static void testPile(int nbAppels){ System.out.println("appel numro " + nbAppels); testPile(nbAppels + 1); }

L'excution testPile(1); entraine l'affichage suivant


appel numro 1 appel numro 2 ... appel numro 5711 Exception in thread "main" java.lang.StackOverflowError at sun.nio.cs.SingleByteEncoder.encodeArrayLoop(Unknown Source) at sun.nio.cs.SingleByteEncoder.encodeLoop(Unknown Source) at java.nio.charset.CharsetEncoder.encode(Unknown Source) at sun.nio.cs.StreamEncoder.implWrite(Unknown Source) at sun.nio.cs.StreamEncoder.write(Unknown Source) at java.io.OutputStreamWriter.write(Unknown Source) at java.io.BufferedWriter.flushBuffer(Unknown Source) at java.io.PrintStream.write(Unknown Source) at java.io.PrintStream.print(Unknown Source) at java.io.PrintStream.println(Unknown Source) at Test.testPile(Test.java:24) at Test.testPile(Test.java:25) ...
6

noitammargorP te euqimhtiroglA - 2 ertsemeS - euqitamrofnI ecneciL

noitammargorP te euqimhtiroglA - 2 ertsemeS - euqitamrofnI ecneciL

Rcursif versus itratif (1/3)


L'excution d'une version rcursive d'un programme est moins rapide que celle de la version itrative, mme si le nombre d'instructions est le mme ( cause des appels rcursifs) Exemple : l'excution de ce programme affiche gnralement une dure suprieure pour le calcul rcursif
public static int factRecur(int i){ if(i==1) return 1; else return i*factRecur(i-1); } public static int factIter(int i){ int result = i; while(i>1){i = i 1; result = result*i;} return result; } public static void main(String arg[]){ long duree = System.nanoTime(); factRecur(60); duree = System.nanoTime() - duree; System.out.println("Execution recursive " + duree); duree = System.nanoTime(); factIter(60); duree = System.nanoTime() - duree; System.out.println("Execution iterative " + duree); }

Rcursif versus itratif (2/3)


La version rcursive d'un algorithme peut parfois conduire excuter bien plus d'instructions que la version itrative Exemple : calcul des termes de la suite de Fibonacci (certains termes de la suite seront calculs plusieurs fois)
fonction avec retour entier fibo(entier n){ si ((n=1) ou (n=0)) alors retourne 1; sinon retourne fibo(n-1)+fibo(n-2); finsi } fibo(1) + fibo(2) + fibo(3) fibo(1) fibo(0)

Excution de fibo(4) :

fibo(4)

+ fibo(2) fibo(1) + fibo(0)


8

noitammargorP te euqimhtiroglA - 2 ertsemeS - euqitamrofnI ecneciL

noitammargorP te euqimhtiroglA - 2 ertsemeS - euqitamrofnI ecneciL

Rcursif versus itratif (3/3)


Ecrire un programme sous forme rcursive est souvent plus facile et rapide que sous forme itrative, en particulier pour des calculs rcursifs, ou lorsqu'on utilise des structures de donnes rcursives (listes, arbres) Conclusion : ne pas s'interdire d'crire en rcursif pour se faciliter la vie, mais garder un oeil sur l'efficacit Remarque : on peut toujours (thoriquement) drcursiver un algorithme rcursif, c'est--dire le transformer en algorithme itratif, mais ce n'est pas toujours facile

Rcursivit terminale (1/3)


Un algorithme est dit rcursif terminal si aucun traitement n'est effectu la remonte d'un appel rcursif (sauf le retour de la valeur) Contre exemple : forme rcursive non terminale de la factorielle
fonction avec retour entier factorielle2(entier n) dbut si (n = 1) alors retourne 1; sinon retourne n*factorielle2(n-1); finsi fin

Exemple : forme rcursive terminale de la factorielle


fonction avec retour entier factorielle3(entier n, entier resultat) dbut si (n = 1) alors retourne resultat; sinon retourne factorielle3(n-1, n * resultat); finsi fin // factorielle(4,1) renvoie la factorielle de 4
01

noitammargorP te euqimhtiroglA - 2 ertsemeS - euqitamrofnI ecneciL

noitammargorP te euqimhtiroglA - 2 ertsemeS - euqitamrofnI ecneciL

Rcursivit terminale (2/3)


Un algorithme rcursif terminal est en thorie plus efficace (mais souvent moins facile crire) que son quivalent non terminal : il n'y a qu'une phase de descente et pas de phase de remonte En rcursivit terminale, les appels rcursifs n'ont pas besoin d'tres empils car l'appel suivant remplace simplement l'appel prcdent dans le contexte d'excution Certains langages utilisent cette proprit pour excuter les rcursions terminales aussi efficacement que les itrations (ce n'est pas le cas de Java)

Rcursivit terminale (2/3)


Un algorithme rcursif terminal est facile drcursiver :
fonction avec retour ... iterative(P) tantque (non ConditionArret) faire // instructions P <- f(P); fintantque // instructions arret fin

fonction avec retour ... recursive(P) si (ConditionArret) alors // instructions arret sinon // instructions recursive(f(P)); finsi fin

Exemple : drcursivation de la factorielle terminale


fonction avec retour entier factorielle4(entier n, entier resultat) dbut tantque (n 1) faire resultat <- n*resultat; n <- n-1; fintantque retourne resultat; fin

11

21

noitammargorP te euqimhtiroglA - 2 ertsemeS - euqitamrofnI ecneciL

noitammargorP te euqimhtiroglA - 2 ertsemeS - euqitamrofnI ecneciL

Rcursivits multiple et imbrique


Le calcul de la suite de Fibonacci requiert une rcursivit multiple (plusieurs appels rcursifs dans la fonction) On peut galement faire de la rcursivit imbrique : un appel rcursif fait alors appel un autre appel rcursif Exemple : la suite d'Ackerman
A(m,n) = n+1 si m = 0, A(m,n) = A(m-1,1) si n=0 et m > 0 A(m,n) = A(m-1, A(m,n-1)) sinon
fonction avec retour entier ackerman(entier m, entier n) dbut si (m = 0) alors retourne n+1; sinon si ((m>0) et (n=0)) alors retourne ackerman(m-1,1); sinon retourne ackerman(m-1,ackerman(m,n-1)); finsi finsi fin
31

41

noitammargorP te euqimhtiroglA - 2 ertsemeS - euqitamrofnI ecneciL

On peut crire des algorithmes o les appels rcursifs se croisent d'une fonction l'autre, c'est de la rcursivit croise Exemple : rcursivit croise entre deux fonctions
// cette fonction renvoie vrai si l'entier est pair, faux sinon // on suppose que l'entier est positif ou nul fonction avec retour boolen estPair(entier n) dbut si (m = 0) alors retourne VRAI; sinon retourne estImpair(n-1); finsi fin // cette fonction renvoie vrai si l'entier est impair, faux sinon // on suppose que l'entier est positif ou nul fonction avec retour boolen estImpair(entier n) dbut si (m = 0) alors retourne FAUX; sinon retourne estPair(n-1); finsi fin

noitammargorP te euqimhtiroglA - 2 ertsemeS - euqitamrofnI ecneciL

Rcursivit croise