Une définition de fonction est récursive si son corps contient une expression d'invocation d'elle-même, dite invocation récursive . L'intérêt des définitions récursives est double. D'une part, elles permettent de transcrire de façon quasiment littérale certaines définitions mathématiques, souvent appelées récurrentes. Ainsi la transcription de l'identité
package exemples; class Récursion { static int sommerEntiers(int m) { return (m <= 0) ? 0 : sommerEntiers(m-1) + m; // invocation récursive } public static void main(String[] args) { System.out.println("somme de 1 à 3 = " + sommerEntiers(3)); // --> 6 } }
Une définition récursive comporte toujours une condition d'arrêt, qui est ici m <= 0.
Le mécanisme d'invocation des fonctions s'applique en particulier aux
fonctions définies récursivement. Par exemple, une invocation
sommerEntiers(3)
dans le corps de main
provoque la suite
d'invocations :
L'évolution de la pile pendant cette invocation est la suivante :
Un exemple classique de définition récursive est celui de la fonction factorielle, pour laquelle il n'existe pas d'expression arithmétique :
package exemples; class Récursion { static int fact(int n) { return (n == 0) ? 1 : n*fact(n-1); // fact(n-1) est une invocation récursive } } public static void main(String[] args) { System.out.println("fact(3) = " + fact(3)); // --> 6 } }
La dichotomie est un exemple simple de cette méthode. On cherche
à calculer un zéro d'une fonction réelle continue sur un intervalle
, prenant des valeurs de signes opposés aux extrémités. Le
théorème des valeurs intermédiaires assure l'existence d'un zéro.
L'idée de la dichotomie est de chercher un zéro sur
ou
bien sur
, selon le signe de
.
package exemples; class Dichotomie { static double f(double x) { ... } static final double EPS = 1e-5; static double zéro(double a, double b) // on suppose que f(a)*f(b) < 0 { double m = (a+b)/2; double fm = f(m); if (Math.abs(fm)<EPS) { return m; } else { if (f(a)*fm<0) { return zéro(a,m); } else { return zéro(m,b); } } } }
La condition d'arrêt est Math.abs(fm)<EPS, EPS étant une variable de la classe Dichotomie, et Math.abs étant la fonction qui calcule la valeur absolue d'un double.
La portée d'une variable d'une classe comprend cette classe.
static int fib(int n) { return n<=1 ? 1 : fib(n-1) + fib(n-2); }
La figure représente l'arbre des invocations de
fib(4), et la figure
représente les états
successifs de la pile, pour une invocation de fib(4) dans
main(...) (pour alléger, les cadres d'invocation de
main(), fib(4), fib(3), etc, y sont désignés
par m, 4, 3, etc). On remarquera que
certaines invocations sont exécutées plusieurs fois : fib(2)
est exécutée deux fois, fib(1) trois fois, fib(0) deux
fois. Rappelons que les sous-expressions étant évaluées de gauche à
droite, l'invocation fib(n-1) est évaluée avant
fib(n-2).
Un ensemble de définitions de fonctions est mutuellement récursif
si la relation « invoque
» admet un cycle :
invoque
...invoque
invoque
.
L'exemple suivant est classique
(et sans grand intérêt) :
class Parité { static boolean impair(int n) { if (n == 0) { return false; } else { return pair(n-1); } } static boolean pair(int n) { if (n == 0) { return true; } else { return impair(n-1); } } }
Les fonctions Parité.pair et Parité.impair
s'invoquent mutuellement :