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


126

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

Чи є випадки, коли завдання можна виконати лише за допомогою рекурсії, а не циклу?


13
Я серйозно сумніваюся в цьому. Рекурсія - це прославлена ​​петля.
Гонки легкості по орбіті

6
Побачивши різні напрямки, в яких йдуть відповіді (і я щойно не зміг себе запропонувати кращу), ви можете зробити будь-кого, хто намагається відповісти на послугу, якщо ви надасте трохи більше інформації та відповіді, на яку ви хочете піти. Ви хочете теоретичного доказування гіпотетичних машин (з необмеженим часом зберігання та часу роботи)? Або практичні приклади? (Де "смішно складно" може бути "не можна зробити".) Або щось інше?
5gon12eder

8
@LightnessRacesinOrbit На моє вухо, яке не говорить англійською мовою, "Рекурсія - це прославлений цикл", звучить у вас на увазі "Ви можете також використовувати контурну конструкцію замість рекурсивного дзвінка де завгодно, і концепція насправді не заслуговує на це власне ім'я" . Можливо, тоді я трактую ідіому "прославленого чогось" неправильно.
Гайд

13
Що з функцією Ackermann? en.wikipedia.org/wiki/Ackermann_function , що не особливо корисно, але неможливо зробити за допомогою циклу. (Ви також можете перевірити це відео youtube.com/watch?v=i7sm9dzFtEI від Computerphile)
WizardOfMenlo

8
@WizardOfMenlo код befunge - це реалізація рішення ERRE (яке також є інтерактивним рішенням ... зі стеком). Ітеративний підхід зі стеком може імітувати рекурсивний виклик. У будь-якому програмуванні, що є досить потужним, одна петлева конструкція може бути використана для імітації іншої. Регістр машин з інструкцією INC (r), JZDEC (r, z)може реалізувати машину Тьюринга. У нього немає «рекурсії» - ось стрибок, якщо нульовий ДЕКРЕМЕНТ. Якщо функція Ackermann є обчислювальною (вона є), ця реєстраційна машина може це зробити.

Відповіді:


164

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

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

Часто рекурсивне рішення проблеми є гарнішим. Це технічний термін, і це має значення.


120
В основному, робити петлі замість рекурсії - означає вручну обробити стек.
Сільвіу Бурсеа

15
... стек (и) . Наступна ситуація може сильніше віддати перевагу наявності декількох стеків. Розглянемо одну рекурсивну функцію, Aяка знаходить щось на дереві. Щоразу, коли Aстикається з цією річчю, вона запускає іншу рекурсивну функцію, Bяка знаходить споріднену річ у піддереві на тому місці, де вона була запущена A. Після Bзакінчення рекурсії, до якої вона повертається A, і остання продовжує власну рекурсію. Можна оголосити один стек для Aі один для B, або поставити Bстек всередині Aциклу. Якщо хтось наполягає на використанні однієї стеки, все стає справді складним.
rwong

35
Therefore, the one thing recursion can do that loops can't is make some tasks super easy. І єдине, що петлі можуть зробити, що рекурсія не може, це зробити деякі завдання дуже просто. Чи бачили ви потворні, неінтуїтивні речі, які вам доведеться зробити, щоб перетворити більшість природно-ітеративних проблем від наївної рекурсії до хвостової рекурсії, щоб вони не підірвали стек?
Мейсон Уілер

10
@MasonWheeler У 99% часу ці "речі" можуть бути краще інкапсульовані всередині оператора рекурсії на кшталт mapабо fold(адже, якщо ви вирішите вважати їх примітивними, я думаю, ви можете використовувати fold/ unfoldяк третю альтернативу циклів або рекурсії). Якщо ви не пишете код бібліотеки, не так багато випадків, коли ви повинні турбуватися про реалізацію ітерації, а не про завдання, яке вона повинна виконувати - на практиці це означає, що явні петлі та явна рекурсія є однаково поганими абстракції, яких слід уникати на верхньому рівні.
Левшенко

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

78

Немає.

Переходячи до самих основ необхідних мінімумів, щоб зробити обчислення, вам просто потрібно вміти циклічно (це одне недостатньо, але, скоріше, є необхідним компонентом). Не має значення як .

Будь-яка мова програмування, яка може реалізувати машину Тьюрінга, називається Turing завершеною . І є багато мов, які закінчуються.

Моя улюблена мова в дорозі там, що "насправді працює?" Повнота Тьюрінга - це повна FRACTRAN , яка є Тьюрінгом завершеною . Він має одну структуру циклу, і ви можете реалізувати в ньому машину Тюрінга. Таким чином, все, що обчислюється, може бути реалізовано мовою, яка не має рекурсії. Отже, нічого, що може дати вам рекурсія з точки зору обчислюваності, що не може просте цикління.

Це дійсно зводиться до кількох моментів:

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

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

І хоча я переніс це до крайнього "esolang" (здебільшого тому, що ви можете знайти речі, які Тюрінг є завершеними та реалізованими досить дивними способами), це не означає, що езоланг в будь-якому разі не є обов'язковим. Є цілий список речей, які випадково закінчуються Тюрінгом, включаючи Magic the Gathering, Sendmail, шаблони MediaWiki та систему типу Scala. Багато з них далеко не оптимальні, коли справа стосується того, щоб насправді робити щось практичне, це лише те, що ви можете обчислити все, що можна обчислити за допомогою цих інструментів.


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

Якщо у вас є, скажімо, факторний метод, записаний як:

int fact(int n) {
    return fact(n, 1);
}

int fact(int n, int accum) {
    if(n == 0) { return 1; }
    if(n == 1) { return accum; }
    return fact(n-1, n * accum);
}

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

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


Якщо ви хочете вникнути в цю теоретичну сторону, дивіться тезу Церкви Тьюрінга . Ви також можете вважати корисною тезу церкви Тьюрінга про CS.SE.


29
Тюрінг завершеності обертається занадто великим, як це має значення. Багато речей - Тьюрінг завершений ( як Magic the Gathering ), але це не означає, що це те саме, що щось інше, що Turing Complete. Принаймні, не на рівні, який має значення. Я не хочу ходити по дереву з Magic the Gathering.
Мізерний Роджер

7
Після того, як ви зможете зменшити проблему до ", що вона має рівну потужність, ніж машина Тьюрінга", її досить доставити туди. Машини Тьюрінга - це досить низьке перешкода, але це все, що потрібно. Немає нічого циклу, що не може зробити рекурсія, ні навпаки.

4
Заява у цій відповіді, звичайно, правильна, але я смію сказати, що аргумент насправді не переконливий. Машини Тьюрінга не мають прямого поняття рекурсії, тому висловлювання "ви можете імітувати машину Тьюрінга без рекурсії" насправді нічого не підтверджує. Що вам доведеться показати, щоб довести твердження, це те, що машини Тьюрінга можуть імітувати рекурсію. Якщо ви цього не демонструєте, ви повинні вірно припустити, що гіпотеза Церкви Тьюрінга також є рекурсією (що це робить), але ОП це поставило під сумнів.
5gon12eder

10
Питання ОП - це "може", а не "найкраще" чи "найефективніше" чи якийсь інший класифікатор. "Turing Complete" означає, що все, що можна зробити за допомогою рекурсії, також можна зробити за допомогою петлі. Чи це найкращий спосіб зробити це в будь-якій конкретній мовній реалізації - це зовсім інше питання.
Стівен Бернап

7
"Можна" - це НЕ те саме, що "найкраще". Якщо ви помиляєтеся "не найкраще" на "не можу", ви стаєте паралізованими, оскільки незалежно від того, яким чином ви щось робите, майже завжди є кращий спосіб.
Стівен Бернап

31

Чи є випадки, коли завдання можна виконати лише за допомогою рекурсії, а не циклу?

Ви завжди можете перетворити рекурсивний алгоритм у цикл, який використовує структуру даних Last-In-First-Out (стек AKA) для зберігання тимчасового стану, оскільки рекурсивний виклик - це саме те, зберігаючи поточний стан у стеку, продовжуючи виконання алгоритму, потім пізніше відновлення держави. Отже, коротка відповідь: Ні, таких випадків немає .

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

Це рекурсія, якщо ви реалізуєте виклик рекурсії самостійно, як окремі кроки "натиснути стан" та "перейти до початку" та "попсового стану"? А відповідь на це: Ні, це все ще не називається рекурсією, це називається ітерацією з явним стеком (якщо ви хочете використовувати встановлену термінологію).


Зауважте, це також залежить від визначення поняття "завдання". Якщо завдання полягає в сортуванні, то ви можете зробити це за допомогою багатьох алгоритмів, для багатьох з яких не потрібна будь-яка рекурсія. Якщо завдання полягає у впровадженні конкретного алгоритму, наприклад сортування злиття, то зазначена вище неоднозначність.

Тож розглянемо питання, чи існують загальні завдання, для яких існують лише алгоритми, подібні до рекурсії. З коментаря @WizardOfMenlo під питанням, функція Ackermann є простим прикладом цього. Отже, концепція рекурсії стоїть сама по собі, навіть якщо вона може бути реалізована за допомогою іншої конструкції комп'ютерної програми (ітерація з явним стеком).


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

@Joshua Дійсно! Це питання рівня абстракції. Якщо ви йдете на рівень або два нижче, це просто логічні ворота.
Гайд

2
Це не зовсім правильно. Щоб імітувати рекурсію за допомогою ітерації, вам потрібен стек, де можливий випадковий доступ. Один стек без випадкового доступу плюс кінцевий обсяг безпосередньо доступної пам'яті був би КПК, який не є повним Тьюрінгом.
Жиль

@Gilles Стара публікація, але навіщо потрібен стек довільного доступу? Крім того, чи не всі справжні комп’ютери навіть менше, ніж КПК, оскільки у них є лише обмежена кількість прямо доступної пам'яті, і немає стека взагалі (крім використання цієї пам'яті)? Це не здається практичною абстракцією, якщо в ній сказано "ми не можемо робити рекурсії в реальності".
Гайд

20

Це залежить від того, наскільки суворо ви визначаєте "рекурсію".

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

Але давайте розглянемо випадок, коли ми робимо рекурсивний виклик і використовуємо результат рекурсивного виклику для цього рекурсивного дзвінка.

public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
  if (m == 0)
    return  n+1;
  if (n == 0)
    return Ackermann(m - 1, 1);
  else
    return Ackermann(m - 1, Ackermann(m, n - 1));
}

Зробити перший рекурсивний ітеративний дзвінок просто:

public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
restart:
  if (m == 0)
    return  n+1;
  if (n == 0)
  {
    m--;
    n = 1;
    goto restart;
  }
  else
    return Ackermann(m - 1, Ackermann(m, n - 1));
}

Тоді ми можемо прибрати прибирання, gotoщоб позбавити велоцирапторів і відтінок Dijkstra:

public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
  while(m != 0)
  {
    if (n == 0)
    {
      m--;
      n = 1;
    }
    else
      return Ackermann(m - 1, Ackermann(m, n - 1));
  }
  return  n+1;
}

Але для видалення інших рекурсивних викликів нам доведеться зберігати значення деяких дзвінків у стек:

public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
  Stack<BigInteger> stack = new Stack<BigInteger>();
  stack.Push(m);
  while(stack.Count != 0)
  {
    m = stack.Pop();
    if(m == 0)
      n = n + 1;
    else if(n == 0)
    {
      stack.Push(m - 1);
      n = 1;
    }
    else
    {
      stack.Push(m - 1);
      stack.Push(m);
      --n;
    }
  }
  return n;
}

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

Враховуючи, для чого це складено, ми перетворили код, який використовує стек виклику, щоб реалізувати рекурсію в код, який цього не робить (і при цьому перетворив код, який передасть виняток переповнення стека для навіть зовсім невеликих значень у код, який буде просто прийняти болісно довго , щоб повернутися [см Як я можу запобігти мою функцію Акерман від переповнення стека? для деяких додаткових оптимізацій , які роблять це на самому справі повернутися на багато більше можливих входів]).

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

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

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


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

Це найкраща відповідь, чи не єдина правильна відповідь. Навіть другий приклад все ще є рекурсивним, і на цьому рівні відповідь на початкове запитання - ні . При більш широкому визначенні рекурсії рекурсії для функції Акермана неможливо уникнути.
Герріт

@gerrit і з більш вузьким, це уникає. Зрештою, це зводиться до країв саме того, що ми робимо чи не застосовуємо до цієї корисної мітки, яку ми використовуємо для певного коду.
Джон Ханна

1
Приєднався до сайту, щоб проголосувати за це. Функція Ackermann / має / рекурсивний характер. Реалізація рекурсивної структури з циклом і стеком не робить це ітеративним рішенням, ви просто перемістили рекурсію в область користувачів.
Аарон Макміллін

9

Класична відповідь - «ні», але дозвольте мені детальніше пояснити, чому я вважаю «так» - кращою відповіддю.


Перш ніж продовжувати, давайте щось вийдемо з точки зору обчислюваності та складності:

  • Відповідь "ні", якщо вам дозволено мати допоміжний стек під час циклу.
  • Відповідь "так", якщо вам не дозволено зайвих даних під час циклу.

Гаразд, тепер давайте поставимо одну ногу в практику-землю, іншу ногу тримаємо в теорії-землі.


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

Коли це розрізнення може мати значення? Коли ви працюєте з реальними інструментами. Ось приклад:

Скажіть, ви реалізуєте N-шлях mergesort. Можливо, у вас є forцикл, який проходить через кожен з Nсегментів, викликає mergesortїх окремо, а потім об'єднує результати.

Як ви могли б паралелізувати це з OpenMP?

У рекурсивній царині це надзвичайно просто: просто покладіть #pragma omp parallel forнавколо себе цикл, який іде від 1 до N, і ви закінчите. У ітераційній царині цього зробити не можна. Ви повинні нереститися нерестими вручну і передавати їм відповідні дані вручну, щоб вони знали, що робити.

З іншого боку, є й інші інструменти (наприклад, автоматичні векторизатори, наприклад #pragma vector), які працюють з петлями, але вкрай марні при рекурсії.

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

тобто: Інструменти однієї парадигми не автоматично переводяться на інші парадигми.

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


Навіть поза тим, врахуйте, що набір задач, які можна вирішити за допомогою автоматичного автоматичного автомата, що більший, ніж набір, який можна вирішити з кінцевим автоматом (детермінованим чи не), але менший, ніж набір, який можна вирішити за допомогою Тюрінг машина.
supercat

8

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

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

Узагальнити:

  • Випадок рекурсії = Контрольний потік + Стек (+ Купа)
  • Корпус петлі = Контрольний потік + Куча

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

  1. Ні стека, ні купи: рекурсія та динамічні структури неможливі. (рекурсія = цикл)
  2. Стек, без купи: рекурсія в порядку, динамічні структури неможливі. (рекурсія> цикл)
  3. Немає стека, купа: рекурсія неможлива, динамічні структури в порядку. (рекурсія = цикл)
  4. Стек, купа: рекурсія та динамічні структури в порядку. (рекурсія = цикл)

Якщо правила гри трохи суворіші, а рекурсивна реалізація заборонена використовувати петлі, ми отримуємо це замість цього:

  1. Ні стека, ні купи: рекурсія та динамічні структури неможливі. (рекурсія <цикл)
  2. Стек, без купи: рекурсія в порядку, динамічні структури неможливі. (рекурсія> цикл)
  3. Немає стека, купа: рекурсія неможлива, динамічні структури в порядку. (рекурсія <цикл)
  4. Стек, купа: рекурсія та динамічні структури в порядку. (рекурсія = цикл)

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


2

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

  • Викликаючи переповнення стека.
  • Повністю заплутаних початківців програмістів.
  • Створення швидко шукаючих функцій, які насправді є O (n ^ n).

3
Будь ласка, це дуже легко з петлями, я їх бачу весь час. Чорт забираючи, доклавши певних зусиль, вам навіть петлі не потрібні. Навіть якщо рекурсія простіше.
AviD

1
власне, A (0, n) = n + 1; A (m, 0) = A (m-1,1), якщо m> 0; A (m, n) = A (m-1, A (m, n-1)), якщо m> 0, n> 0 росте навіть трохи швидше, ніж O (n ^ n) (для m = n) :)
Джон Донн

1
@JohnDonn Більше, ніж трохи, це супер експоненціально. для n = 3 n ^ n ^ n для n = 4 n ^ n ^ n ^ n ^ n тощо. n до n потужності n разів.
Аарон Макміллін

1

Існує різниця між рекурсивними функціями та примітивними рекурсивними функціями. Примітивні рекурсивні функції - це ті, які обчислюються за допомогою циклів, де максимальний кількість ітерацій кожного циклу обчислюється до початку циклу. (І "рекурсивний" тут не має нічого спільного з використанням рекурсії).

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


3
Я не впевнений, як це стосується вищезазначеного питання? Чи можете ви, будь ласка, зробити це з'єднання більш зрозумілим?
Якк

1
Заміна неточної "петлі" важливою відмінністю між "циклом з обмеженим числом ітерацій" та "циклом з необмеженим числом ітерацій", про який я думав, що всі знають з CS 101.
gnasher729

Звичайно, але це все ще не стосується питання. Питання стосується петлі та рекурсії, а не примітивної рекурсії та рекурсії. Уявіть, що хтось запитав про відмінності C / C ++, а ви відповіли про різницю між K&R C та Ansi C. Звичайно, це робить речі більш точними, але це не відповідає на питання.
Якк

1

Якщо ви програмуєте на c ++ і використовуєте c ++ 11, то є одне, що потрібно зробити за допомогою рекурсій: constexpr функції. Але стандарт обмежує це до 512, як пояснено у цій відповіді . Використання циклів у цьому випадку неможливо, оскільки в цьому випадку функція не може бути constexpr, але це змінюється в c ++ 14.


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

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

@CodesInChaos Відредаговано.
Гульшань

-6

Я погоджуюся з іншими питаннями. З рекурсією нічого не можна зробити, чого не можна зробити з циклом.

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


2
Наївна рекурсивна реалізація чисел Фібоначчі з рекурсією закінчиться "поза часом", перш ніж у неї не вистачить місця для стеку. Я думаю, є й інші проблеми, які краще для цього прикладу. Також для багатьох проблем циклічна версія має такий же вплив на пам'ять, що і рекурсивна, просто на купі замість стека (якщо ваша мова програмування відрізняє їх).
Paŭlo Ebermann

6
Цикл також може бути "дуже небезпечним", якщо ви просто забудете збільшити змінну циклу ...
h22

2
Таким чином, навмисне створення переповнення стека - це завдання, яке стає дуже складним без використання рекурсії.
5gon12eder

@ 5gon12eder, який підводить нас до Які методи існують, щоб уникнути переповнення стека в рекурсивному алгоритмі? - написання для участі в TCO, або Пам'ятка може бути корисною. Ітеративний та рекурсивний підходи також цікаві, оскільки стосуються двох різних рекурсивних підходів для Фібоначчі.

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