web-dev-qa-db-fra.com

Pourquoi la récursivité devrait-elle être préférée à l'itération?

L'itération est plus performante que la récursivité, non? Alors pourquoi certaines personnes pensent que la récursivité est meilleure (plus élégante, selon leurs mots) que l'itération? Je ne vois vraiment pas pourquoi certaines langues comme Haskell ne permettent pas l'itération et encouragent la récursivité? N'est-ce pas absurde d'encourager quelque chose qui a de mauvaises performances (et cela aussi quand une option plus performante, c'est-à-dire la récursivité est disponible)? Merci de faire la lumière là-dessus. Merci.

61
Debashish12

L'itération est plus performante que la récursivité, non?

Pas nécessairement. Cette conception provient de nombreux langages de type C, où l'appel d'une fonction, récursive ou non, avait une surcharge importante et créait un nouveau stackframe pour chaque appel.

Pour de nombreuses langues, ce n'est pas le cas et la récursivité est tout aussi ou plus performante qu'une version itérative. De nos jours, même certains compilateurs C réécrivent certaines constructions récursives dans une version itérative, ou réutilisent le cadre de pile pour un appel récursif de queue.

62
nos

Essayez d'implémenter la recherche en profondeur d'abord de manière récursive et itérative et dites-moi laquelle vous a facilité la tâche. Ou fusionner le tri. Pour de nombreux problèmes, il s'agit de maintenir explicitement votre propre pile par rapport à laisser vos données sur la pile de fonctions.

Je ne peux pas parler à Haskell car je ne l'ai jamais utilisé, mais c'est pour répondre à la partie plus générale de la question posée dans votre titre.

36
danben

Haskell n'autorise pas l'itération car l'itération implique un état mutable (l'index).

14
kennytm

Comme d'autres l'ont dit, il n'y a rien de moins intrinsèquement moins performant dans la récursivité. Il y a certaines langues où cela sera plus lent, mais ce n'est pas une règle universelle.

Cela étant dit, pour moi la récursivité est un outil, à utiliser quand cela a du sens. Il existe certains algorithmes qui sont mieux représentés sous forme de récursivité (tout comme certains sont meilleurs via l'itération).

Exemple concret:

fib 0 = 0
fib 1 = 1
fib n = fib(n-1) + fib(n-2)

Je ne peux pas imaginer une solution itérative qui pourrait éventuellement rendre l'intention plus claire que cela.

9
RHSeeger

Voici quelques informations sur les avantages et les inconvénients de la récursivité et de l'itération en c:

http://www.stanford.edu/~blp/writings/clc/recursion-vs-iteration.html

Surtout pour moi, la récursivité est parfois plus facile à comprendre que l'itération.

5
John Boker

L'itération n'est qu'une forme spéciale de récursivité.

4
Don Stewart

Plusieurs choses:

  1. L'itération n'est pas nécessairement plus rapide
  2. Racine de tout mal: encourager quelque chose simplement parce qu'il pourrait être modérément plus rapide est prématuré; il y a d'autres considérations.
  3. La récursivité est souvent beaucoup plus succincte et communique clairement votre intention
  4. En évitant l'état mutable en général, les langages de programmation fonctionnels sont plus faciles à raisonner et à déboguer, et la récursivité en est un exemple.
  5. La récursivité prend plus de mémoire que l'itération.
4
user24359

La récursivité est l'une de ces choses qui semblent élégantes ou efficaces en théorie mais en pratique est généralement moins efficace (sauf si le compilateur ou le recompilateur dynamique) change ce que fait le code. En général, tout ce qui provoque des appels de sous-programme inutiles sera plus lent, surtout lorsque plus d'un argument est poussé/sauté. Tout ce que vous pouvez faire pour supprimer les cycles du processeur, c'est-à-dire les instructions que le processeur doit mâcher, est un jeu équitable. Les compilateurs peuvent faire un très bon travail de ces jours en général, mais il est toujours bon de savoir écrire du code efficace à la main.

3
hjorgan

J'ai du mal à penser que l'un est toujours meilleur que l'autre.

Je travaille sur une application mobile qui doit effectuer un travail d'arrière-plan sur le système de fichiers utilisateur. L'un des fils d'arrière-plan doit balayer de temps en temps l'ensemble du système de fichiers pour conserver les données mises à jour pour l'utilisateur. Donc, par peur de Stack Overflow, j'avais écrit un algorithme itératif. Aujourd'hui j'en ai écrit un récursif, pour le même métier. À ma grande surprise, l'algorithme itératif est plus rapide: récursif -> 37s, itératif -> 34s (travaillant sur la même structure de fichier exacte).

récursif:

private long recursive(File rootFile, long counter) {
            long duration = 0;
            sendScanUpdateSignal(rootFile.getAbsolutePath());
            if(rootFile.isDirectory()) {
                File[] files = getChildren(rootFile, MUSIC_FILE_FILTER);
                for(int i = 0; i < files.length; i++) {
                    duration += recursive(files[i], counter);
                }
                if(duration != 0) {
                    dhm.put(rootFile.getAbsolutePath(), duration);
                    updateDurationInUI(rootFile.getAbsolutePath(), duration);
                }   
            }
            else if(!rootFile.isDirectory() && checkExtension(rootFile.getAbsolutePath())) {
                duration = getDuration(rootFile);
                dhm.put(rootFile.getAbsolutePath(), getDuration(rootFile));
                updateDurationInUI(rootFile.getAbsolutePath(), duration);
            }  
            return counter + duration;
        }

Itératif: - recherche itérative en profondeur d'abord, avec retour en arrière récursif

private void traversal(File file) {
            int pointer = 0;
            File[] files;
            boolean hadMusic = false;
            long parentTimeCounter = 0;
            while(file != null) {
                sendScanUpdateSignal(file.getAbsolutePath());
                try {
                    Thread.sleep(Constants.THREADS_SLEEP_CONSTANTS.TRAVERSAL);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                files = getChildren(file, MUSIC_FILE_FILTER);

                if(!file.isDirectory() && checkExtension(file.getAbsolutePath())) {
                    hadMusic = true;
                    long duration = getDuration(file);
                    parentTimeCounter = parentTimeCounter + duration;
                    dhm.put(file.getAbsolutePath(), duration);
                    updateDurationInUI(file.getAbsolutePath(), duration);
                }

                if(files != null && pointer < files.length) {
                    file = getChildren(file,MUSIC_FILE_FILTER)[pointer];
                }
                else if(files != null && pointer+1 < files.length) {
                    file = files[pointer+1];
                    pointer++;
                }
                else {
                    pointer=0;
                    file = getNextSybling(file, hadMusic, parentTimeCounter);
                    hadMusic = false;
                    parentTimeCounter = 0;
                }
            }
        }

private File getNextSybling(File file, boolean hadMusic, long timeCounter) {
            File result= null;
            //se o file é /mnt, para
            if(file.getAbsolutePath().compareTo(userSDBasePointer.getParentFile().getAbsolutePath()) == 0) {
                return result;
            }
            File parent = file.getParentFile();
            long parentDuration = 0;
            if(hadMusic) { 
                if(dhm.containsKey(parent.getAbsolutePath())) {
                    long savedValue = dhm.get(parent.getAbsolutePath());
                    parentDuration = savedValue + timeCounter;
                }
                else {
                    parentDuration = timeCounter; 
                }
                dhm.put(parent.getAbsolutePath(), parentDuration);
                updateDurationInUI(parent.getAbsolutePath(), parentDuration);
            }

            //procura irmao seguinte
            File[] syblings = getChildren(parent,MUSIC_FILE_FILTER);
            for(int i = 0; i < syblings.length; i++) {
                if(syblings[i].getAbsolutePath().compareTo(file.getAbsolutePath())==0) {
                    if(i+1 < syblings.length) {
                        result = syblings[i+1];
                    }
                    break;
                }
            }
            //backtracking - adiciona pai, se tiver filhos musica
            if(result == null) {
                result = getNextSybling(parent, hadMusic, parentDuration);
            }
            return result;
        }

Bien sûr, l'itératif n'est pas élégant, mais bien qu'il soit actuellement implémenté de manière inefficace, il est toujours plus rapide que celui récursif. Et j'ai un meilleur contrôle dessus, car je ne veux pas qu'il fonctionne à pleine vitesse, et je laisserai le garbage collector faire son travail plus fréquemment.

Quoi qu'il en soit, je ne prendrai pas pour acquis qu'une méthode est meilleure que l'autre et je passerai en revue d'autres algorithmes actuellement récursifs. Mais au moins à partir des 2 algorithmes ci-dessus, l'itératif sera celui du produit final.

2
jfv

Je ne pense pas qu'il y ait quoi que ce soit intrinsèquement moins performant dans la récursivité - du moins dans l'abstrait. La récursivité est une forme spéciale d'itération. Si un langage est conçu pour bien prendre en charge la récursivité, il est possible qu'il fonctionne aussi bien que l'itération.

En général, la récursivité rend explicite l'état que vous avancez dans la prochaine itération (ce sont les paramètres). Cela peut faciliter la parallélisation de l'exécution par les processeurs de langage. C'est du moins une direction que les concepteurs de langage tentent d'exploiter.

2
Michael Burr

En Java, les solutions récursives surpassent généralement celles non récursives. En C, c'est généralement l'inverse. Je pense que cela vaut en général pour les langues compilées de manière adaptative par rapport aux langues compilées à l'avance.

Edit: Par "généralement", je veux dire quelque chose comme une répartition 60/40. Cela dépend beaucoup de l'efficacité avec laquelle le langage gère les appels de méthode. Je pense que la compilation JIT favorise la récursivité car elle peut choisir comment gérer l'inline et utiliser les données d'exécution dans l'optimisation. Cela dépend cependant de l'algorithme et du compilateur en question. Java en particulier continue de devenir plus intelligent sur la gestion de la récursivité.

Résultats de l'étude quantitative avec Java (lien PDF) . Notez que ce sont principalement des algorithmes de tri et utilisent un ancien Java Virtual Machine (1.5.x si je lis bien). Ils obtiennent parfois une amélioration des performances 2: 1 ou 4: 1 en utilisant l'implémentation récursive, et rarement la récursivité est beaucoup plus lente. Dans mon expérience personnelle, la différence n'est pas souvent aussi prononcée, mais une amélioration de 50% est courante lorsque j'utilise la récursivité de manière sensée.

2
BobMcGee

Je comparerais la récursivité à un explosif: vous pouvez atteindre un gros résultat en un rien de temps. Mais si vous l'utilisez sans précautions, le résultat pourrait être désastreux.

J'ai été très impressionné en prouvant la complexité de la récursivité qui calcule les nombres de Fibonacci ici . La récursivité dans ce cas a la complexité O ((3/2) ^ n) tandis que l'itération est juste O (n). Le calcul de n = 46 avec une récursion écrite sur c # prend une demi-minute! Sensationnel...

La récursivité à mon humble avis ne doit être utilisée que si la nature des entités convient bien à la récursivité (arbres, analyse syntaxique, ...) et jamais à cause de l'esthétique. Les performances et la consommation de ressources de tout code récursif "divin" doivent être examinées de près.

1
ivan_d

Je pense que cela aiderait à comprendre ce qu'est réellement la performance. Ce lien montre comment une application parfaitement codée a réellement beaucoup de place pour l'optimisation - à savoir un facteur de 43! Rien de tout cela n'avait à voir avec l'itération vs la récursivité.

Lorsqu'une application a été réglée jusque-là , elle arrive au point où les cycles enregistrés par itération par rapport à la récursivité peuvent réellement faire la différence.

1
Mike Dunlavey

En tant que bas niveau, ITERATION traite du registre CX pour compter les boucles, et bien sûr des registres de données. RECURSION ne traite pas seulement de cela, il ajoute également des références au pointeur de pile pour conserver les références des appels précédents, puis comment revenir en arrière.

Mon professeur d'université m'a dit que tout ce que vous faites avec la récursivité peut être fait avec les itérations et vice-versa, mais il est parfois plus simple de le faire par récursion que l'itération (plus élégant) mais à un niveau de performance, il est préférable d'utiliser les itérations.

1
MRFerocius

La récursivité est l'implémentation typique de l'itération. C'est juste un niveau d'abstraction inférieur (au moins en Python):

class iterator(object):
    def __init__(self, max):
        self.count = 0
        self.max = max

    def __iter__(self):
        return self

    # I believe this changes to __next__ in Python 3000
    def next(self):
        if self.count == self.max:
            raise StopIteration
        else:
            self.count += 1
            return self.count - 1

# At this level, iteration is the name of the game, but
# in the implementation, recursion is clearly what's happening.
for i in iterator(50):
    print(i)
1
orokusaki

"L'itération est plus performante que la récursivité" est vraiment spécifique au langage et/ou au compilateur. Le cas qui me vient à l'esprit est lorsque le compilateur effectue le déroulement de boucle. Si vous avez implémenté une solution récursive dans ce cas, cela va être un peu plus lent.

C'est là que ça vaut la peine d'être scientifique (tester des hypothèses) et de connaître ses outils ...

0
Austin Salonen

L'itération est plus performante que la récursivité, non?

Oui.

Cependant, lorsque vous avez un problème qui correspond parfaitement à une structure de données récursive, la meilleure solution est toujours récursive.

Si vous faites semblant de résoudre le problème avec itérations vous finirez par réinventer la pile et créer un plus désordonné et laid par rapport à la version élégante récursive du code.

Cela dit, Itération sera toujours plus rapide que Récursivité . (dans une architecture Von Neumann), donc si vous utilisez toujours la récursivité, même là où une boucle suffira, vous paierez une pénalité de performance.

La récursivité est-elle toujours plus rapide que le bouclage?

0
Lucio M. Tato