web-dev-qa-db-fra.com

Chaque récursivité peut-elle être convertie en itération?

A reddit thread a soulevé une question apparemment intéressante:

Les fonctions récursives de queue peuvent être facilement converties en fonctions itératives. Les autres peuvent être transformés en utilisant une pile explicite. Est-ce que chaque récursion peut être transformée en itération?

L'exemple (compteur?) Dans le post est la paire:

(define (num-ways x y)
  (case ((= x 0) 1)
        ((= y 0) 1)
        (num-ways2 x y) ))

(define (num-ways2 x y)
  (+ (num-ways (- x 1) y)
     (num-ways x (- y 1))
174
Tordek

Pouvez-vous toujours transformer une fonction récursive en une fonction itérative? Oui, tout à fait, et la thèse de Church-Turing le prouve si la mémoire le permet. En termes simples, il est indiqué que ce qui est calculable par des fonctions récursives l'est par un modèle itératif (tel que la machine de Turing) et inversement. La thèse ne vous dit pas exactement comment faire la conversion, mais elle dit que c'est définitivement possible.

Dans de nombreux cas, la conversion d'une fonction récursive est facile. Knuth propose plusieurs techniques dans "L'Art de la programmation informatique". Et souvent, une chose calculée de manière récursive peut être calculée par une approche complètement différente en moins de temps et d'espace. L'exemple classique en est les nombres de Fibonacci ou leurs séquences. Vous avez sûrement rencontré ce problème dans votre plan de formation.

Sur le revers de la médaille, nous pouvons certainement imaginer un système de programmation assez avancé pour traiter une définition récursive d’une formule comme une invitation à mémoriser les résultats antérieurs, offrant ainsi l’avantage de la rapidité sans la peine de dire à l'ordinateur les étapes à suivre. suivre dans le calcul d'une formule avec une définition récursive. Dijkstra a presque certainement imaginé un tel système. Il a passé beaucoup de temps à essayer de séparer l'implémentation de la sémantique d'un langage de programmation. Encore une fois, ses langages de programmation non déterministes et multitraitement sont dans une ligue au-dessus du programmeur professionnel pratiquant.

En dernière analyse, de nombreuses fonctions sont tout simplement plus faciles à comprendre, à lire et à écrire sous forme récursive. À moins d'une raison impérieuse, vous ne devriez probablement pas (manuellement) convertir ces fonctions en un algorithme explicitement itératif. Votre ordinateur va gérer ce travail correctement.

Je peux voir une raison impérieuse. Supposons que vous ayez un système prototype dans un langage de très haut niveau tel que [enfiler des sous-vêtements en amiante] Scheme, LISP, Haskell, OCaml, Perl ou Pascal. Supposons que les conditions sont telles que vous avez besoin d'une implémentation en C ou en Java. (Peut-être que c'est de la politique.) Alors vous pourriez certainement avoir des fonctions écrites de manière récursive, mais qui, traduites littéralement, feraient exploser votre système d'exécution. Par exemple, une récursion de queue infinie est possible dans Scheme, mais le même idiome pose un problème pour les environnements C existants. Un autre exemple est l'utilisation de fonctions imbriquées lexicalement et d'une portée statique, que Pascal prend en charge mais que C ne prend pas.

Dans ces circonstances, vous pouvez essayer de vaincre la résistance politique à la langue d'origine. Vous pourriez vous retrouver à réimplémenter mal LISP, comme dans la dixième loi de Greenspun. Ou vous pouvez simplement trouver une approche complètement différente de la solution. Mais de toute façon, il y a sûrement un moyen.

175
Ian

Est-il toujours possible d'écrire une forme non récursive pour chaque fonction récursive?

Oui. Une preuve formelle simple consiste à montrer que le calcul µ récursion et un calcul non récursif tel que GOTO sont tous deux complets de Turing. Puisque tous les calculs complets de Turing sont strictement équivalents en puissance expressive, toutes les fonctions récursives peuvent être implémentées par le calcul non-récursif de Turing-complet.

Malheureusement, je suis incapable de trouver une bonne définition formelle de GOTO en ligne, voici donc celle-ci:

Un programme GOTO est une séquence de commandes [~ # ~] p [~ # ~] exécutées sur un machine enregistreuse telle que [~ # ~] p [~ # ~] est l'un des éléments suivants:

  • HALT, qui arrête l'exécution
  • r = r + 1r est un registre
  • r = r – 1r est un registre
  • GOTO xx est une étiquette
  • IF r ≠ 0 GOTO xr est un registre quelconque et x est une étiquette
  • Une étiquette, suivie de l’une des commandes ci-dessus.

Cependant, les conversions entre fonctions récursives et non récursives ne sont pas toujours anodines (sauf par une réimplémentation manuelle insensée de la pile d’appels).

Pour plus d'informations, voir cette réponse .

39
Konrad Rudolph

La récursivité est implémentée sous forme de piles ou de constructions similaires dans les interpréteurs ou les compilateurs réels. Donc, vous pouvez certainement convertir une fonction récursive en une contrepartie itérative parce que c'est comme ça que ça se fait toujours (si automatiquement). Vous ne ferez que dupliquer le travail du compilateur de manière ad hoc et probablement d'une manière très laide et inefficace.

27
Vinko Vrsalovic

En gros, oui, vous devez essentiellement remplacer les appels de méthode (qui implicitement Push state sur la pile) par des poussées de pile explicites pour se souvenir de la position de "l'appel précédent", puis exécuter la "méthode appelée". au lieu.

J'imagine que la combinaison d'une boucle, d'une pile et d'une machine à états pourrait être utilisée pour tous les scénarios en simulant fondamentalement les appels de méthode. Que ce soit ou non "meilleur" (soit plus rapide, soit plus efficace dans un certain sens) n'est pas vraiment possible de le dire en général.

12
jerryjvl
  • Le flux d'exécution de fonction récursive peut être représenté sous forme d'arborescence.

  • La même logique peut être réalisée par une boucle, qui utilise une structure de données pour parcourir cette arborescence.

  • Le parcours en profondeur d'abord peut être effectué à l'aide d'une pile, le parcours en largeur d'abord à l'aide d'une file d'attente.

Donc, la réponse est oui. Pourquoi: https://stackoverflow.com/a/531721/2128327 .

Une récursion peut-elle être faite en une seule boucle? Oui parce que

une machine Turing fait tout ce qu'elle fait en exécutant une seule boucle:

  1. aller chercher une instruction,
  2. évaluez-le,
  3. aller à 1.
9
Khaled.K

Oui, utiliser explicitement une pile (mais la récursion est beaucoup plus agréable à lire, à mon humble avis).

6
dfa

Oui, il est toujours possible d'écrire une version non récursive. La solution triviale consiste à utiliser une structure de données de pile et à simuler l'exécution récursive.

6
Heinzi

En principe, il est toujours possible de supprimer la récursivité et de la remplacer par une itération dans un langage dont l'état est infini, à la fois pour les structures de données et pour la pile d'appels. Ceci est une conséquence fondamentale de la thèse de Church-Turing.

Étant donné un langage de programmation réel, la réponse n’est pas aussi évidente. Le problème est qu’il est tout à fait possible d’avoir un langage dans lequel la quantité de mémoire pouvant être allouée dans le programme est limitée, mais dans lequel la quantité de pile d’appels pouvant être utilisée est illimitée (C 32 bits où l’adresse des variables de la pile n'est pas accessible). Dans ce cas, la récursivité est plus puissante simplement parce qu'elle dispose de plus de mémoire qu'elle peut utiliser. il n'y a pas assez de mémoire explicitement allouable pour émuler la pile d'appels. Pour une discussion détaillée à ce sujet, voir cette discussion .

3
Zayenz

Parfois, remplacer la récursion est beaucoup plus facile que cela. La récursivité était la chose à la mode enseignée dans CS dans les années 1990, et donc beaucoup de développeurs moyens de cette époque pensaient que si vous résolviez quelque chose avec la récursivité, c'était une meilleure solution. Ainsi, ils utiliseraient la récursivité au lieu de boucler en arrière pour inverser l'ordre, ou des choses stupides comme celle-là. Donc, parfois, supprimer l’exercice de récursivité est un type d’exercice "duh, c’est évident".

C'est moins un problème maintenant, car la mode a évolué vers d'autres technologies.

1
Matthias Wandel

Toutes les fonctions calculables peuvent être calculées par Turing Machines. Par conséquent, les systèmes récursifs et les machines de Turing (systèmes itératifs) sont équivalents.

1
JOBBINE

Consultez les entrées suivantes sur wikipedia, vous pouvez les utiliser comme point de départ pour trouver une réponse complète à votre question.

Suit un paragraphe qui peut vous donner un indice sur le point de départ:

Résoudre une relation de récurrence signifie obtenir une solution de forme fermée : une fonction non récursive de n.

Regardez aussi le dernier paragraphe de cette entrée .

0
Alberto Zaccagni

Il est possible de convertir n'importe quel algorithme récursif en un algorithme non récursif, mais la logique est souvent beaucoup plus complexe et nécessite de ce fait l'utilisation d'une pile. En fait, la récursivité utilise elle-même une pile: la pile de fonctions.

Plus de détails: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions

0
Teoman shipahi

L'élimination de la récursivité est un problème complexe et est réalisable dans des circonstances bien définies.

Les cas ci-dessous sont parmi les plus faciles:

0
Nick Dandoulakis

Je dirais que oui - un appel de fonction n’est autre qu’un goto et une opération de pile (en gros). Tout ce que vous avez à faire est d’imiter la pile construite lors de l’appel de fonctions et de faire quelque chose de similaire en tant que goto (vous pouvez imiter les gotos avec des langues qui n’ont pas explicitement ce mot-clé également).

0
sfussenegger

Outre la pile explicite, l'utilisation d'un trampoline est un autre moyen de convertir la récursivité en itération.

Ici, les fonctions renvoient soit le résultat final, soit une fermeture de l'appel de fonction qu'il aurait sinon effectué. Ensuite, la fonction d'initiation (trampoline) continue à invoquer les fermetures renvoyées jusqu'à ce que le résultat final soit atteint.

Cette approche fonctionne pour les fonctions mutuellement récursives, mais je crains que cela ne fonctionne que pour les appels finaux.

http://en.wikipedia.org/wiki/Trampoline_ (ordinateurs)

0
Chris Vest