Une boucle while est-elle intrinsèquement une récursivité?
37
Je me suis demandé si une boucle while est intrinsèquement une récursivité?
Je pense que c’est parce qu’une boucle while peut être vue comme une fonction qui s’appelle à la fin. Si ce n'est pas la récursivité, alors quelle est la différence?
Vous pouvez convertir la récursivité en itération et inversement, oui. Cela ne signifie pas qu'ils sont identiques, ils ont juste les mêmes capacités. Il y a des moments où la récursion est plus naturelle et il y a des moments où l'itération est plus naturelle.
Polygnome
18
@MooingDuck Vous pouvez prouver par induction que toute récursivité peut être écrite sous forme d'itération et inversement. Oui, cela aura l'air très différent, mais vous pouvez le faire quand même.
Polygnome
6
Que veut dire intrinsèquement la même chose ici? En programmation, utiliser la récursivité signifie une chose spécifique, différente de l'itération (boucles). En CS, lorsque vous vous rapprochez du côté mathématique théorique, ces choses commencent à signifier des choses un peu différentes.
Hyde
3
@MooingDuck La conversion de récursive en itérative est en réalité assez triviale. Vous conservez simplement une pile de paramètres d’appel de fonction et une pile de résultats pour les appels de fonction. Vous remplacez les appels récursifs en ajoutant les paramètres à la pile d'appels. Bien sûr, toute la manipulation de la pile casse un peu la structure de l'algorithme, mais une fois que vous avez compris, il est assez facile de voir que le code fait la même chose. Fondamentalement, vous écrivez explicitement la pile d'appels implicite dans les définitions récursives.
Bakuriu
Réponses:
116
Les boucles ne sont vraiment pas récursives. En fait, ils sont le meilleur exemple du mécanisme opposé : l' itération .
L'intérêt de la récursivité est qu'un élément du traitement appelle une autre instance d'elle-même. Le mécanisme de contrôle de boucle simple saute jusqu'au point où il a commencé.
Sauter dans le code et appeler un autre bloc de code sont des opérations différentes. Par exemple, lorsque vous sautez au début de la boucle, la variable de contrôle de la boucle a toujours la même valeur qu’avant le saut. Mais si vous appelez une autre instance de la routine dans laquelle vous vous trouvez, la nouvelle instance dispose de nouvelles copies non liées de toutes ses variables. Effectivement, une variable peut avoir une valeur au premier niveau de traitement et une autre valeur à un niveau inférieur.
Cette fonctionnalité est cruciale pour le fonctionnement de nombreux algorithmes récursifs. C'est pourquoi vous ne pouvez pas émuler la récursivité par itération sans gérer également une pile de trames appelées qui garde toutes ces valeurs.
@ Giorgio C'est peut-être vrai, mais c'est un commentaire sur une revendication que la réponse n'a pas apportée. "Arbitrairement" n'est pas présent dans cette réponse et changerait considérablement le sens.
hvd
12
@hvd En principe, la récursion de la queue est une récursion complète comme les autres. Les compilateurs intelligents peuvent optimiser la partie "créer un nouveau cadre de pile" de manière à ce que le code généré ressemble beaucoup à une boucle, mais les concepts dont nous parlons s'appliquent au niveau du code source. Je considère que la forme d'un algorithme en tant que code source est la chose la plus importante. Je l'appellerais toujours récursion.
Kilian Foth
15
@Giorgio "c'est exactement ce que fait la récursivité: s'appeler avec de nouveaux arguments" - à l'exception de l'appel. Et les arguments.
Hobbs
12
@Giorgio Vous utilisez différentes définitions de mots que la plupart des gens ici. Les mots, vous savez, sont la base de la communication. Ce sont des programmeurs, pas CS Stack Exchange. Si nous utilisions des mots comme "argument", "appel", "fonction", etc. comme vous le suggérez, il serait impossible de discuter du code réel.
Hyde
6
@Giorgio Je regarde le concept abstrait. Il y a le concept de récurrence et le concept de boucle. Ce sont des concepts différents . Hobbs a 100% raison de dire qu'il n'y a pas d'argument et qu'il n'y a pas d'appel. Ils sont fondamentalement et abstraitement différents. Et c'est bien parce qu'ils résolvent différents problèmes. D'autre part, vous étudiez comment vous pourriez implémenter des boucles lorsque votre seul outil est la récursivité. Ironiquement, vous dites à Hobbs de cesser de penser à la mise en œuvre et de commencer à regarder les concepts lorsque votre méthodologie est celle qui nécessite vraiment une réévaluation.
CorsiKa
37
Dire que X est intrinsèquement Y n'a de sens que si vous avez un système (formel) à l'esprit que vous exprimez X en. Si vous définissez la sémantique de whilelambda calcul, vous pouvez mentionner récursion *; si vous le définissez en termes de machine à enregistrer, vous ne le ferez probablement pas.
Dans les deux cas, les gens ne vous comprendront probablement pas si vous appelez une fonction récursive simplement parce qu'elle contient une boucle while.
* Bien que peut-être seulement indirectement, par exemple si vous le définissez en termes de fold.
Pour être juste, la fonction n'est récursive dans aucune définition. Il ne contient qu'un élément récursif, la boucle.
Luaan
@Luaan: Absolument, mais étant donné que dans les langues avec une whileconstruction, la récursivité est généralement une propriété de fonctions, je ne vois rien d'autre qui puisse être décrit comme "récursif" dans ce contexte.
Anton Golov
36
Cela dépend de votre point de vue.
Si vous examinez la théorie de la calculabilité , l'itération et la récursivité sont également expressives . Cela signifie que vous pouvez écrire une fonction qui calcule quelque chose, et que vous le fassiez de manière récursive ou itérative, vous pourrez choisir les deux approches. Il n'y a rien que vous puissiez calculer récursivement qui ne puisse être calculé de manière itérative et inversement (bien que le fonctionnement interne du programme puisse être différent).
De nombreux langages de programmation ne traitent pas la récursivité et l'itération de la même manière et pour une bonne raison. En règle générale , la récursivité signifie que le langage / le compilateur gère la pile d'appels et l'itération signifie que vous devrez peut-être gérer vous-même la pile.
Cependant, il existe des langages - en particulier des langages fonctionnels - dans lesquels des choses comme les boucles (pour, tandis que) ne sont en effet que du sucre syntaxique pour la récursion et sont implémentées en coulisse de cette façon. C'est souvent souhaitable dans les langages fonctionnels, car ils n'ont généralement pas le concept de bouclage, et l'ajouter rendrait leur calcul plus complexe, pour une raison peu pratique.
Donc non, ils ne sont pas intrinsèquement les mêmes . Ils sont également expressifs , ce qui signifie que vous ne pouvez pas calculer quelque chose de manière itérative, ni de manière récursive ni inversement, mais c'est à peu près tout, dans le cas général (selon la thèse de Church-Turing).
Notez que nous parlons ici de programmes récursifs . Il existe d'autres formes de récursivité, par exemple dans les structures de données (par exemple, les arbres).
Si vous le regardez du point de vue de la mise en œuvre , récursivité et itération ne sont pratiquement plus les mêmes. La récursivité crée un nouveau cadre de pile pour chaque appel. Chaque étape de la récursivité est autonome, les arguments du calcul étant obtenus par l'appelé (lui-même).
Les boucles d’autre part ne créent pas de cadres d’appel. Pour eux, le contexte n'est pas préservé à chaque étape. Pour la boucle, le programme revient simplement au début de la boucle jusqu'à ce que la condition de la boucle échoue.
C'est très important à savoir, car cela peut faire des différences assez radicales dans le monde réel. Pour la récursivité, le contexte entier doit être sauvegardé à chaque appel. Pour l'itération, vous avez un contrôle précis sur les variables en mémoire et sur ce qui est enregistré.
Si vous le regardez ainsi, vous voyez rapidement que pour la plupart des langues, l'itération et la récursivité sont fondamentalement différentes et ont des propriétés différentes. Selon la situation, certaines propriétés sont plus souhaitables que d’autres.
Récursivité peut rendre les programmes plus simples et plus faciles à tester et la preuve . La conversion d'une récursion en itération rend généralement le code plus complexe, ce qui augmente les risques d'échec. D'autre part, la conversion en itération et la réduction du nombre de trames de la pile d'appels peuvent économiser beaucoup de mémoire.
Un langage avec des variables locales et une récursivité, mais aucun tableau ne peut exécuter des tâches qui ne pourraient pas être exécutées par un langage itératif avec des variables locales et aucun tableau. Par exemple, indiquez si une entrée contient une chaîne alphanumérique de longueur inconnue suivie d'un blanc, puis des caractères de la chaîne d'origine dans l'ordre inverse.
Supercat
3
Tant que la langue est complète, elle le peut. Un tableau peut facilement être remplacé par une liste (double) chaînée, par exemple. Parler d'itération ou de récurrence et de leur égalité n'a de sens que si vous comparez deux langues complètes.
Polygnome
Je voulais dire n'avoir rien d'autre que de simples variables statiques ou automatiques, c'est-à-dire ne pas être complet de Turing. Un langage purement itératif serait limité aux tâches pouvant être accomplies via un simple automate fini déterministe, tandis qu'un langage récursif ajouterait la capacité d'effectuer des tâches nécessitant au moins un automate fini déterministe.
Supercat
1
Si la langue n’est pas complète, il est d’abord inutile. Les DFA ne peuvent effectuer ni itération arbitraire ni récursion.
Polygnome
2
Aucune mise en œuvre n'est réellement complète par Turing, et les langues qui ne le sont pas peuvent être utiles à de nombreuses fins. Tout programme pouvant être exécuté avec un nombre fini de variables avec une plage finie peut être adapté à un DFA où chaque combinaison de valeurs possible est un état discret.
Supercat
12
La différence réside dans la pile implicite et la sémantique.
Une boucle while qui "s'appelle elle-même à la fin" n'a pas de pile à explorer lorsque c'est fait. C'est la dernière itération qui définit l'état final.
La récursivité ne peut cependant pas être réalisée sans cette pile implicite qui se souvient de l’état du travail effectué auparavant.
Il est vrai que vous pouvez résoudre tout problème de récursion par itération si vous lui donnez explicitement accès à une pile. Mais le faire de cette façon n'est pas la même.
La différence sémantique tient au fait que regarder du code récursif transmet une idée de manière complètement différente du code itératif. Le code itératif fait les choses une étape à la fois. Il accepte n'importe quel état d'avant et ne travaille que pour créer l'état suivant.
Le code récursif divise un problème en fractales. Cette petite partie ressemble à cette grosse partie pour que nous puissions en faire un peu et le faire de la même manière. C'est une façon différente de penser aux problèmes. C'est très puissant et il faut s'y habituer. On peut dire beaucoup de choses en quelques lignes. Vous ne pouvez tout simplement pas sortir de cette boucle, même s'il a accès à une pile.
Je pense que "pile implicite" est trompeuse. La récursivité fait partie de la sémantique d'une langue et non d'un détail d'implémentation. (Certes, la plupart des langues prenant en charge la récursivité utilisent une pile d’appels; d’abord, certaines de ces langues ne le font pas, et deuxièmement, même dans les langues qui le font, tous les appels récursifs ne s’ajoutent pas nécessairement à la pile des appels, car de nombreuses langues prennent en charge des optimisations telles que comme l' élimination des appels récursifs .) Comprendre la mise en œuvre habituelle / simple peut être utile pour obtenir une poignée sur l'abstraction, mais vous ne devriez pas vous tromper en pensant que c'est toute l'histoire.
ruakh
2
@ruakh Je dirais qu'une fonction qui s'exécute dans un espace constant en utilisant l'élimination des appels de queue est vraiment une boucle. Ici, la pile n'est pas le détail de l'implémentation, c'est l'abstraction qui vous permet d'accumuler différents états pour différents niveaux de récursivité.
Cimbali
@ruakh: tout état dans un seul appel récursif sera stocké sur une pile implicite, sauf si la récursivité peut être convertie par le compilateur en une boucle itérative. L'élimination de l'appel final est un détail de l'implémentation, celui dont vous devez être conscient si vous souhaitez réorganiser votre fonction pour qu'elle soit récursive. En outre, "peu de langues de ce type ne le font pas" - pouvez-vous donner un exemple de langues qui n'ont pas besoin d'une pile pour les appels récursifs?
@ruakh: CPS crée par lui-même la même pile implicite. Il doit donc s'appuyer sur l'élimination des appels en bout de ligne pour donner un sens (ce qu'il rend trivial en raison de la manière dont il a été construit). Même l'article sur wikipedia auquel vous êtes lié dit la même chose: utiliser CPS sans optimisation d'appel final (TCO) entraînera une croissance potentielle non seulement de la suite construite pendant la récursivité, mais également de la pile d'appels .
Groo
7
Tout dépend de votre utilisation du terme intrinsèquement . Au niveau du langage de programmation, ils sont syntaxiquement et sémantiquement différents, et leurs performances et leur utilisation de la mémoire sont très différentes. Mais si vous creusez suffisamment dans la théorie, ils peuvent être définis en termes les uns des autres, et sont donc "les mêmes" dans un sens théorique.
La vraie question est la suivante: quand faut-il faire la distinction entre itération (boucles) et récursion, et quand est-il utile de penser que c'est la même chose? La réponse est que lors de la programmation (par opposition à l'écriture d'épreuves mathématiques), il est important de faire la distinction entre itération et récursivité.
La récursivité crée un nouveau cadre de pile, c'est-à-dire un nouvel ensemble de variables locales pour chaque appel. Cela entraîne une surcharge et prend de la place sur la pile, ce qui signifie qu'une récursion suffisamment profonde peut déborder de la pile, ce qui provoque le blocage du programme. D'autre part, l'itération ne modifie que les variables existantes, elle est donc généralement plus rapide et ne nécessite qu'une quantité de mémoire constante. C'est donc une distinction très importante pour un développeur!
Dans les langues avec récursivité d'appels (généralement des langages fonctionnels), le compilateur peut optimiser les appels récursifs de manière à ne prendre qu'une quantité de mémoire constante. Dans ces langues, la distinction importante n’est pas l’itération contre la récursivité, mais bien la version non-tail-call-récursive-call-récursion.
En bout de ligne: Vous devez être capable de faire la différence, sinon votre programme plantera.
whileles boucles sont une forme de récursivité, voir par exemple la réponse acceptée à cette question . Ils correspondent à l'opérateur μ dans la théorie de la calculabilité (voir par exemple ici ).
Toutes les variantes de forboucles qui itèrent sur une plage de nombres, une collection finie, un tableau, etc., correspondent à une récursion primitive, voir par exemple ici et ici . Notez que les forboucles de C, C ++, Java, etc. constituent en réalité un sucre syntaxique pour une whileboucle et que, par conséquent, elles ne correspondent pas à la récursion primitive. La forboucle Pascal est un exemple de récursion primitive.
Une différence importante est que la récursion primitive se termine toujours, alors que la récursion généralisée ( whileboucles) peut ne pas se terminer.
MODIFIER
Quelques clarifications concernant les commentaires et autres réponses. "La récursivité se produit lorsqu'une chose est définie en termes d'elle-même ou de son type." (voir wikipedia ). Alors,
Une boucle while est-elle intrinsèquement une récursivité?
Puisque vous pouvez définir une whileboucle en tant que telle
while p do c := if p then (c; while p do c))
alors, oui , une whileboucle est une forme de récursion. Les fonctions récursives sont une autre forme de récursivité (un autre exemple de définition récursive). Les listes et les arbres sont d'autres formes de récursivité.
Une autre question implicitement supposée par de nombreuses réponses et commentaires est
Les boucles while et les fonctions récursives sont-elles équivalentes?
La réponse à cette question est non : une whileboucle correspond à une fonction queue récursive, où les variables auxquelles la boucle a accès correspondent aux arguments de la fonction récursive implicite, mais, comme d'autres l'ont souligné, des fonctions non récursives ne peut pas être modélisé par une whileboucle sans utiliser une pile supplémentaire.
Ainsi, le fait "qu'une whileboucle soit une forme de récursion" ne contredit pas le fait que "certaines fonctions récursives ne peuvent pas être exprimées par une whileboucle".
@morbidCode: la récursion primitive et la récursion sont des formes de récursion avec des restrictions spécifiques (ou leur absence), étudiées par exemple dans la théorie de la calculabilité. En fait, un langage avec seulement une FORboucle peut calculer exactement toutes les fonctions récursives primitives, et un langage avec seulement une WHILEboucle peut calculer exactement toutes les fonctions µ-récursives (et il s'avère que les fonctions µ-récursives sont exactement ces fonctions une machine de Turing peut calculer). Bref, la récursion primitive et la récursion sont des termes techniques de la théorie mathématique / calculabilité.
Jörg W Mittag
2
Je pensais que "récursion" impliquait une fonction qui s'appelait elle-même, entraînant le transfert de l'état d'exécution actuel vers la pile, etc. par conséquent, la plupart des machines ont une limite pratique sur le nombre de niveaux que vous pouvez appeler. Les boucles While n'ont pas de telles limites car elles utilisent en interne quelque chose comme un "JMP" et n'utilisent pas la pile. Juste ma compréhension, pourrait être faux.
Jay
13
Cette réponse utilise une définition complètement différente du mot "récursif" par rapport au mot utilisé par le PO, et est donc très trompeuse.
Mooing Duck
2
@DavidGrinberg: Quote: "les boucles C, C ++, Java for ne sont pas un exemple de récursion primitive. Récursion primitive signifie que le nombre maximal d'itérations / profondeur de récursivité est fixé avant le démarrage de la boucle." Giorgio parle de primitives de la calculabilité . Non lié aux langages de programmation.
Mooing Duck
3
Je suis d'accord avec Mooing Duck. Bien que la théorie de la calculabilité puisse être intéressante dans le CS théorique, je pense que tout le monde convient que le PO parlait du concept de langage de programmation.
Voo le
2
Un appel final (ou appel récursif) est implémenté exactement comme un "goto avec arguments" (sans pousser de trame d'appel supplémentaire sur la pile d'appels ) et dans certains langages fonctionnels (notamment Ocaml) est la méthode habituelle de bouclage.
Ainsi, une boucle while (dans les langues qui les ont) peut être vue comme se terminant par un appel de queue à son corps (ou à son test de la tête).
De même, les appels récursifs ordinaires (sans appel final) peuvent être simulés par des boucles (en utilisant une pile).
Il est vrai que la récursion et la boucle while sans bornes sont équivalentes en termes d’expressivité de calcul. Autrement dit, tout programme écrit de manière récursive peut être réécrit en un programme équivalent en utilisant des boucles, et inversement. Les deux approches sont complètes , c'est-à-dire qu'elles peuvent être utilisées pour calculer toute fonction calculable.
La différence fondamentale en termes de programmation est que la récursivité vous permet d'utiliser les données stockées dans la pile d'appels. Pour illustrer cela, supposons que vous souhaitiez imprimer les éléments d'une liste à lien unique en utilisant une boucle ou une récursivité. Je vais utiliser C pour l'exemple de code:
Pour la fonction de boucle cependant, nous avons un problème. Notre liste est liée individuellement et ne peut donc être parcourue que vers l’avant. Mais comme nous imprimons en sens inverse, nous devons commencer à imprimer le dernier élément. Une fois que nous avons atteint le dernier élément, nous ne pouvons plus revenir à l’avant-dernier élément.
Nous devons donc soit procéder à de nombreuses ré-traverses, soit créer une structure de données auxiliaire qui assure le suivi des éléments visités et à partir de laquelle nous pouvons ensuite imprimer efficacement.
Pourquoi n'avons-nous pas ce problème de récursivité? Parce que dans la récursivité, nous avons déjà une structure de données auxiliaire en place: la pile d'appels de fonction.
Comme la récursivité nous permet de revenir à l'appel précédent de l'appel récursif, toutes les variables locales et l'état de cet appel restant intacts, nous bénéficions d'une certaine souplesse qu'il serait fastidieux de modéliser dans le cas itératif.
Bien sûr, la deuxième fonction récursive n'est pas récursive: il est beaucoup plus difficile d'optimiser l'espace, car vous ne pouvez pas utiliser TCO pour réutiliser la pile. L'implémentation d'une liste doublement chaînée rendrait les deux algorithmes triviaux dans les deux sens, au détriment de l'espace d'un pointeur / référence par élément.
Baldrickk
@Baldrickk Ce qui est amusant avec la récursion de la queue, c'est que vous vous retrouvez avec une version beaucoup plus proche de celle de la version en boucle, car elle supprime à nouveau votre capacité à stocker l'état sur la pile d'appels. Une liste doublement chaînée le résoudrait, mais la refonte de la structure de données n’est souvent pas une option lorsque vous rencontrez ce problème. Bien que l'exemple présenté ici soit quelque peu artificiellement contraint, il illustre un motif qui apparaît fréquemment dans les langages fonctionnels dans le contexte de types algébriques récursifs.
ComicSansMS
Mon point était que si vous rencontrez ce problème, c'est plus dû à un manque de conception fonctionnelle qu'à la construction de langage que vous utilisez pour l'implémenter, et chaque choix a ses propres aspects positifs et négatifs :)
Baldrickk
0
Les boucles sont une forme spéciale de récursivité permettant d’accomplir une tâche spécifique (principalement une itération). On peut implémenter une boucle dans un style récursif avec la même performance [1] dans plusieurs langues. et dans le SICP [2], vous pouvez voir que les boucles sont décrites comme "sucre syntastique". Dans la plupart des langages de programmation impératifs, les blocs for et while utilisent la même portée que leur fonction parent. Néanmoins, dans la plupart des langages de programmation fonctionnels, il n’existe pas de boucles for, ni de boucles, car elles ne sont pas nécessaires.
La raison pour laquelle les langages impératifs ont des boucles for / while est qu’ils gèrent les états en les migrant. Mais en réalité, si vous regardez sous un angle différent, si vous pensez à un bloc while comme une fonction elle-même, prenez paramètre, traitez-le et renvoyez un nouvel état - qui pourrait aussi bien être l’appel de la même fonction avec des paramètres différents -. peut penser à la boucle comme une récursion.
Le monde pourrait également être défini comme mutable ou immuable. si nous définissons le monde comme un ensemble de règles et appelons une fonction ultime qui prend toutes les règles et l’état actuel en tant que paramètres et renvoie le nouvel état en fonction de ces paramètres, qui a la même fonctionnalité (génère le suivant dans le même état) On pourrait aussi bien dire que c’est une récursion et une boucle.
dans l'exemple suivant, la vie est la fonction prend deux paramètres "règles" et "état", et nouvel état sera construit dans la prochaine tick de temps.
life rules state = life rules new_state
where new_state = construct_state_in_time rules state
[1]: L’optimisation d’appel en aval est une optimisation courante dans les langages de programmation fonctionnels consistant à utiliser la pile de fonctions existante dans des appels récursifs au lieu de créer un nouveau.
@Giorgio Ce n'est pas mon avis négatif, mais c'est juste une supposition: je pense que la plupart des programmeurs pensent que la récursivité implique un appel de fonction récursif, car c'est bien ce que la récursion est, une fonction qui s'appelle. Dans une boucle, il n'y a pas d'appel de fonction récursif. Donc, dire qu'une boucle sans appel de fonction récursive est une forme spéciale de récursivité serait totalement faux, si on utilisait cette définition.
Hyde
1
Eh bien, peut-être que, d'un point de vue plus abstrait, les choses semblent différentes, sont en réalité les mêmes sur le plan conceptuel. Je trouve assez décourageant et triste de penser que les réponses négatives soient obtenues simplement parce qu'elles ne correspondent pas à leurs attentes au lieu de les prendre comme une chance d'apprendre quelque chose. Toutes les réponses qui essaient de dire: "Hé, regarde, ces choses-là semblent différentes, mais sont en réalité les mêmes à un niveau plus abstrait" ont été rejetées.
Giorgio
3
@Georgio: Le but de ce site est d'obtenir des réponses aux questions. Les réponses utiles et correctes méritent des votes positifs. Les réponses confuses et inutiles méritent des votes négatifs. Une réponse qui utilise subtilement une définition différente d'un terme commun sans indiquer clairement quelle définition est utilisée est source de confusion et inutile. Des réponses qui n'ont de sens que si vous connaissez déjà la réponse, pour ainsi dire, ne sont pas utiles et ne servent qu'à montrer aux rédacteurs une compréhension supérieure de la terminologie.
JacquesB
2
@JacquesB: "Des réponses qui n'ont de sens que si vous connaissez déjà la réponse, pour ainsi dire, ne sont d'aucune utilité ...": On peut également en dire autant des réponses qui confirment seulement ce que le lecteur sait ou pense savoir. Si une réponse introduit une terminologie qui n'est pas claire, il est possible d'écrire des commentaires pour demander plus de détails avant de voter à la baisse.
Giorgio
4
Les boucles ne sont pas une forme spéciale de récursivité. Regardez la théorie de la calculabilité et par exemple le langage théorique WHILE et le µ-calcul. Oui, certaines langues utilisent des boucles comme le sucre syntaxique pour réellement utiliser récursivité dans les coulisses, mais ils peuvent le faire parce récursivité et itération sont tout aussi expressifs , non pas parce qu'ils sont les mêmes.
Polygnome
-1
Une boucle while est différente de la récursivité.
Lorsqu'une fonction est appelée, les opérations suivantes ont lieu:
Un cadre de pile est ajouté à la pile.
Le pointeur de code se déplace au début de la fonction.
Quand une boucle while est à la fin, ceci se produit:
Une condition demande si quelque chose est vrai.
Si c'est le cas, le code saute à un point.
En général, la boucle while s'apparente au pseudocode suivant:
if (x)
{
Jump_to(y);
}
Le plus important de tous, la récursivité et les boucles ont différentes représentations du code d'assemblage et des représentations du code machine. Cela signifie qu'ils ne sont pas les mêmes. Ils peuvent avoir les mêmes résultats, mais le code machine différent prouve qu'ils ne sont pas 100% identiques.
Vous parlez de l'implémentation d'un appel de procédure et d'une boucle while et, comme ils sont implémentés différemment, vous concluez qu'ils sont différents. Cependant, conceptuellement, ils sont très similaires.
Giorgio
1
Selon le compilateur, un appel de récursion intégré optimisé peut très bien produire le même assemblage que la boucle simple.
Hyde
@hyde ... qui n'est qu'un exemple du fait bien connu que l'un peut s'exprimer par l'autre; ne signifie pas qu'ils sont identiques. Un peu comme la masse et l'énergie. Bien sûr, on peut affirmer que toutes les manières de produire une sortie identique sont "les mêmes". Si le monde était fini, tous les programmes seraient constexpr, à la fin.
Peter - Réintégrer Monica le
@Giorgio Nah, c'est une description logique, pas une implémentation. Nous savons que les deux choses sont équivalentes ; mais l'équivalence n'est pas une identité , car la question (et les réponses) est exactement comment nous obtenons le résultat, c'est-à-dire qu'elles contiennent nécessairement des descriptions algorithmiques (qui peuvent être exprimées en termes de pile et de variable, etc.).
Peter - Réintégrer Monica le
1
@ PeterA.Schneider Oui, mais cette réponse indique "Le plus important de tous ... un code d'assemblage différent", ce qui n'est pas tout à fait correct.
Hyde
-1
Une simple itération est insuffisante pour être généralement équivalente à une récursivité, mais une itération avec une pile est généralement équivalente. Toute fonction récursive peut être reprogrammée en tant que boucle itérative avec une pile et inversement. Cela ne signifie toutefois pas que ce soit pratique, cependant, et dans toute situation particulière, l’une ou l’autre des formes peut présenter des avantages évidents par rapport à l’autre version.
Je ne sais pas pourquoi c'est controversé. La récursivité et l'itération avec une pile sont le même processus de calcul. Ils sont le même "phénomène", pour ainsi dire.
La seule chose à laquelle je peux penser, c'est que lorsque je considère ces outils comme des "outils de programmation", je conviens que vous ne devriez pas les considérer comme la même chose. Elles sont équivalentes "mathématiquement" ou "informatiquement" (encore une fois, itération avec une pile , pas itération en général), mais cela ne signifie pas que vous devriez aborder les problèmes avec la pensée que l'une ou l'autre fera. Du point de vue de la mise en œuvre et de la résolution de problèmes, certains problèmes peuvent mieux fonctionner, et votre travail en tant que programmeur consiste à déterminer correctement celui qui convient le mieux.
Pour clarifier, la réponse à la question Est-ce qu'une boucle while est intrinsèquement une récursion? est un non définitif , ou du moins "à moins que vous n'ayez également une pile".
Réponses:
Les boucles ne sont vraiment pas récursives. En fait, ils sont le meilleur exemple du mécanisme opposé : l' itération .
L'intérêt de la récursivité est qu'un élément du traitement appelle une autre instance d'elle-même. Le mécanisme de contrôle de boucle simple saute jusqu'au point où il a commencé.
Sauter dans le code et appeler un autre bloc de code sont des opérations différentes. Par exemple, lorsque vous sautez au début de la boucle, la variable de contrôle de la boucle a toujours la même valeur qu’avant le saut. Mais si vous appelez une autre instance de la routine dans laquelle vous vous trouvez, la nouvelle instance dispose de nouvelles copies non liées de toutes ses variables. Effectivement, une variable peut avoir une valeur au premier niveau de traitement et une autre valeur à un niveau inférieur.
Cette fonctionnalité est cruciale pour le fonctionnement de nombreux algorithmes récursifs. C'est pourquoi vous ne pouvez pas émuler la récursivité par itération sans gérer également une pile de trames appelées qui garde toutes ces valeurs.
la source
Dire que X est intrinsèquement Y n'a de sens que si vous avez un système (formel) à l'esprit que vous exprimez X en. Si vous définissez la sémantique de
while
lambda calcul, vous pouvez mentionner récursion *; si vous le définissez en termes de machine à enregistrer, vous ne le ferez probablement pas.Dans les deux cas, les gens ne vous comprendront probablement pas si vous appelez une fonction récursive simplement parce qu'elle contient une boucle while.
* Bien que peut-être seulement indirectement, par exemple si vous le définissez en termes de
fold
.la source
while
construction, la récursivité est généralement une propriété de fonctions, je ne vois rien d'autre qui puisse être décrit comme "récursif" dans ce contexte.Cela dépend de votre point de vue.
Si vous examinez la théorie de la calculabilité , l'itération et la récursivité sont également expressives . Cela signifie que vous pouvez écrire une fonction qui calcule quelque chose, et que vous le fassiez de manière récursive ou itérative, vous pourrez choisir les deux approches. Il n'y a rien que vous puissiez calculer récursivement qui ne puisse être calculé de manière itérative et inversement (bien que le fonctionnement interne du programme puisse être différent).
De nombreux langages de programmation ne traitent pas la récursivité et l'itération de la même manière et pour une bonne raison. En règle générale , la récursivité signifie que le langage / le compilateur gère la pile d'appels et l'itération signifie que vous devrez peut-être gérer vous-même la pile.
Cependant, il existe des langages - en particulier des langages fonctionnels - dans lesquels des choses comme les boucles (pour, tandis que) ne sont en effet que du sucre syntaxique pour la récursion et sont implémentées en coulisse de cette façon. C'est souvent souhaitable dans les langages fonctionnels, car ils n'ont généralement pas le concept de bouclage, et l'ajouter rendrait leur calcul plus complexe, pour une raison peu pratique.
Donc non, ils ne sont pas intrinsèquement les mêmes . Ils sont également expressifs , ce qui signifie que vous ne pouvez pas calculer quelque chose de manière itérative, ni de manière récursive ni inversement, mais c'est à peu près tout, dans le cas général (selon la thèse de Church-Turing).
Notez que nous parlons ici de programmes récursifs . Il existe d'autres formes de récursivité, par exemple dans les structures de données (par exemple, les arbres).
Si vous le regardez du point de vue de la mise en œuvre , récursivité et itération ne sont pratiquement plus les mêmes. La récursivité crée un nouveau cadre de pile pour chaque appel. Chaque étape de la récursivité est autonome, les arguments du calcul étant obtenus par l'appelé (lui-même).
Les boucles d’autre part ne créent pas de cadres d’appel. Pour eux, le contexte n'est pas préservé à chaque étape. Pour la boucle, le programme revient simplement au début de la boucle jusqu'à ce que la condition de la boucle échoue.
C'est très important à savoir, car cela peut faire des différences assez radicales dans le monde réel. Pour la récursivité, le contexte entier doit être sauvegardé à chaque appel. Pour l'itération, vous avez un contrôle précis sur les variables en mémoire et sur ce qui est enregistré.
Si vous le regardez ainsi, vous voyez rapidement que pour la plupart des langues, l'itération et la récursivité sont fondamentalement différentes et ont des propriétés différentes. Selon la situation, certaines propriétés sont plus souhaitables que d’autres.
Récursivité peut rendre les programmes plus simples et plus faciles à tester et la preuve . La conversion d'une récursion en itération rend généralement le code plus complexe, ce qui augmente les risques d'échec. D'autre part, la conversion en itération et la réduction du nombre de trames de la pile d'appels peuvent économiser beaucoup de mémoire.
la source
La différence réside dans la pile implicite et la sémantique.
Une boucle while qui "s'appelle elle-même à la fin" n'a pas de pile à explorer lorsque c'est fait. C'est la dernière itération qui définit l'état final.
La récursivité ne peut cependant pas être réalisée sans cette pile implicite qui se souvient de l’état du travail effectué auparavant.
Il est vrai que vous pouvez résoudre tout problème de récursion par itération si vous lui donnez explicitement accès à une pile. Mais le faire de cette façon n'est pas la même.
La différence sémantique tient au fait que regarder du code récursif transmet une idée de manière complètement différente du code itératif. Le code itératif fait les choses une étape à la fois. Il accepte n'importe quel état d'avant et ne travaille que pour créer l'état suivant.
Le code récursif divise un problème en fractales. Cette petite partie ressemble à cette grosse partie pour que nous puissions en faire un peu et le faire de la même manière. C'est une façon différente de penser aux problèmes. C'est très puissant et il faut s'y habituer. On peut dire beaucoup de choses en quelques lignes. Vous ne pouvez tout simplement pas sortir de cette boucle, même s'il a accès à une pile.
la source
Tout dépend de votre utilisation du terme intrinsèquement . Au niveau du langage de programmation, ils sont syntaxiquement et sémantiquement différents, et leurs performances et leur utilisation de la mémoire sont très différentes. Mais si vous creusez suffisamment dans la théorie, ils peuvent être définis en termes les uns des autres, et sont donc "les mêmes" dans un sens théorique.
La vraie question est la suivante: quand faut-il faire la distinction entre itération (boucles) et récursion, et quand est-il utile de penser que c'est la même chose? La réponse est que lors de la programmation (par opposition à l'écriture d'épreuves mathématiques), il est important de faire la distinction entre itération et récursivité.
La récursivité crée un nouveau cadre de pile, c'est-à-dire un nouvel ensemble de variables locales pour chaque appel. Cela entraîne une surcharge et prend de la place sur la pile, ce qui signifie qu'une récursion suffisamment profonde peut déborder de la pile, ce qui provoque le blocage du programme. D'autre part, l'itération ne modifie que les variables existantes, elle est donc généralement plus rapide et ne nécessite qu'une quantité de mémoire constante. C'est donc une distinction très importante pour un développeur!
Dans les langues avec récursivité d'appels (généralement des langages fonctionnels), le compilateur peut optimiser les appels récursifs de manière à ne prendre qu'une quantité de mémoire constante. Dans ces langues, la distinction importante n’est pas l’itération contre la récursivité, mais bien la version non-tail-call-récursive-call-récursion.
En bout de ligne: Vous devez être capable de faire la différence, sinon votre programme plantera.
la source
while
les boucles sont une forme de récursivité, voir par exemple la réponse acceptée à cette question . Ils correspondent à l'opérateur μ dans la théorie de la calculabilité (voir par exemple ici ).Toutes les variantes de
for
boucles qui itèrent sur une plage de nombres, une collection finie, un tableau, etc., correspondent à une récursion primitive, voir par exemple ici et ici . Notez que lesfor
boucles de C, C ++, Java, etc. constituent en réalité un sucre syntaxique pour unewhile
boucle et que, par conséquent, elles ne correspondent pas à la récursion primitive. Lafor
boucle Pascal est un exemple de récursion primitive.Une différence importante est que la récursion primitive se termine toujours, alors que la récursion généralisée (
while
boucles) peut ne pas se terminer.MODIFIER
Quelques clarifications concernant les commentaires et autres réponses. "La récursivité se produit lorsqu'une chose est définie en termes d'elle-même ou de son type." (voir wikipedia ). Alors,
Puisque vous pouvez définir une
while
boucle en tant que tellealors, oui , une
while
boucle est une forme de récursion. Les fonctions récursives sont une autre forme de récursivité (un autre exemple de définition récursive). Les listes et les arbres sont d'autres formes de récursivité.Une autre question implicitement supposée par de nombreuses réponses et commentaires est
La réponse à cette question est non : une
while
boucle correspond à une fonction queue récursive, où les variables auxquelles la boucle a accès correspondent aux arguments de la fonction récursive implicite, mais, comme d'autres l'ont souligné, des fonctions non récursives ne peut pas être modélisé par unewhile
boucle sans utiliser une pile supplémentaire.Ainsi, le fait "qu'une
while
boucle soit une forme de récursion" ne contredit pas le fait que "certaines fonctions récursives ne peuvent pas être exprimées par unewhile
boucle".la source
FOR
boucle peut calculer exactement toutes les fonctions récursives primitives, et un langage avec seulement uneWHILE
boucle peut calculer exactement toutes les fonctions µ-récursives (et il s'avère que les fonctions µ-récursives sont exactement ces fonctions une machine de Turing peut calculer). Bref, la récursion primitive et la récursion sont des termes techniques de la théorie mathématique / calculabilité.Un appel final (ou appel récursif) est implémenté exactement comme un "goto avec arguments" (sans pousser de trame d'appel supplémentaire sur la pile d'appels ) et dans certains langages fonctionnels (notamment Ocaml) est la méthode habituelle de bouclage.
Ainsi, une boucle while (dans les langues qui les ont) peut être vue comme se terminant par un appel de queue à son corps (ou à son test de la tête).
De même, les appels récursifs ordinaires (sans appel final) peuvent être simulés par des boucles (en utilisant une pile).
Lisez aussi sur les continuations et le style de continuation-passant .
Donc, "récursion" et "itération" sont profondément équivalents.
la source
Il est vrai que la récursion et la boucle while sans bornes sont équivalentes en termes d’expressivité de calcul. Autrement dit, tout programme écrit de manière récursive peut être réécrit en un programme équivalent en utilisant des boucles, et inversement. Les deux approches sont complètes , c'est-à-dire qu'elles peuvent être utilisées pour calculer toute fonction calculable.
La différence fondamentale en termes de programmation est que la récursivité vous permet d'utiliser les données stockées dans la pile d'appels. Pour illustrer cela, supposons que vous souhaitiez imprimer les éléments d'une liste à lien unique en utilisant une boucle ou une récursivité. Je vais utiliser C pour l'exemple de code:
Simple, non? Faisons maintenant une légère modification: Imprimez la liste dans l’ordre inverse.
Pour la variante récursive, il s’agit d’une modification presque triviale de la fonction originale:
Pour la fonction de boucle cependant, nous avons un problème. Notre liste est liée individuellement et ne peut donc être parcourue que vers l’avant. Mais comme nous imprimons en sens inverse, nous devons commencer à imprimer le dernier élément. Une fois que nous avons atteint le dernier élément, nous ne pouvons plus revenir à l’avant-dernier élément.
Nous devons donc soit procéder à de nombreuses ré-traverses, soit créer une structure de données auxiliaire qui assure le suivi des éléments visités et à partir de laquelle nous pouvons ensuite imprimer efficacement.
Pourquoi n'avons-nous pas ce problème de récursivité? Parce que dans la récursivité, nous avons déjà une structure de données auxiliaire en place: la pile d'appels de fonction.
Comme la récursivité nous permet de revenir à l'appel précédent de l'appel récursif, toutes les variables locales et l'état de cet appel restant intacts, nous bénéficions d'une certaine souplesse qu'il serait fastidieux de modéliser dans le cas itératif.
la source
Les boucles sont une forme spéciale de récursivité permettant d’accomplir une tâche spécifique (principalement une itération). On peut implémenter une boucle dans un style récursif avec la même performance [1] dans plusieurs langues. et dans le SICP [2], vous pouvez voir que les boucles sont décrites comme "sucre syntastique". Dans la plupart des langages de programmation impératifs, les blocs for et while utilisent la même portée que leur fonction parent. Néanmoins, dans la plupart des langages de programmation fonctionnels, il n’existe pas de boucles for, ni de boucles, car elles ne sont pas nécessaires.
La raison pour laquelle les langages impératifs ont des boucles for / while est qu’ils gèrent les états en les migrant. Mais en réalité, si vous regardez sous un angle différent, si vous pensez à un bloc while comme une fonction elle-même, prenez paramètre, traitez-le et renvoyez un nouvel état - qui pourrait aussi bien être l’appel de la même fonction avec des paramètres différents -. peut penser à la boucle comme une récursion.
Le monde pourrait également être défini comme mutable ou immuable. si nous définissons le monde comme un ensemble de règles et appelons une fonction ultime qui prend toutes les règles et l’état actuel en tant que paramètres et renvoie le nouvel état en fonction de ces paramètres, qui a la même fonctionnalité (génère le suivant dans le même état) On pourrait aussi bien dire que c’est une récursion et une boucle.
dans l'exemple suivant, la vie est la fonction prend deux paramètres "règles" et "état", et nouvel état sera construit dans la prochaine tick de temps.
[1]: L’optimisation d’appel en aval est une optimisation courante dans les langages de programmation fonctionnels consistant à utiliser la pile de fonctions existante dans des appels récursifs au lieu de créer un nouveau.
[2]: Structure et interprétation des programmes informatiques, MIT. https://mitpress.mit.edu/books/structure-and-interpretation-computer-programs
la source
Une boucle while est différente de la récursivité.
Lorsqu'une fonction est appelée, les opérations suivantes ont lieu:
Un cadre de pile est ajouté à la pile.
Le pointeur de code se déplace au début de la fonction.
Quand une boucle while est à la fin, ceci se produit:
Une condition demande si quelque chose est vrai.
Si c'est le cas, le code saute à un point.
En général, la boucle while s'apparente au pseudocode suivant:
Le plus important de tous, la récursivité et les boucles ont différentes représentations du code d'assemblage et des représentations du code machine. Cela signifie qu'ils ne sont pas les mêmes. Ils peuvent avoir les mêmes résultats, mais le code machine différent prouve qu'ils ne sont pas 100% identiques.
la source
Une simple itération est insuffisante pour être généralement équivalente à une récursivité, mais une itération avec une pile est généralement équivalente. Toute fonction récursive peut être reprogrammée en tant que boucle itérative avec une pile et inversement. Cela ne signifie toutefois pas que ce soit pratique, cependant, et dans toute situation particulière, l’une ou l’autre des formes peut présenter des avantages évidents par rapport à l’autre version.
Je ne sais pas pourquoi c'est controversé. La récursivité et l'itération avec une pile sont le même processus de calcul. Ils sont le même "phénomène", pour ainsi dire.
La seule chose à laquelle je peux penser, c'est que lorsque je considère ces outils comme des "outils de programmation", je conviens que vous ne devriez pas les considérer comme la même chose. Elles sont équivalentes "mathématiquement" ou "informatiquement" (encore une fois, itération avec une pile , pas itération en général), mais cela ne signifie pas que vous devriez aborder les problèmes avec la pensée que l'une ou l'autre fera. Du point de vue de la mise en œuvre et de la résolution de problèmes, certains problèmes peuvent mieux fonctionner, et votre travail en tant que programmeur consiste à déterminer correctement celui qui convient le mieux.
Pour clarifier, la réponse à la question Est-ce qu'une boucle while est intrinsèquement une récursion? est un non définitif , ou du moins "à moins que vous n'ayez également une pile".
la source