Почему рекурсия должна быть предпочтительнее, чем итерация? - PullRequest
61 голосов
/ 02 февраля 2010

Итерация более производительна, чем рекурсия, верно? Тогда почему некоторые люди считают, что рекурсия лучше (по их словам, более элегантна), чем итерация? Я действительно не понимаю, почему некоторые языки, такие как Haskell, не допускают итерации и поощряют рекурсию? Разве это не абсурдно поощрять что-то, что имеет плохую производительность (и это также, когда более производительный вариант, т.е. рекурсия доступна)? Пожалуйста, пролите немного света на это. Спасибо.

Ответы [ 18 ]

62 голосов
/ 02 февраля 2010

Итерация более производительна, чем рекурсия, верно?

Не обязательно.Эта концепция взята из многих C-подобных языков, где вызов функции, рекурсивной или нет, имел большие издержки и создавал новый стек-кадр для каждого вызова.одинаково или более производительный, чем итеративная версия.В наши дни даже некоторые компиляторы C переписывают некоторые рекурсивные конструкции в итеративную версию или повторно используют кадр стека для хвостового рекурсивного вызова.

36 голосов
/ 02 февраля 2010

Попробуйте осуществить рекурсивный и итеративный поиск в глубину и скажите, какой из них облегчил вам задачу. Или сортировка слиянием. Для многих проблем все сводится к тому, чтобы явно поддерживать свой собственный стек, а не оставлять свои данные в стеке функций.

Я не могу говорить с Хаскеллом, так как никогда не использовал его, но это для того, чтобы ответить на более общую часть вопроса, поставленного в вашем заголовке.

14 голосов
/ 02 февраля 2010

Haskell не разрешает итерацию, потому что итерация включает изменяемое состояние (индекс).

9 голосов
/ 02 февраля 2010

Как уже говорили другие, в рекурсии нет ничего менее производительного. В некоторых языках это будет медленнее, но это не универсальное правило.

Как говорится, для меня рекурсия - это инструмент, который следует использовать, когда это имеет смысл. Есть некоторые алгоритмы, которые лучше представить в виде рекурсии (так же, как некоторые лучше с помощью итерации).

Показательный пример:

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

Я не могу представить себе итеративное решение, которое могло бы сделать намерение более ясным, чем это.

5 голосов
/ 02 февраля 2010

Вот некоторая информация о плюсах и минусах рекурсии и итерации в c:

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

В основном для меня рекурсия иногда легче понять, чем итерация.

4 голосов
/ 02 февраля 2010

Итерация - это просто особая форма рекурсии.

4 голосов
/ 02 февраля 2010

Несколько вещей:

  1. Итерация не обязательно быстрее
  2. Корень всего зла: преждевременное поощрение чего-либо только потому, что оно может быть умеренно быстрым; Есть и другие соображения.
  3. Рекурсия часто гораздо лаконичнее и яснее сообщает о ваших намерениях
  4. Благодаря отказу от изменяемого состояния в целом, функциональные языки программирования легче рассуждать и отлаживать, и рекурсия является примером этого.
  5. Рекурсия требует больше памяти, чем итерация.
3 голосов
/ 28 июля 2011

Рекурсия - это одна из тех вещей, которые кажутся элегантными или эффективными в теории, но на практике, как правило, менее эффективны (если только компилятор или динамический перекомпилятор) не изменяют то, что делает код.В общем, все, что вызывает ненужные вызовы подпрограммы, будет работать медленнее, особенно когда более / менее одного аргумента выдвигается / выталкивается.Все, что вы можете сделать, чтобы удалить циклы процессора, то есть инструкции, которые процессор должен пережевать, является честной игрой.В настоящее время компиляторы могут делать довольно хорошую работу в целом, но всегда полезно знать, как писать эффективный код вручную.

2 голосов
/ 02 февраля 2010

В Java рекурсивные решения обычно превосходят нерекурсивные.В Си все наоборот.Я думаю, что в целом это относится к адаптивно скомпилированным языкам по сравнению с заранее скомпилированными языками.

Редактировать: под "в целом" я имею в виду что-то вроде 60/40.Это очень зависит от того, насколько эффективно язык обрабатывает вызовы методов.Я думаю, что JIT-компиляция предпочитает рекурсию, потому что она может выбирать, как обрабатывать встраивание и использовать данные времени выполнения при оптимизации.Это очень зависит от рассматриваемого алгоритма и компилятора.В частности, Java продолжает умнее обращаться с рекурсией.

Количественные результаты исследования с Java (PDF link) .Обратите внимание, что это в основном алгоритмы сортировки, и они используют более старую виртуальную машину Java (1.5.x, если я правильно понял). Они иногда получают улучшение производительности в 2: 1 или 4: 1, используя рекурсивную реализацию, и редко рекурсия значительно медленнее. По моему личному опыту, разница не часто выражена, но составляет 50%улучшение часто встречается, когда я использую рекурсию разумно.

2 голосов
/ 19 октября 2013

Мне трудно рассуждать, что одно лучше, чем другое все время.

Я работаю над мобильным приложением, которое должно выполнять фоновую работу в пользовательской файловой системе.Один из фоновых потоков должен время от времени сканировать всю файловую систему, чтобы поддерживать обновленные данные для пользователя.Поэтому, опасаясь переполнения стека, я написал итерационный алгоритм.Сегодня я написал рекурсивный для той же работы.К моему удивлению, итерационный алгоритм работает быстрее: рекурсивный -> 37 с, итерационный -> 34 с (работает с точно такой же файловой структурой).

рекурсивный:

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;
        }

Итеративный: - итеративный поиск в глубину с рекурсивным возвратом

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;
        }

Конечно, итеративный не элегантный, но, несмотря на то, что в настоящее время он реализован неэффективным способом, он все ещебыстрее, чем рекурсивный.И у меня есть лучший контроль над этим, поскольку я не хочу, чтобы он работал на полной скорости, и позволю сборщику мусора выполнять свою работу более часто.

В любом случае, я не буду считать само собой разумеющимся, что один метод лучше, чемдругой, и рассмотрим другие алгоритмы, которые в настоящее время являются рекурсивными.Но, по крайней мере, из двух алгоритмов, приведенных выше, итеративным будет тот, который в конечном продукте.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...