Existe-t-il des heuristiques générales, des conseils, des astuces ou des paradigmes de conception courants qui peuvent être utilisés pour convertir un algorithme récursif en algorithme itératif? Je sais que cela peut être fait, je me demande s'il y a des pratiques qui méritent d'être gardées à l'esprit en le faisant.
Vous pouvez souvent conserver entièrement la structure d'origine d'un algorithme récursif, mais éviter la pile, en utilisant des appels de queue et en changeant en continuation-passant , comme suggéré par cette entrée de blog . (Je devrais vraiment cuisiner un meilleur exemple autonome.)
Une technique courante que j'utilise lorsque je suis en train de remplacer un algorithme récursif par un algorithme itératif est généralement d'utiliser une pile, en poussant les paramètres qui sont passés à la fonction récursive.
Consultez les articles suivants:
Une pratique courante est pour gérer une LIFO qui conserve une liste courante de ce qui "reste à faire", et pour gérer l'ensemble du processus dans une boucle while qui continue jusqu'à ce que la liste soit vide.
Avec ce modèle, ce qui serait un appel de récursivité dans le vrai modèle de récursivité est remplacé par
- une poussée du "contexte" de la tâche actuelle (partiellement terminée) sur la pile,
- une poussée de la nouvelle tâche (celle qui a provoqué la récursivité) sur la pile
- et pour "continuer" (c'est-à-dire passer au début de) la boucle while. Près de la tête de la boucle, la logique affiche le contexte le plus récemment inséré et commence à travailler sur cette base.
Effectivement cela "déplace" simplement les informations qui auraient autrement été conservées dans des cadres de pile imbriqués sur la pile "système", vers un conteneur de pile géré par l'application. Il s'agit cependant d'une amélioration, car ce conteneur de pile peut être alloué n'importe où (la limite de récursivité est généralement liée aux limites de la pile "système"). Par conséquent, essentiellement, le même travail est effectué, mais la gestion explicite d'une "pile" permet que cela se fasse au sein d'une construction à boucle unique plutôt que d'appels récursifs.
Souvent, la récursivité générale peut être remplacée par la récursivité de queue, en collectant des résultats partiels dans un accumulateur et en les transmettant avec l'appel récursif. La récursivité de la queue est essentiellement itérative, l'appel récursif peut être implémenté comme un saut.
Par exemple, la définition récursive générale standard de factorielle
factorial(n) = if n = 0 then 1 else n * factorial(n - 1)
peut être remplacé par
factorial(n) = f_iter(n, 1)
et
f_iter(n, a) = if n = 0 then a else f_iter(n - 1, n * a)
qui est récursif de queue. C'est la même chose que
a = 1;
while (n != 0) {
a = n * a;
n = n - 1;
}
return a;
Je commence généralement par le cas de base (chaque fonction récursive en a une) et je recule, stockant les résultats dans un cache (tableau ou table de hachage) si nécessaire.
Votre fonction récursive résout un problème en résolvant des sous-problèmes plus petits et en les utilisant pour résoudre l'instance du problème plus grande. Chaque sous-problème est également décomposé davantage et ainsi de suite jusqu'à ce que le sous-problème soit si petit que la solution soit triviale (c'est-à-dire le cas de base).
L'idée est de commencer par le cas de base (ou les cas de base) et de l'utiliser pour construire la solution pour les cas plus grands, puis de les utiliser pour construire des cas encore plus grands et ainsi de suite, jusqu'à ce que tout le problème soit résolu. Cela ne nécessite pas de pile et peut être effectué avec des boucles.
Un exemple simple (en Python):
#recursive version
def fib(n):
if n==0 or n==1:
return n
else:
return fib(n-1)+fib(n-2)
#iterative version
def fib2(n):
if n==0 or n==1:
return n
prev1,prev2=0,1 # start from the base case
for i in xrange(n):
cur=prev1+prev2 #build the solution for the next case using the previous solutions
prev1,prev2=cur,prev1
return cur
Jetez un œil à ces liens pour des exemples de performances
Récursion VS Itération (Boucle): Comparaison Vitesse & Mémoire
et
Remplacer la récursivité par l'itération
et
Q: La version récursive est-elle généralement plus rapide? R: Non - c'est généralement plus lent (en raison des frais généraux liés au maintien de la pile)
Q: Does the recursive version usually use less memory? A: No -- it usually uses more memory (for the stack). Q: Then why use recursion?? A: Sometimes it is much simpler to write the recursive version (but
nous devrons attendre d'avoir discuté des arbres pour voir de très bons exemples ...)
Un modèle est Tail Recursion :
Un appel de fonction est dit récursif de queue s'il n'y a rien à faire après le retour de la fonction, sauf renvoyer sa valeur.
Wiki .