Aller au contenu

Récursivité et pile d'appels en JavaScript

La récursivité est un concept fondamental en JavaScript qui permet aux fonctions de s'appeler elles-mêmes. Cette méthode est essentielle pour résoudre des problèmes qui peuvent être décomposés en tâches plus simples et répétitives. Cet article offre un aperçu complet de la récursivité, en explorant le contexte d'exécution, la pile d'appels et ses applications dans le parcours de structures récursives.

Qu'est-ce que la récursivité ?

La récursivité en programmation est une méthode où une fonction s'appelle elle-même une ou plusieurs fois jusqu'à ce qu'une condition spécifiée soit remplie, moment auquel le reste de chaque répétition est traité. Voici comment cela fonctionne :

  1. Cas de base : C'est la condition sous laquelle la récursivité se termine.
  2. Cas récursif : Si le cas de base n'est pas rempli, la fonction s'appelle à nouveau avec un nouvel ensemble de paramètres.

Chaque fois qu'une fonction récursive est appelée, elle crée une nouvelle entrée dans la pile d'appels, qui est essentiellement un enregistrement des appels de fonction en cours.

Contexte d'exécution et pile d'appels

Lorsqu'une fonction s'exécute en JavaScript, le moteur JavaScript crée un contexte d'exécution qui inclut toutes les variables, les paramètres et la position actuelle dans la fonction. La pile d'appels est essentiellement une pile de ces contextes d'exécution. En récursivité, chaque appel à une fonction récursive crée un nouveau contexte d'exécution qui est empilé sur la pile.

INFO

Soyez conscient des limites de taille de la pile en JavaScript. Les fonctions récursives peuvent rapidement consommer l'espace de la pile, entraînant une erreur « Maximum call stack size exceeded » (taille maximale de la pile d'appels dépassée). Pour éviter cela, optimisez vos algorithmes récursifs ou envisagez d'utiliser une approche itérative pour les appels profondément imbriqués.

Exemple : Rêves imbriqués

Imaginez un scénario où un personnage dans une histoire s'endort et rêve qu'il est quelqu'un d'autre, qui à son tour s'endort pour rêver d'un autre personnage, et ainsi de suite. Chaque niveau de rêve représente un appel récursif, et se réveiller de chaque niveau de rêve représente la suppression d'un contexte d'exécution de la pile d'appels.


Output appears here after Run.

Dans la fonction dream(level) que vous avez fournie, les cas de base et cas récursif sont clairement définis :

  • Cas de base : Cela se produit lorsque level === 0. C'est la condition qui empêche la récursivité de continuer indéfiniment. Dans ce cas, lorsque level atteint 0, la fonction affiche « Wake up! » et arrête de faire d'autres appels récursifs.
  • Cas récursif : Il est défini lorsque level > 0. Dans cette situation, la fonction affiche le level actuel, puis s'appelle à nouveau avec level - 1, réduisant ainsi le level de un à chaque appel. Cela continue jusqu'à ce que la condition du cas de base soit remplie.

Ces deux parties fonctionnent ensemble pour garantir que la fonction s'exécute correctement et se termine éventuellement.

Parcours récursifs

Le parcours récursif est une technique souvent utilisée avec des structures contenant plusieurs niveaux d'objets imbriqués, tels que des arbres ou des répertoires. Cette méthode est idéale pour effectuer des opérations telles que la recherche ou la construction d'une structure visuelle à partir de composants imbriqués.

Exemple : Parcours d'un système de fichiers

Voici comment vous pourriez utiliser la récursivité pour parcourir un système de fichiers, en listant tous les fichiers de chaque répertoire :


Output appears here after Run.

Dans la fonction listFiles(directory) que vous avez partagée, la récursivité implique le parcours d'une structure de répertoires :

  • Cas de base : De manière intéressante, la condition d'arrêt de cette fonction n'est pas explicitement formulée comme un cas de base traditionnel (comme une instruction if qui met fin à la récursivité). Au lieu de cela, elle s'arrête implicitement lorsqu'elle rencontre un répertoire sans sous-répertoires supplémentaires (c'est-à-dire que directory.directories est un tableau vide). Cela est dû au fait que la méthode forEach sur un tableau vide ne génère aucun appel récursif supplémentaire.
  • Cas récursif : Le cas récursif est explicitement invoqué avec directory.directories.forEach(listFiles);. Cela se produit lorsqu'un répertoire contient un ou plusieurs sous-répertoires, et que listFiles est appelé de manière récursive pour chaque sous-répertoire. Chaque appel récursif traite les fichiers et répertoires à l'intérieur de ce sous-répertoire, approfondissant continuellement la structure jusqu'à ce qu'aucun sous-répertoire ne soit trouvé (cas de base implicite).

Cette fonction démontre efficacement comment la récursivité peut naviguer dans des structures imbriquées complexes en s'appelant elle-même pour gérer des tâches similaires à chaque niveau d'imbrication.

Structures récursives

Les structures récursives sont des structures autoréférentielles où chaque partie est définie en termes de parties similaires. Les exemples courants incluent les organigrammes, les arbres binaires et plus encore.

Exemple : Organigramme

Prenons un organigramme où chaque manager peut avoir plusieurs subordonnés qui eux-mêmes peuvent être des managers.


Output appears here after Run.

Dans la fonction showOrgChart(employee), la récursivité est structurée pour visualiser un organigramme :

  • Cas de base : Similaire à l'exemple précédent de listFiles, le cas de base n'est pas explicitement indiqué comme un point d'arrêt conditionnel dans la fonction. Au lieu de cela, la récursivité se termine naturellement lorsqu'un employé n'a pas de subordonnés (employee.subordinates est un tableau vide). La méthode forEach n'exécute aucune itération lorsque le tableau est vide, donc aucun appel récursif supplémentaire n'est effectué.
  • Cas récursif : Le comportement récursif se produit avec la ligne employee.subordinates.forEach(showOrgChart). Cela signifie que chaque fois qu'un employé a un ou plusieurs subordonnés, la fonction est appelée de manière récursive pour chaque subordonné. Cette récursivité se poursuit dans la hiérarchie, enregistrant le nom et le poste de chaque subordonné, jusqu'à ce qu'elle atteigne des employés sans subordonnés (cas de base implicite).

Cette fonction fournit une démonstration claire de la manière dont la récursivité peut être utilisée pour naviguer et afficher des structures hiérarchiques telles que les organigrammes, où chaque niveau de récursivité pénètre plus profondément dans la structure.

Quand utiliser la récursivité ?

La récursivité est particulièrement utile lorsque vous pouvez décomposer une tâche en sous-tâches plus petites similaires à la tâche globale. Elle est puissante pour :

  • Trier des données (comme avec le tri fusion ou le tri rapide)
  • Parcourir des arbres et des graphes
  • Manipuler des données structurées complexes

Cependant, il est crucial de s'assurer que chaque appel récursif progresse vers le cas de base pour éviter la récursivité infinie et les erreurs de débordement de pile potentiels.

Conclusion

Comprendre la récursivité et la pile d'appels en JavaScript améliore votre capacité à résoudre des problèmes complexes de manière efficace et efficiente. Avec de la pratique, la récursivité peut devenir un outil précieux dans votre arsenal de programmation, vous permettant d'écrire un code plus propre et plus efficace. Qu'il s'agisse de parcourir des structures de données ou de mettre en œuvre des algorithmes complexes, maîtriser la récursivité élèvera incontestablement vos compétences en codage.

Pratique

Qu'est-ce que la récursivité en JavaScript et comment fonctionne-t-elle ?

Trouvez-vous cela utile?

Aperçu dual-run — comparez avec les routes Symfony en production.