Ітерація може замінити рекурсію?


42

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

Навіть була висококваліфікована нитка з дуже схваленою відповіддю, яка сказала так, що це можливо.

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

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

Я також сумніваюся, що гіпер-операції можуть бути виражені ітеративно.

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

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

Моє запитання таке:

Чи можна кожну рекурсію перетворити на ітерацію і чому?

Будь ласка, дайте відповідь, яскравий гімназист чи перший рік підрядник зрозуміє. Дякую.

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

PPS: Якщо відповідь "так", чи не могли б ви, наприклад, написати ітераційну версію непомітивно-рекурсивної функції. Наприклад, Аккерман у відповіді. Це допоможе мені зрозуміти.


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

6
На низькому рівні більшість із нас знає, що рекурсія дорівнює ітерації плюс стеку. Безтекстові граматики рекурсують модель, тоді як автоматичні автоматичні розгортки - це ітеративні «програми» зі стековою пам’яттю.
Гендрик Ян

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

можливо реалізувати машинний емулятор Тьюрінга лише з ітерацією
Sarge Borsch

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

Відповіді:


52

Рекурсію можна замінити ітерацією плюс необмеженою пам'яттю .

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

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

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

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

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

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

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

Я не збираюся навіть ескізувати доказ тут, але існує інтуїтивно зрозумілий зв'язок між переходом від примітивної рекурсії до повної рекурсії і переходом від циклів до циклів циклу: в обох випадках це передбачає не знати заздалегідь, коли ви Стоп. При повній рекурсії це робиться з оператором мінімізації, де ви продовжуєте роботу, поки не знайдете параметр, який задовольняє умові. З циклами while, це робиться, продовжуючи, поки не буде виконано стан циклу.

forwhilen


Приймаючи детальне пояснення, зберігається на рівні, про який вимагається.
Тобі Алафін

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

@Agent_L Так, для рекурсії потрібна необмежена пам'ять, щоб зберігати всі кадри стека. При рекурсійному підході існує потреба в необмеженій пам'яті, але вона не може бути інтуїтивно відокремлена від послідовності операцій, як при ітераціях.
Жил "ТАК - перестань бути злим"

1
Можливо, зацікавила б теза Церкви Тьюрінга. Машини Тьюрінга - це високо ітераційні машини, що не мають (притаманної) концепції рекурсії. Обчислення лямбда - мова, розроблена для вираження обчислень рекурсивним чином. Теза Церкви-Тьюрінга показала, що всі вирази лямбда можна оцінити на машині Тьюрінга, пов'язуючи рекурсію та ітерацію.
Корт Аммон

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

33

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

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


25

a(n,m)

s

push(s,x)xxpop(s)empty(s)

Ackermann(n0,m0):

  • s= (ініціалізувати порожній стек)
  • push(s,n0)
  • push(s,m0)
  • while(true):
    • mpop(s)
    • if(empty(s)):
      • return m (цим закінчується цикл)
    • npop(s)
    • if(n=0):
      • push(s,m+1)
    • else if(m=0):
      • push(s,n1)
      • push(s,1)
    • else:
      • push(s,n1)
      • push(s,n)
      • push(s,m1)

Я реалізував це на Цейлоні, ви можете запустити його в WebIDE , якщо хочете. (Він видає стек на початку кожної ітерації циклу.)

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


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

4
Дякуємо, що доклали зусиль для написання потрібної програми.
Тобі Алафін

16

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

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

Тепер, що буде, якби ви створили свій власний стек, замінили рекурсивні виклики функцій на натискання на стек, замінили повернення з рекурсивних функцій вискакуванням стека, і у нього був цикл, який тривав, поки стек не порожній?


2

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

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