рекурсія проти ітерації


109

Чи правильно сказати, що скрізь, де використовується рекурсія, може використовуватися forцикл? А якщо рекурсія зазвичай повільніше, що є технічною причиною того, що коли-небудь використовувати її за допомогою forітерації циклу?

І якщо завжди можна перетворити рекурсію в forцикл, чи існує правило, як це зробити?


3
recursionпроти iteration? iteration = for loopЯ думаю.
gongzhitaao

4
У блозі Тома Моертеля є чотири чудові повідомлення про перетворення рекурсивного коду в ітеративний код: blog.moertel.com/tags/recursion.html
cjohnson318

Відповіді:


148

Рекурсія, як правило, набагато повільніше, тому що всі виклики функцій повинні зберігатися в стеку, щоб повернутись до функцій виклику. У багатьох випадках пам'ять повинна бути виділена і скопійована для здійснення ізоляції сфери.

Деякі оптимізації, як-от оптимізація хвостових викликів , роблять рекурсії швидшими, але не завжди можливі та не реалізуються на всіх мовах.

Основними причинами використання рекурсії є

  • що він більш інтуїтивний у багатьох випадках, коли він імітує наш підхід до проблеми
  • що деякі структури даних, такі як дерева, простіше досліджувати за допомогою рекурсії (або в будь-якому випадку знадобиться стек)

Звичайно, кожна рекурсія може бути змодельована як певний цикл: саме це і зробить процесор. А сама рекурсія, пряміше, означає розміщення функцій викликів та діапазонів у стек. Але для зміни рекурсивного алгоритму до циклу може знадобитися велика робота і зробити ваш код менш рентабельним: як і для кожної оптимізації, слід намагатись лише тоді, коли якесь профілювання або докази показали, що це необхідно.


10
Додамо до цього - рекурсія тісно пов'язана з терміном скорочення, який відіграє центральну роль у багатьох алгоритмах і в CS загалом.
SomeWittyUsername

3
Чи можете ви надати мені приклад, коли рекурсія робить код більш рентабельним? На мій досвід, це завжди навпаки. Дякую
Йейкель

@Yeikel Написати функцію, f(n)яка повертає n-е число Фібоначчі .
Метт

54

Чи правильно сказати, що скрізь, де використовується рекурсія, може бути використана петля?

Так, оскільки рекурсія в більшості процесорів моделюється циклами та структурою даних стека.

А якщо рекурсія зазвичай повільніше, що є технічною причиною її використання?

Це не "зазвичай повільніше": це рекурсія, яка застосовується неправильно, це повільніше. Крім того, сучасні компілятори добре перетворюють деякі рекурсії в цикли, навіть не запитуючи.

І якщо завжди можна перетворити рекурсію в цикл for, чи існує правило, як це зробити?

Напишіть ітеративні програми для алгоритмів, які найкраще розуміються, коли пояснюються ітеративно; записувати рекурсивні програми для алгоритмів, що найкраще пояснюються рекурсивно.

Наприклад, пошук бінарних дерев, запуск quicksort та аналіз виразів у багатьох мовах програмування часто пояснюється рекурсивно. Вони також найкраще кодуються рекурсивно. З іншого боку, обчислення факторіалів та обчислення чисел Фібоначчі набагато простіше пояснити з точки зору ітерацій. Використовувати для них рекурсію - це як бити мухи кувалдою: це не дуже гарна ідея, навіть коли кувалда робить у цьому дійсно хорошу роботу + .


+ Я запозичив аналогію кувалди з "Дисципліни програмування" Дійкстри.


7
Рекурсія, як правило, дорожча (повільніше / більше пам'яті), через створення фреймів стека та іншого. Різниця може бути невеликою при правильному застосуванні для досить складної проблеми, але вона все ж дорожча. Можливі винятки, такі як оптимізація хвостової рекурсії.
Бернхард Баркер

Я не впевнений у єдиному для циклу у кожному випадку. Розглянемо складнішу рекурсію або рекурсію з більш ніж однією змінною
SomeWittyUsername

@dasblinkenlight Теоретично можливо звести кілька циклів до однієї, але не впевнений у цьому.
SomeWittyUsername

@icepack Так, можливо. Це може бути не дуже, але це можливо.
Бернхард Баркер

Я не впевнений, що я згоден з вашим першим твердженням. Самі процесори взагалі не моделюють рекурсії, саме інструкції, що виконуються на процесорі, моделюють рекурсію. по-друге, структура циклу (не обов'язково) має динамічно зростаючий і скорочується набір даних, де рекурсивний алгоритм, як правило, буде для кожного рівня глибокого, на який повинна діяти рекурсія.
сурми

28

Питання:

І якщо рекурсія зазвичай проходить повільніше, яка технічна причина коли-небудь використовувати її для ітерації циклу?

Відповідь:

Тому що в деяких алгоритмах важко це ітераційно вирішити. Спробуйте вирішити пошук по глибині як в рекурсивно, так і в повторному періоді. У вас з’явиться думка, що вирішити DFS за допомогою ітерації просто важко.

Ще одна добра річ, яку потрібно спробувати: Спробуйте записати сортування Об’єднати ітеративно. Це займе у вас досить багато часу.

Питання:

Чи правильно сказати, що скрізь, де використовується рекурсія, може бути використана петля?

Відповідь:

Так. Ця нитка має дуже гарну відповідь на це.

Питання:

І якщо завжди можна перетворити рекурсію в цикл for, чи існує правило, як це зробити?

Відповідь:

Довірся мені. Спробуйте написати свою власну версію, щоб вирішити пошук в глибині перших ітерацій. Ви помітите, що деякі проблеми простіше вирішити рекурсивно.

Підказка: Рекурсія хороша, коли ви вирішуєте проблему, яку можна вирішити методом розділення та перемоги .


3
Я високо ціную спробу надати авторитетну відповідь, і я впевнений, що автор розумний, але "повір мені" не є корисною відповіддю на змістовне запитання, відповідь якого не відразу очевидна. Існують дуже прості алгоритми для проведення ітеративного пошуку в глибині першого спочатку. Дивіться приклад внизу цієї сторінки для опису алгоритму в псевдокоді: csl.mtu.edu/cs2321/www/newLectures/26_Depth_First_Search.html
jdelman

3

Окрім того, що відбувається повільніше, рекурсія також може призвести до помилок переповнення стека залежно від того, наскільки глибока вона проходить.


3

Щоб написати еквівалентний метод за допомогою ітерації, ми повинні явно використовувати стек. Той факт, що ітеративна версія вимагає стеку для її вирішення, вказує на те, що проблема є досить складною, що може отримати користь від рекурсії. Як правило, рекурсія найбільш підходить для проблем, які неможливо вирішити з фіксованим об'ємом пам'яті і, отже, вимагають стека, коли вона ітераційно вирішується. Сказавши це, рекурсія та ітерація можуть показувати однаковий результат, якщо вони дотримуються різної моделі. Щоб вирішити, який метод працює краще, це залежно від конкретного випадку, а найкраща практика полягає у виборі, виходячи з тієї схеми, за якою випливає проблема.

Наприклад, щоб знайти n-е трикутне число трикутної послідовності: 1 3 6 10 15 ... Програма, яка використовує ітераційний алгоритм для знаходження n-го трикутного числа:

Використання ітеративного алгоритму:

//Triangular.java
import java.util.*;
class Triangular {
   public static int iterativeTriangular(int n) {
      int sum = 0;
      for (int i = 1; i <= n; i ++)
         sum += i;
      return sum;
   }
   public static void main(String args[]) {
      Scanner stdin = new Scanner(System.in);
      System.out.print("Please enter a number: ");
      int n = stdin.nextInt();
      System.out.println("The " + n + "-th triangular number is: " + 
                            iterativeTriangular(n));
   }
}//enter code here

Використання рекурсивного алгоритму:

//Triangular.java
import java.util.*;
class Triangular {
   public static int recursiveTriangular(int n) {
      if (n == 1)
     return 1;  
      return recursiveTriangular(n-1) + n; 
   }

   public static void main(String args[]) {
      Scanner stdin = new Scanner(System.in);
      System.out.print("Please enter a number: ");
      int n = stdin.nextInt();
      System.out.println("The " + n + "-th triangular number is: " + 
                             recursiveTriangular(n)); 
   }
}

1

Більшість відповідей, здається, припускають, що iterative= for loop. Якщо ваш цикл for не обмежений ( a la C, ви можете робити все, що завгодно, зі своїм лічильником циклу), то це правильно. Якщо це реальний for цикл (скажімо , як в Python або більшості функціональних мов , де ви не можете вручну змінити лічильник циклу), то це НЕ правильно.

Усі (обчислювані) функції можна реалізовувати як рекурсивно, так і за допомогою whileциклів (або умовних стрибків, які в основному одне і те ж). Якщо ви справді обмежите себе for loops, ви отримаєте лише підмножину цих функцій (примітивні рекурсивні функції, якщо ваші елементарні операції розумні). Зрозуміло, це досить велика підмножина, яка, як правило, містить кожну функцію, яку ви, ймовірно, сприяєте на практиці.

Що набагато важливіше - це те, що безліч функцій дуже просто реалізувати рекурсивно і дуже важко ітеративно реалізувати (керування стеком викликів вручну не враховується).


1

Так, як сказав по Thanakron Tandavas ,

Рекурсія хороша, коли ви вирішуєте задачу, яку можна вирішити технікою розділення та перемоги.

Наприклад: Вежі Ханої

  1. N кілець у збільшенні розміру
  2. 3 жердини
  3. Кільця починаються укладеними на полюс 1. Мета - перемістити кільця так, щоб вони були укладені на полюс 3 ... Але
    • Можна переміщати лише одне кільце за раз.
    • Більше кільце не можна покласти поверх меншого.
  4. Ітеративне рішення - "потужне, але потворне"; рекурсивне рішення "елегантне".

Цікавий приклад. Я думаю, ви знаєте документ MC Er "Вежі Ханої та двійкові числа". Також розглядається у фантастичному відео від 3brown1blue.
Andrestand

0

Мені здається, я пам'ятаю, як мій професор інформатики ще тоді говорив, що всі проблеми, які мають рекурсивні рішення, також мають ітераційні рішення. Він каже, що рекурсивне рішення, як правило, повільніше, але їх часто використовують, коли їх простіше міркувати і кодувати, ніж ітеративні рішення.

Однак, у випадку більш досконалих рекурсивних рішень, я не вірю, що вони завжди зможуть реалізувати їх за допомогою простого forциклу.


Завжди можна перетворити рекурсивний алгоритм в ітеративний (використовуючи стеки). Ви можете не закінчитись особливо простим циклом, але це можливо.
Бернхард Баркер

-4

рекурсія + запам'ятовування може призвести до більш ефективного рішення порівняння з чисто ітераційним підходом, наприклад, перевірте це: http://jsperf.com/fibach-memoized-vs-iterative-for-large-n


3
Будь-який рекурсивний код може бути перетворений у функціонально ідентичний ітераційний код за допомогою стеків. Різниця, яку ви показуєте, полягає в різниці між двома підходами до вирішення однієї і тієї ж проблеми, а не різницею між рекурсією та ітерацією.
Бернхард Баркер

-6

Коротка відповідь: ремісія - рекурсія швидша і для циклів займає менше пам'яті майже у всіх випадках. Однак зазвичай існують способи зміни циклу for або рекурсії, щоб зробити його швидшим

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.