Чи цикл часу деякий час є рекурсією?


37

Мені було цікаво, чи цикл певного часу є суто рекурсією?

Я думаю, це тому, що певний час цикл може розглядатися як функція, яка викликає себе в кінці. Якщо це не рекурсія, то в чому різниця?



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

18
@MooingDuck Індукцією можна довести, що будь-яка рекурсія може бути записана як ітерація та навпаки. Так, це виглядатиме зовсім інакше, але ви все-таки можете це зробити.
Полігном

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

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

Відповіді:


116

Петлі - це не рекурсія. Насправді вони є яскравим прикладом протилежного механізму: ітерації .

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

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

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


10
@Giorgio Це може бути правдою, але це коментар до претензії, відповіді не було. "Довільно" немає у цій відповіді та суттєво змінило б значення.
hvd

12
@hvd У принципі хвостова рекурсія - це повна рекурсія, як і будь-яка інша. Розумні компілятори можуть оптимізувати фактичну частину "створення нового кадру стека", щоб згенерований код був дуже схожий на цикл, але поняття, про які ми говоримо, стосуються рівня вихідного коду. Я вважаю, що форма алгоритму є вихідним кодом найважливішою справою, тому я все-таки називаю це рекурсією
Kilian Foth

15
@Giorgio "це саме те, що робить рекурсія: викликайте себе новими аргументами" - крім виклику. І аргументи.
варення

12
@Giorgio Ви використовуєте різні визначення слів, ніж більшість тут. Слова, ви знаєте, є основою спілкування. Це програмісти, а не обмін CS стеками. Якби ми використовували такі слова, як "аргумент", "виклик", "функція" тощо, як ви пропонуєте, було б неможливо обговорити фактичний код.
Гайд

6
@Giorgio Я дивлюся на абстрактне поняття. Є концепція, де ви повторюєтесь, і концепція, де ви циклічно. Вони різні поняття. Хоббс на 100% правильний, що аргументів немає і дзвінка немає. Вони принципово і абстрактно відрізняються. І це добре, бо вони вирішують різні проблеми. Ви, з іншого боку, дивитесь на те, як можна реалізувати цикли, коли єдиним вашим інструментом є рекурсія. Іронічно ви говорите Гоббсу, щоб перестати думати про реалізацію та почати розглядати концепції, коли ваша методологія є такою, яка справді потребує переоцінки.
corsiKa

37

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

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

* Хоча, можливо, лише побічно, наприклад, якщо ви визначаєте це в термінах fold.


4
Якщо чесно, функція не є рекурсивною в жодному визначенні. Він просто містить рекурсивний елемент, цикл.
Луань

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

36

Це залежить від вашої точки зору.

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

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

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

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

Зауважте, що ми говоримо про рекурсивні програми тут. Існують і інші форми рекурсії, наприклад, в структурах даних (наприклад, дерева).


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

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

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

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

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


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

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

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

1
Якщо мова не є цілковитою, то спершу її безглуздо. DFA не можуть ані довільної ітерації, ані рекурсії btw.
Полігном

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

12

Різниця полягає в неявному стеку та смисловості.

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

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

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

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

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


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

2
@ruakh Я б заперечував, що функція, яка виконується в постійному просторі, використовуючи усунення хвостових викликів, справді є циклом. Тут стек не є деталізацією реалізації, саме абстракція дозволяє накопичувати різні стани для різних рівнів рекурсії.
Cimbali

@ruakh: будь-який стан в рамках одного рекурсивного виклику буде зберігатися в неявному стеку, якщо тільки рекурсія не може бути перетворена компілятором в ітераційний цикл. Усунення хвостових викликів - це детальна інформація про реалізацію, яку ви повинні знати, якщо ви хочете реорганізувати свою функцію, щоб бути рекурсивною. Крім того, "мало таких мов немає" - чи можете ви навести приклад мов, яким не потрібен стек для рекурсивних дзвінків?
Groo


@ruakh: CPS сам по собі створює той самий неявний стек, тому він повинен покладатися на усунення хвостових викликів, щоб мати сенс (що це робить тривіальним через спосіб його побудови). Навіть стаття з wikipedia, з якою ви посилаєтесь, говорить те саме: Використання CPS без оптимізації виклику хвоста (TCO) призведе до зростання не тільки побудованого продовження під час рекурсії, але й стека викликів .
Groo

7

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

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

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

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

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


3

whileпетлі є формою рекурсії, див., наприклад, прийняту відповідь на це питання . Вони відповідають μ-оператору в теорії обчислень (див., Наприклад, тут ).

Усі варіанти forциклів, які повторюються на діапазоні чисел, кінцевій колекції, масиві тощо, відповідають примітивній рекурсії, див., Наприклад, тут і тут . Зауважте, що forпетлі C, C ++, Java та ін., Насправді є синтаксичним цукром для whileциклу, і тому це не відповідає примітивній рекурсії. forПетля Паскаля - приклад примітивної рекурсії.

Важлива відмінність полягає в тому, що примітивна рекурсія завжди припиняється, тоді як генералізована рекурсія ( whileпетлі) може не закінчитися.

EDIT

Деякі роз’яснення щодо коментарів та інші відповіді. "Рекурсія виникає, коли річ визначається з точки зору самої себе або її типу". (див. Вікіпедію ). Так,

Чи цикл часу деякий час є рекурсією?

Оскільки ви можете визначити whileцикл з точки зору себе

while p do c := if p then (c; while p do c))

то, так , whileпетля - це форма рекурсії. Рекурсивні функції - це ще одна форма рекурсії (інший приклад рекурсивного визначення). Списки та дерева - це інші форми рекурсії.

Ще одне питання, на яке явно передбачається багато відповідей та коментарів, є

Чи одночасно циклічні та рекурсивні функції рівноцінні?

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

Отже, той факт, що " whileцикл - це форма рекурсії", не суперечить тому, що "деякі рекурсивні функції не можуть бути виражені whileциклом".


2
@morbidCode: примітивна рекурсія та μ-рекурсія - це форми рекурсії із специфічними обмеженнями (або їх відсутністю), вивчені, наприклад, в теорії обчислюваності. Як виявляється, мова з лише FORциклом може обчислити саме всі примітивні рекурсивні функції, а мова з простою WHILEциклом може обчислити саме всі µ-рекурсивні функції (і виявляється, що µ-рекурсивні функції - це саме ті функції, які Машина Тьюрінга може обчислити). Або, якщо коротко: примітивна рекурсія та µ-рекурсія - це технічні терміни з математики / теорії обчислення.
Йорг W Міттаг

2
Я подумав, що "рекурсія" передбачає функцію, яка викликає себе, в результаті чого поточний стан виконання висувається до стека тощо; тому більшість машин мають практичне обмеження на кількість рівнів, які ви можете повторити. Хоча циклі не мають таких обмежень, оскільки вони внутрішньо використовували б щось на зразок "JMP" і не використовують стек. Просто моє розуміння, може бути помилковим.
Jay

13
Ця відповідь використовує зовсім інше визначення для слова "рекурсивний", ніж використовувало ОП, і, таким чином, є дуже оманливим.
Mooing Duck

2
@DavidGrinberg: Цитування: "C, C ++, Java для циклів не є прикладом примітивної рекурсії. Примітивна рекурсія означає, що максимальна кількість ітерацій / глибини рекурсії фіксується перед початком циклу." Джорджіо говорить про примітиви теорії обчислюваності . Не пов'язані з мовами програмування.
Mooing Duck

3
Я мушу погодитися з Муо Каком. Хоча теорія обчислень може бути цікавою в теоретичному КС, я думаю, всі згодні з тим, що ОП говорило про концепцію мов програмування.
Voo

2

Хвіст виклик (або хвіст рекурсивний виклик) точно реалізований як «Гото з аргументами» (без натискання будь - якої додатковий кадру виклику на стеку викликів ) , а в деяких функціональних мовах (Ocaml в зокрема) є звичайним способом зациклення.

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

Точно так само звичайні рекурсивні дзвінки (без зворотного дзвінка) можуть бути імітовані циклами (використовуючи деякий стек).

Читайте також про продовження та стиль продовження проходження .

Тож "рекурсія" та "ітерація" глибоко рівнозначні.


1

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

Принципова різниця в плані програмування полягає в тому, що рекурсія дозволяє використовувати дані, що зберігаються в стеку викликів. Щоб проілюструвати це, припустимо, що ви хочете надрукувати елементи спільно пов'язаного списку, використовуючи цикл або рекурсію. Я буду використовувати C для прикладу коду:

 typedef struct List List;
 struct List
 {
     List* next;
     int element;
 };

 void print_list_loop(List* l)
 {
     List* it = l;
     while(it != NULL)
     {
          printf("Element: %d\n", it->element);
          it = it->next;
     }
 }

 void print_list_rec(List* l)
 {
      if(l == NULL) return;
      printf("Element: %d\n", l->element);
      print_list_rec(l->next);
 }

Просте, правда? Тепер зробимо одну невелику модифікацію: надрукуйте список у зворотному порядку.

Для рекурсивного варіанту це майже тривіальна модифікація вихідної функції:

void print_list_reverse_rec(List* l)
{
    if (l == NULL) return;
    print_list_reverse_rec(l->next);
    printf("Element: %d\n", l->element);
}

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

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

Чому ми не маємо цієї проблеми з рекурсією? Оскільки в рекурсії у нас вже є допоміжна структура даних: Стек виклику функції.

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


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

@Baldrickk Найцікавіше, що стосується рекурсії хвоста - це те, що ви закінчуєте версію, яка набагато ближче до того, як виглядала б версія циклу, оскільки вона знову знімає вашу здатність зберігати стан у стеці викликів. Подвійно пов'язаний список вирішив би це, але перепроектування структури даних часто не є можливим при зіткненні з цією проблемою. Хоча приклад тут дещо штучно обмежений, він ілюструє закономірність, яка часто з’являється на функціональних мовах у контексті рекурсивних алгебраїчних типів.
ComicSansMS

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

0

Петлі - це особлива форма рекурсії для досягнення конкретного завдання (переважно ітерації). Можна реалізувати цикл у рекурсивному стилі з однаковою продуктивністю [1] кількома мовами. а в SICP [2] ви бачите, як петлі описані як "синтастичний цукор". У більшості імперативних мов програмування блоки для і в той час використовують ті ж області, що і їх батьківська функція. Тим не менш, у більшості функціональних мов програмування не існує ні циклів, ні циклів, оскільки в них немає потреби.

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

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

у наступному прикладі "life" функція приймає два параметри "правила" та "стан", і новий стан буде побудовано в наступному галочку.

life rules state = life rules new_state
    where new_state = construct_state_in_time rules state

[1]: оптимізація хвостових викликів - це звичайна оптимізація функціональних мов програмування для використання існуючого стека функцій у рекурсивних викликах замість створення нового.

[2]: Структура та інтерпретація комп'ютерних програм, MIT. https://mitpress.mit.edu/books/structure-and-interpretation-computer-programs


4
@Giorgio Не мій відгук, а лише здогадка: я думаю, що більшість програмістів вважають, що рекурсія означає, що існує рекурсивний виклик функції, тому що, ну, саме це рекурсія, це функція, яка викликає себе. У циклі немає рекурсивного виклику функції. Так що сказати, що цикл без рекурсивного виклику функції є особливою формою рекурсії було б явно неправильним, якщо йти за цим визначенням.
Гайд

1
Що ж, можливо, якщо поглянути на це з більш абстрактної точки зору, то, що здається різними речами, насправді концептуально однакове. Мені здається, що це дуже відлякує і сумно думати, що люди заперечують відповіді лише тому, що вони не відповідають їх очікуванням, а не сприймають їх як шанс чогось навчитися. Усі відповіді, які намагаються сказати: "Гей, дивись, ці речі на поверхні виглядають по-різному, але насправді однакові на більш абстрактному рівні".
Джорджіо

3
@Georgio: Мета цього сайту - отримати відповіді на запитання. Корисні та правильні відповіді заслуговують на рейтинги, відповіді на які є заплутаними та некорисними, заслуговують на опромінення. Відповідь, яка тонко використовує інше визначення загального терміна, не даючи зрозуміти, яке інше визначення використовується, є заплутаною і непотрібною. Відповіді, які мають сенс лише у тому випадку, якщо ви вже знаєте відповідь, так би мовити, не є корисними, а служать лише для того, щоб показати письменникам найкраще розуміння термінології.
ЖакБ

2
@JacquesB: "Відповіді, які мають сенс лише в тому випадку, якщо ви вже знаєте відповідь, так би мовити, не корисні ...": Це також можна сказати про відповіді, які лише підтверджують те, що читач уже знає або думає знати. Якщо у відповідь вводиться не зрозуміла термінологія, можна писати коментарі, щоб попросити більше деталей, перш ніж звертати увагу.
Джорджіо

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

-1

Певний час цикл відрізняється від рекурсії.

Коли викликається функція, відбувається наступне:

  1. До стека додається рамка стека.

  2. Покажчик коду переміщується на початок функції.

Коли цикл деякий час знаходиться в кінці, відбувається наступне:

  1. Умова запитує, чи є щось правдою.

  2. Якщо так, код переходить до точки.

Взагалі цикл while схожий на наступний псевдокод:

 if (x)

 {

      Jump_to(y);

 }

Найголовніше, що у рекурсії та циклів є різні представлення коду складання та машинного коду. Це означає, що вони не однакові. Вони можуть мати однакові результати, але різний машинний код доводить, що вони не на 100% одне і те ж.


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

1
Залежно від компілятора, оптимізований вбудований виклик рекурсії цілком може створити таку ж збірку, як і звичайний цикл.
Гайд

@hyde ... що є лише прикладом для загальновідомого факту, що одне можна виразити через інше; не означає, що вони однакові. Трохи як маса і енергія. Звичайно, можна стверджувати, що всі способи отримання однакового випуску є "однаковими". Якби світ був кінцевим, врешті-решт усі програми були б contexpr.
Пітер - Відновіть Моніку

@Giorgio Nah, це логічний опис, а не реалізація. Ми знаємо, що дві речі рівнозначні ; але еквівалентність не є тотожністю , тому що питання (і відповіді) - це саме те , як ми дістаємося до результату, тобто вони обов'язково містять алгоритмічні описи (які можна виразити у вигляді стека та змінної тощо).
Пітер - Відновіть Моніку

1
@ PeterA.Schneider Так, але ця відповідь зазначає "Найголовніше з усіх ... різний код складання", що не зовсім правильно.
Гайд

-1

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

Я не впевнений, чому це суперечливо. Рекурсія та ітерація зі стеком - це той самий обчислювальний процес. Вони те саме «явище», так би мовити.

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

Для уточнення, відповідь на запитання Чи цикл часу деякий час є рекурсією? це певне ні , або, принаймні, "ні, якщо ви також не маєте стека".

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