Récursion en Java

Récursion en Java

1. introduction

Dans cet article, nous allons nous concentrer sur un concept de base dans n'importe quel langage de programmation: la récursivité.

Nous expliquerons les caractéristiques d'unrecursive function et montrerons comment utiliser la récursivité pour résoudre divers problèmes en Java.

2. Comprendre la récursivité

2.1. La définition

En Java, le mécanisme d'appel de fonction prend en chargethe possibility of having a method call itself. Cette fonctionnalité est connue sous le nom derecursion.

Par exemple, supposons que nous voulions additionner les entiers de 0 à une valeurn:

public int sum(int n) {
    if (n >= 1) {
        return sum(n - 1) + n;
    }
    return n;
}

Il y a deux exigences principales pour une fonction récursive:

  • A Stop Condition - la fonction renvoie une valeur lorsqu'une certaine condition est satisfaite, sans autre appel récursif

  • The Recursive Call - la fonction s'appelle elle-même avec uninput qui est un pas de plus vers la condition d'arrêt

Chaque appel récursif ajoutera un nouveau cadre à la mémoire de pile de la machine virtuelle. Donc,if we don’t pay attention to how deep our recursive call can dive, an out of memory exception may occur.

Ce problème potentiel peut être évité en utilisant l'optimisation de la récursion de la queue.

2.2. Récurrence de la queue et récursivité de la tête

Nous nous référons à une fonction récursive commetail-recursionwhen the recursive call is the last thing that function executes. Sinon, elle est connue sous le nom dehead-recursion.

Notre implémentation ci-dessus de la fonctionsum() est un exemple de récursivité de tête et peut être changée en récursivité de queue:

public int tailSum(int currentSum, int n) {
    if (n <= 1) {
        return currentSum + n;
    }
    return tailSum(currentSum + n, n - 1);
}

Avec la récursion de queue,the recursive call is the last thing the method does, so there is nothing left to execute within the current function.

Ainsi, logiquement, il n’est pas nécessaire de stocker le cadre de pile de la fonction actuelle.

Bien que le compilateurcan utilise ce point pour optimiser la mémoire, il convient de noter que lesJava compiler doesn’t optimize for tail-recursion for now.

2.3. Récursivité contre itération

La récursivité peut aider à simplifier la mise en œuvre de certains problèmes complexes en rendant le code plus clair et plus lisible.

Mais comme nous l'avons déjà vu, l'approche récursive nécessite souvent plus de mémoire car la mémoire de pile requise augmente à chaque appel récursif.

Au lieu de cela, si nous pouvons résoudre un problème de récursion, nous pouvons également le résoudre par itération.

Par exemple, notre méthodesum pourrait être implémentée en utilisant l'itération:

public int iterativeSum(int n) {
    int sum = 0;
    if(n < 0) {
        return -1;
    }
    for(int i=0; i<=n; i++) {
        sum += i;
    }
    return sum;
}

Par rapport à la récursivité, l'approche itérative pourrait potentiellement donner de meilleures performances. Ceci étant dit, l'itération sera plus compliquée et plus difficile à comprendre par rapport à la récursivité, par exemple: traverser un arbre binaire.

Faire le bon choix entre la récursion de la tête, la récursion de la queue et une approche itérative dépend du problème et de la situation spécifiques.

3. Exemples

Maintenant, essayons de résoudre certains problèmes de manière récursive.

3.1. Trouver la n-ième puissance de dix

Supposons que nous devions calculer la puissancen-ème de 10. Ici, notre entrée estn. En pensant de manière récursive, nous pouvons d'abord calculer(n-1)-ème puissance de 10, et multiplier le résultat par 10.

Ensuite, pour calculer la puissance(n-1)-ème de 10 sera la puissance(n-2)-ème de 10 et multiplier ce résultat par 10, et ainsi de suite. Nous continuerons ainsi jusqu'à ce que nous arrivions à un point où nous devons calculer la (n-n) -ième puissance de 10, qui est 1.

Si nous voulions l'implémenter en Java, nous écririons:

public int powerOf10(int n) {
    if (n == 0) {
        return 1;
    }
    return powerOf10(n-1) * 10;
}

3.2. Recherche du n-ième élément de la séquence de Fibonacci

À partir de0 et1,the Fibonacci Sequence is a sequence of numbers where each number is defined as the sum of the two numbers proceeding it:0 1 1 2 3 5 8 13 21 34 55

Donc, étant donné un nombren, notre problème est de trouver l'élémentn-ème deFibonacci Sequence. To implement a recursive solution, we need to figure out the Stop Condition and the*Recursive Call*.

Heureusement, c'est très simple.

Appelonsf(n) la valeurn-ème de la séquence. Ensuite, nous auronsf(n) = f(n-1) + f(n-2) (lesRecursive Call).

Pendant ce temps,f(0) = 0 etf(1) = 1 (Stop Condition).

Ensuite, il est vraiment évident pour nous de définir une méthode récursive pour résoudre le problème:

public int fibonacci(int n) {
    if (n <= 1) {
        return n;
    }
    return fibonacci(n-1) + fibonacci(n-2);
}

3.3. Conversion de décimal en binaire

Examinons maintenant le problème de la conversion d'un nombre décimal en binaire. La condition est d'implémenter une méthode qui reçoit une valeur entière positiven et renvoie une représentation binaireString.

Une approche pour convertir un nombre décimal en binaire consiste à diviser la valeur par 2, à enregistrer le reste et à diviser le quotient par 2.

Nous continuons à nous diviser comme ça jusqu'à ce que nous obtenions un quotient de0. Ensuite, en écrivant tous les restes en ordre de réserve, nous obtenons la chaîne binaire.

Par conséquent, notre problème est d'écrire une méthode qui renvoie ces restes dans l'ordre de réserve:

public String toBinary(int n) {
    if (n <= 1 ) {
        return String.valueOf(n);
    }
    return toBinary(n / 2) + String.valueOf(n % 2);
}

3.4. Hauteur d'un arbre binaire

La hauteur d'un arbre binaire est définie par le nombre d'arêtes allant de la racine à la feuille la plus profonde. Notre problème maintenant est de calculer cette valeur pour un arbre binaire donné.

Une approche simple serait de trouver la feuille la plus profonde, puis de compter les bords entre la racine et cette feuille.

Mais en essayant de penser à une solution récursive, nous pouvons reformuler la définition de la hauteur d'un arbre binaire comme la hauteur maximale de la branche gauche de la racine et de la branche droite de la racine, plus1.

Si la racine n'a ni branche gauche ni branche droite, sa hauteur est dezero.

Voici notre implémentation:

public int calculateTreeHeight(BinaryNode root){
    if (root!= null) {
        if (root.getLeft() != null || root.getRight() != null) {
            return 1 +
              max(calculateTreeHeight(root.left),
                calculateTreeHeight(root.right));
        }
    }
    return 0;
}

Par conséquent, nous voyons quesome problems can be solved with recursion in a really simple way.

4. Conclusion

Dans ce tutoriel, nous avons introduit le concept de récursivité en Java et en avons fait la démonstration à l'aide de quelques exemples simples.

L'implémentation de cet article peut être trouvéeover on Github.