Может ли каждая рекурсия быть преобразована в итерацию?

A reddit thread вызвал, по-видимому, интересный вопрос:

Рекурсивные функции хвоста могут быть тривиально преобразованы в итерационные функции. Другие могут быть преобразованы с использованием явного стека. Можно ли преобразовать каждую рекурсию в итерацию?

Пример (счетчик?) в сообщении - это пара:

(define (num-ways x y)
  (case ((= x 0) 1)
        ((= y 0) 1)
        (num-ways2 x y) ))

(define (num-ways2 x y)
  (+ (num-ways (- x 1) y)
     (num-ways x (- y 1))

Ответ 1

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

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

На оборотной стороне этой монеты мы можем, конечно, представить, что система программирования настолько продвинута, чтобы рассматривать рекурсивное определение формулы в качестве приглашения запоминать предыдущие результаты, тем самым предлагая преимущество в скорости без хлопот, говорящего на компьютере точно какие шаги следует при вычислении формулы с рекурсивным определением. Дейкстра почти наверняка представила такую ​​систему. Он долго пытался отделить реализацию от семантики языка программирования. С другой стороны, его языки без детерминированного и многопроцессорного программирования находятся в лиге выше практического профессионального программиста.

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

Я вижу одну вескую причину. Предположим, у вас есть прототипная система на языке супервысокого уровня, например [нарядное белье для асбеста] Scheme, Lisp, Haskell, OCaml, Perl или Pascal. Предположим, что условия таковы, что вам нужна реализация на C или Java. (Возможно, это политика.) Тогда вы, безусловно, могли бы записать некоторые функции, написанные рекурсивно, но которые, в буквальном переводе, взорвут вашу систему времени исполнения. Например, бесконечная рекурсия хвоста возможна в Схеме, но одна и та же идиома вызывает проблему для существующих сред C. Другим примером является использование лексически вложенных функций и статической области, которые Pascal поддерживает, но C не делает.

В этих условиях вы можете попытаться преодолеть политическое сопротивление исходному языку. Возможно, вы ошибаетесь, переоценивая Lisp, как в десятом законе Гринспуна (на словах). Или вы можете просто найти совершенно другой подход к решению. Но в любом случае, безусловно, есть способ.

Ответ 2

Всегда ли можно написать нерекурсивную форму для каждой рекурсивной функции?

Да. Простое формальное доказательство состоит в том, чтобы показать, что и µ-рекурсия, и нерекурсивное исчисление, такое как GOTO, являются полными по Тьюрингу. Поскольку все полные по Тьюрингу исчисления строго эквивалентны по своей выразительной силе, все рекурсивные функции могут быть реализованы с помощью нерекурсивного полного по Тьюрингу исчисления.

К сожалению, я не могу найти хорошее, формальное определение GOTO онлайн, поэтому вот одно:

Программа GOTO - это последовательность команд P, выполняемых на машине регистрации, так что P является одной из следующих:

  • HALT, который останавливает исполнение
  • r = r + 1 где r - любой регистр
  • r = r – 1 где r - любой регистр
  • GOTO x где x это метка
  • IF r ≠ 0 GOTO x где r - любой регистр, а x - метка
  • Метка, сопровождаемая любой из вышеперечисленных команд.

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

Для получения дополнительной информации см. Этот ответ.

Ответ 3

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

Ответ 4

В принципе, да, по сути, вам нужно заменить вызовы методов (которые неявно перетаскивают состояние в стек) в явный стек, чтобы помнить, где был "предыдущий вызов", а затем выполнить " называемый метод '.

Я бы предположил, что комбинация цикла, стека и состояния-машины может использоваться для всех сценариев, в основном имитируя вызовы методов. Будет ли это "лучше" (или более быстрое, или более эффективное в некотором смысле), вообще-то невозможно вообще сказать.

Ответ 5

  • Рекурсивный поток выполнения функции может быть представлен как дерево.

  • Такую же логику можно выполнить с помощью цикла, который использует структуру данных для перемещения этого дерева.

  • Прохождение по глубине может быть выполнено с использованием стека, обход в ширину может быть выполнен с использованием очереди.

Итак, ответ: да. Почему: fooobar.com/questions/26505/....

Может ли любая рекурсия быть выполнена в одном цикле? Да, потому что

машина Тьюринга делает все, что она делает, выполняя один цикл:

  • выберите команду,
  • оцените его,
  • перейти 1.

Ответ 6

Да, используя явно стек (но рекурсия гораздо приятнее читать, ИМХО).

Ответ 7

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

Ответ 8

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

Учитывая реальный язык программирования, ответ не столь очевиден. Проблема в том, что вполне возможно иметь язык, на котором объем памяти, который может быть выделен в программе, ограничен, но где количество стека вызовов, которое может быть использовано, неограничено (32-разрядный C, где адрес переменных стека недоступен). В этом случае рекурсия более мощная, потому что у нее больше памяти, которую она может использовать; недостаточно явно выделяемой памяти для эмуляции стека вызовов. Подробное обсуждение этого можно найти в Это обсуждение.

Ответ 9

Иногда замена рекурсии намного проще. Рекурсия была модной вещью, преподаваемой в CS в 1990-х годах, и поэтому многие средние разработчики с того времени поняли, что если вы решили что-то с рекурсией, это было лучшее решение. Таким образом, они будут использовать рекурсию вместо того, чтобы зацикливаться назад, чтобы изменить порядок, или такие глупые вещи. Поэтому иногда удаление рекурсии - это простой упражнение "duh, that was visible".

Это сейчас проблема, поскольку мода изменилась в сторону других технологий.

Ответ 10

Все вычислимые функции могут быть вычислены машинами Тьюринга, и поэтому рекурсивные системы и машины Тьюринга (итерационные системы) эквивалентны.

Ответ 11

Удаление рекурсии является сложной проблемой и возможно при определенных условиях.

Нижеприведенные примеры относятся к числу простых:

Ответ 12

Appart из явного стека, другой шаблон для преобразования рекурсии в итерацию - это использование батута.

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

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

http://en.wikipedia.org/wiki/Trampoline_(computers)

Ответ 13

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

Ответ 14

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

Выполняет абзац, который может дать вам некоторое представление о том, с чего начать:

Решение рекуррентного отношения означает получение закрытого решения: нерекурсивная функция n.

Также посмотрите последний абзац эту запись.

Ответ 15

Можно преобразовать любой рекурсивный алгоритм в нерекурсивный один, но часто логика намного сложнее, и для этого требуется использование стека. Фактически, сама рекурсия использует стек: функциональный стек.

Подробнее: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions

Ответ 16

tazzego, рекурсия означает, что функция будет называть себя, нравится вам это или нет. Когда люди говорят о том, можно ли делать что-либо без рекурсии, они означают это, и вы не можете сказать "нет, это не так, потому что я не согласен с определением рекурсии" как действительный оператор.

Имея это в виду, почти все остальное, что вы говорите, ерунда. Единственное, что вы говорите, что это не вздор, - это идея, что вы не можете представить себе программирование без вызова. Это то, что делалось в течение десятилетий, пока использование столов-столов стало популярным. В старых версиях FORTRAN не хватало callstack, и они отлично работали.

Кстати, существуют языковые версии Turing, которые реализуют рекурсию (например, SML) как средство циклизации. Кроме того, существуют Turing-полные языки, которые реализуют только итерацию как средство циклизации (например, FORTRAN IV). Тезис о Церкви-Тьюринга доказывает, что все, что возможно в языках только для рекурсии, может быть выполнено на нерекурсивном языке и, наоборот, тем, что они оба обладают свойством turing-полноты.

Ответ 17

Вот итеративный алгоритм:

def howmany(x,y)
  a = {}
  for n in (0..x+y)
    for m in (0..n)
      a[[m,n-m]] = if m==0 or n-m==0 then 1 else a[[m-1,n-m]] + a[[m,n-m-1]] end
    end
  end
  return a[[x,y]]
end

Ответ 18

Домашний вопрос, да?

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

Что касается оправдания вашего ответа, вы должны это сделать, это ваше домашнее задание; -)

Ответ 19

Вопрос: если, во-первых, функция делает копию себя в случайном пространстве памяти void, а затем вместо того, чтобы называть себя вызовом копии, является ли это еще рекурсией? (1) Я бы сказал, да.

Явное использование стека является реальным способом удаления рекурсии? (2) Я бы сказал, нет. В принципе, разве мы не подражаем тому, что происходит, когда мы используем явно рекурсию? Я считаю, что мы не можем определить рекурсию просто как "функцию, которая вызывает себя", так как я вижу рекурсию также в "коде для копирования" (1) и в "явном использовании стека" (2).

Кроме того, я не вижу, как CT демонстрирует, что все рекурсивные алгоритмы могут стать итеративными. Мне только кажется, что "все", обладающее "силой" машины Тьюринга, может выразить все алгоритмы, которые это может выразить. Если Машина Тьюринга не может рекурсивно, мы уверены, что у каждого рекурсивного алгоритма есть свой взаимный перевод... Машина Тьюринга может рекурсивно? По моему мнению, если его можно "реализовать" (каким-либо образом), то мы можем сказать, что оно есть. Есть это? Я не знаю.

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

Избегайте копирования (1) и "имитированного стека" (2), продемонстрировали ли мы, что каждый рекурсивный алгоритм может быть на реальных машинах выражен итеративно?! Я не вижу, где мы это продемонстрировали.