Является ли цикл while по своей сути рекурсией?

37

Я задавался вопросом, является ли цикл while по своей сути рекурсией?

Я думаю, это потому, что цикл while можно рассматривать как функцию, которая вызывает себя в конце. Если это не рекурсия, то в чем разница?

badbye
источник
13
Вы можете преобразовать рекурсию в итерацию и наоборот, да. Это не значит, что они одинаковы, они просто имеют одинаковые возможности. Есть моменты, когда рекурсия более естественна, и бывают моменты, когда итерация более естественна.
Полигном
18
@MooingDuck Вы можете по индукции доказать, что любая рекурсия может быть записана как итерация, и наоборот. Да, это будет выглядеть по-другому, но вы можете сделать это, тем не менее.
Полигном
6
Что по сути здесь то же самое ? В программировании использование рекурсии означает особую вещь, которая отличается от итерации (циклы). В CS, когда вы приближаетесь к теоретической математике, эти вещи начинают означать немного разные вещи.
Хайд
3
@MooingDuck Преобразование из рекурсивного в итеративное на самом деле довольно тривиально. Вы просто сохраняете стек параметров вызова функции и стек результатов для вызовов функции. Вы заменяете рекурсивные вызовы, добавляя параметры в стек вызовов. Конечно, есть вся обработка стека, которая немного нарушает структуру алгоритма, но как только вы поймете, это довольно легко увидеть, что код делает то же самое. По сути, вы явно пишете стек вызовов, который подразумевается в рекурсивных определениях.
Бакуриу

Ответы:

116

Петли очень не рекурсивные. Фактически, они являются ярким примером противоположного механизма: итерации .

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

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

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

Килиан Фот
источник
10
@ Джорджио Это может быть правдой, но это комментарий к заявлению, которого ответ не дал. «Произвольно» не присутствует в этом ответе и значительно меняет смысл.
августа
12
@hvd В принципе хвостовая рекурсия - полная рекурсия, как и любая другая. Интеллектуальные компиляторы могут оптимизировать фактическую часть «создания нового стека фрейма», так что сгенерированный код очень похож на цикл, но концепции, о которых мы говорим, применимы к уровню исходного кода. Я считаю, что форма, которую алгоритм имеет в качестве исходного кода, важная вещь, поэтому я бы все равно назвал ее рекурсией
Килиан Фот,
15
@ Джорджио "это именно то, что делает рекурсия: вызывайте себя с новыми аргументами" - за исключением вызова. И аргументы.
Хоббс
12
@Giorgio Вы используете различные определения слов, чем большинство здесь. Слова, вы знаете, являются основой общения. Это программисты, а не CS Stack Exchange. Если бы мы использовали такие слова, как «аргумент», «вызов», «функция» и т. Д. Так, как вы предлагаете, было бы невозможно обсуждать реальный код.
Хайд
6
@ Джорджио Я смотрю на абстрактную концепцию. Есть концепция, в которой вы повторяетесь, и концепция, в которой вы повторяете цикл. Это разные понятия. Хоббс на 100% прав, что нет аргументов и нет вызова. Они принципиально и абстрактно разные. И это хорошо, потому что они решают разные проблемы. Вы, с другой стороны, смотрите, как можно реализовать циклы, когда единственным инструментом является рекурсия. Как ни странно, вы говорите Хоббсу перестать думать о реализации и начать смотреть на концепции, когда ваша методология действительно нуждается в переоценке.
CorsiKa
37

Сказать, что X по сути Y, имеет смысл, только если вы имеете в виду некоторую (формальную) систему, в которой вы выражаете X. Если вы определяете семантику whileв терминах лямбда-исчисления, вы можете упомянуть рекурсию *; если вы определите его в терминах регистрационной машины, вы, вероятно, не будете.

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

* Хотя, возможно, только косвенно, например, если вы определяете его в терминах fold.

Антон Голов
источник
4
Чтобы быть справедливым, функция не является рекурсивной в любом определении. Он просто содержит рекурсивный элемент, цикл.
Луаан
@Luaan: Определенно так, но, поскольку в языках с whileрекурсивностью конструкции, как правило, это свойство функций, я просто не могу придумать что-либо еще, чтобы описать его как «рекурсивное» в этом контексте.
Антон Голов
36

Это зависит от вашей точки зрения.

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

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

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

Так что нет, по сути они не одинаковы . Они одинаково выразительны , то есть вы не можете вычислять что-то итеративно, вы не можете вычислить рекурсивно и наоборот, но это все в общем случае (согласно тезису Черча-Тьюринга).

Обратите внимание, что мы говорим о рекурсивных программах здесь. Существуют и другие формы рекурсии, например, в структурах данных (например, деревья).


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

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

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

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

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

Polygnome
источник
Язык с локальными переменными и рекурсией, но без массивов, может выполнять задачи, которые не могут быть выполнены итеративным языком с локальными переменными и без массивов. Например, сообщите, содержит ли ввод буквенно-цифровую строку неизвестной длины, за которой следует пробел, а затем символы исходной строки в обратном порядке.
суперкат
3
Пока язык завершается, это возможно. Например, массив может быть легко заменен (дважды) связанным списком. Говорить об итерации или рекурсии и о том, равны ли они, имеет смысл, только если вы сравните два языка, полных тьюринга.
Полигном
Я имел в виду не иметь ничего, кроме простых статических или автоматических переменных, то есть не быть завершенным по Тьюрингу. Чисто итеративный язык будет ограничен теми задачами, которые могут быть выполнены с помощью простого детерминированного конечного автомата, в то время как рекурсивный язык добавит возможность выполнять задачи, которые потребуют, по крайней мере, детерминированный конечный автомат с нажатием.
суперкат
1
Если язык не завершен, его бессмысленно начинать. DFA не могут ни выполнять произвольные итерации, ни рекурсию между прочим.
Полигном
2
Ни одна из реализаций не является на самом деле полной по Тьюрингу, и языки, которые не являются полными по Тьюрингу, могут быть полезны для многих целей. Любая программа, которая может быть запущена с конечным числом переменных с конечным диапазоном, может быть обработана DFA, где каждая возможная комбинация значений является дискретным состоянием.
суперкат
12

Разница заключается в неявном стеке и семантике.

Цикл while, который «вызывает себя в конце», не имеет стека для повторного сканирования после завершения. Последняя итерация устанавливает, какое состояние будет в конце.

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

Это правда, что вы можете решить любую проблему рекурсии с помощью итерации, если вы дадите ей доступ к стеку явно. Но делать это не так.

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

Рекурсивный код разбивает проблему на фракталы. Эта маленькая часть выглядит как большая часть, так что мы можем сделать только этот кусочек и этот кусочек одинаково. Это другой способ думать о проблемах. Это очень мощный и требует привыкания. Многое можно сказать в несколько строк. Вы просто не можете получить это из цикла while, даже если у него есть доступ к стеку.

candied_orange
источник
5
Я думаю, что «неявный стек» вводит в заблуждение. Рекурсия является частью семантики языка, а не детали реализации. (Конечно, большинство языков, поддерживающих рекурсию, используют стек вызовов; но, во-первых, некоторые такие языки этого не делают, и, во-вторых, даже в таких языках не каждый рекурсивный вызов обязательно добавляется в стек вызовов, поскольку многие языки поддерживают такие оптимизации, как как исключение хвостового вызова .) Понимание обычной / простой реализации может быть полезно для получения контроля над абстракцией, но вы не должны обманывать себя, думая, что это целая история.
Руах
2
@ruakh Я бы сказал, что функция, которая выполняется в постоянном пространстве с использованием исключения хвостовых вызовов, действительно представляет собой цикл. Здесь стек не деталь реализации, это абстракция, которая позволяет вам накапливать разные состояния для разных уровней рекурсии.
Цимбали
@ruakh: любое состояние в пределах одного рекурсивного вызова будет сохранено в неявном стеке, если только рекурсия не может быть преобразована компилятором в итерационный цикл. Исключение хвостовых вызовов - это деталь реализации, о которой вам нужно знать, если вы хотите реорганизовать свою функцию, чтобы она была хвостовой рекурсивной. Кроме того, «немногие такие языки этого не делают» - можете ли вы привести пример языков, которым не нужен стек для рекурсивных вызовов?
Groo
@ruakh: CPS сам по себе создает тот же неявный стек, поэтому он должен полагаться на удаление хвостовых вызовов, чтобы иметь смысл (который он делает тривиальным из-за способа, которым он построен). Даже статья в википедии, на которую вы ссылаетесь, говорит о том же: использование CPS без оптимизации хвостовых вызовов (TCO) приведет к тому, что не только созданное продолжение потенциально может расти во время рекурсии, но и стек вызовов .
Groo
7

Это все петли на вашем использовании термина искробезопасности . На уровне языка программирования они синтаксически и семантически различны, и у них совершенно разная производительность и использование памяти. Но если вы углубитесь в теорию достаточно глубоко, они могут быть определены в терминах друг друга, и, следовательно, являются «одинаковыми» в некотором теоретическом смысле.

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

Рекурсия создает новый фрейм стека, то есть новый набор локальных переменных для каждого вызова. Это накладные расходы и занимает место в стеке, что означает, что достаточно глубокая рекурсия может переполнить стек, что приводит к сбою программы. Итерация, с другой стороны, только изменяет существующие переменные, поэтому, как правило, быстрее и занимает только постоянный объем памяти. Так что это очень важное отличие для разработчика!

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

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

JacquesB
источник
3

whileциклы - это форма рекурсии, см., например, принятый ответ на этот вопрос . Они соответствуют µ-оператору в теории вычислимости (см., Например, здесь ).

Все вариации forциклов, которые повторяются в диапазоне чисел, конечной коллекции, массиве и т. Д., Соответствуют примитивной рекурсии, см., Например, здесь и здесь . Обратите внимание, что forциклы C, C ++, Java и т. Д. На самом деле являются синтаксическим сахаром для whileцикла, и поэтому они не соответствуют примитивной рекурсии. Цикл Паскаля forявляется примером примитивной рекурсии.

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

РЕДАКТИРОВАТЬ

Некоторые разъяснения по поводу комментариев и других ответов. «Рекурсия возникает, когда вещь определяется в терминах себя или своего типа». (см. википедию ). Так,

Является ли цикл while по своей сути рекурсией?

Поскольку вы можете определить whileцикл в терминах самого себя

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

тогда да , whileцикл - это форма рекурсии. Рекурсивные функции - это еще одна форма рекурсии (еще один пример рекурсивного определения). Списки и деревья - это другие формы рекурсии.

Другой вопрос, который неявно предполагается многими ответами и комментариями:

Являются ли циклы while и рекурсивные функции эквивалентными?

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

Таким образом, тот факт, что « whileцикл является формой рекурсии», не противоречит тому факту, что «некоторые рекурсивные функции не могут быть выражены whileциклом».

Джорджио
источник
2
@morbidCode: примитивная рекурсия и μ-рекурсия - это формы рекурсии с конкретными ограничениями (или их отсутствием), которые изучаются, например, в теории вычислимости. Оказывается, что язык с одним FORциклом может вычислять ровно все примитивные рекурсивные функции, а язык с простым WHILEциклом может вычислять ровно все µ-рекурсивные функции (и оказывается, что µ-рекурсивные функции - это именно те функции, которые машина Тьюринга может вычислить). Или, если коротко: примитивная рекурсия и µ-рекурсия - это технические термины из математики / теории вычислимости.
Йорг Миттаг
2
Я думал, что «рекурсия» подразумевает вызов функции, в результате чего текущее состояние выполнения помещается в стек и т. Д .; поэтому у большинства машин есть практическое ограничение на количество уровней, которые вы можете пройти. В то время как у циклов нет таких ограничений, потому что они внутренне будут использовать что-то вроде «JMP» и не будут использовать стек. Просто мое понимание может быть ошибочным.
Джей
13
В этом ответе используется совершенно иное определение слова «рекурсивный», чем в ОП, и поэтому оно вводит в заблуждение.
Mooing Duck
2
@DavidGrinberg: Цитата: «Циклы C, C ++, Java for не являются примером примитивной рекурсии. Примитивная рекурсия означает, что максимальное число итераций / глубина рекурсии фиксируется до запуска цикла». Джорджио говорит о примитивах теории вычислимости . Не связано с языками программирования.
Mooing Duck
3
Я должен согласиться с Mooing Duck. Хотя теория вычислимости может быть интересна в теоретической CS, я думаю, что все согласны с тем, что OP говорил о концепции языков программирования.
Воу
2

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

Таким образом, цикл while (в тех языках, где они есть) может рассматриваться как завершающийся хвостовым вызовом его тела (или его тестом головы).

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

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

Таким образом, «рекурсия» и «итерация» глубоко эквивалентны.

Василий Старынкевич
источник
1

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

Принципиальное различие в программировании заключается в том, что рекурсия позволяет использовать данные, которые хранятся в стеке вызовов. Чтобы проиллюстрировать это, предположим, что вы хотите напечатать элементы односвязного списка, используя цикл или рекурсию. Я буду использовать 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);
}

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

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

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

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

ComicSansMS
источник
1
Конечно, вторая рекурсивная функция не является хвостовой рекурсивной - ее намного сложнее оптимизировать для пространства, поскольку вы не можете использовать TCO для повторного использования стека. Реализация двусвязного списка сделает оба алгоритма тривиальными в любом случае за счет пространства указателя / ссылки на элемент.
Балдрикк
@Baldrickk Забавная вещь в хвостовой рекурсии состоит в том, что в итоге вы получаете версию, которая намного ближе к тому, как выглядела бы версия цикла, поскольку она снова лишает вас возможности сохранять состояние в стеке вызовов. Решить его может двусвязный список, но изменение структуры данных часто не подходит при решении этой проблемы. Хотя приведенный здесь пример несколько искусственно ограничен, он иллюстрирует шаблон, который часто появляется на функциональных языках в контексте рекурсивных алгебраических типов.
ComicSansMS
Моя точка зрения заключалась в том, что если вы столкнетесь с этой проблемой, то это скорее связано с отсутствием функционального дизайна, чем с тем, какие языковые конструкции вы используете для его реализации, и у каждого выбора есть свои плюсы и минусы :)
Baldrickk
0

Циклы - это особая форма рекурсии для достижения конкретной задачи (в основном, итерации). Можно реализовать цикл в рекурсивном стиле с одинаковой производительностью [1] на нескольких языках. и в SICP [2], вы можете видеть, что петли описаны как «синтетический сахар». В большинстве обязательных языков программирования блоки for и while используют ту же область видимости, что и их родительская функция. Тем не менее, в большинстве функциональных языков программирования не существует ни циклов for, ни while, поскольку в них нет необходимости.

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

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

в следующем примере, функция life is принимает два параметра «rules» и «state», и новое состояние будет построено в следующий раз.

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

zeawee
источник
4
@Giorgio Не мое отрицательное мнение, а всего лишь предположение: я думаю, что большинство программистов считают, что рекурсия подразумевает рекурсивный вызов функции, потому что, в общем-то, это и есть рекурсия, вызывающая саму функцию. В цикле нет рекурсивного вызова функции. Поэтому говорить, что цикл без рекурсивного вызова функции является особой формой рекурсии, было бы явно неправильно, если идти по этому определению.
Хайд
1
Ну, может быть, если взглянуть на это с более абстрактной точки зрения, то, что кажется разными, на самом деле концептуально одинаково. Я нахожу довольно обескураживающим и грустным думать, что люди понижают ответы только потому, что они не соответствуют их ожиданиям вместо того, чтобы воспринимать их как возможность чему-то научиться. Все ответы, которые пытаются сказать: «Эй, смотри, эти вещи выглядят по-разному на поверхности, но на самом деле они одинаковы на более абстрактном уровне», были отвергнуты.
Джорджио
3
@ Georgio: Цель этого сайта - получить ответы на вопросы. Ответы, которые являются полезными и правильными, заслуживают положительных отзывов, ответы, которые вводят в заблуждение и бесполезны, заслуживают отрицательных ответов. Ответ, который тонко использует другое определение общего термина, не давая понять, какое другое определение используется , сбивает с толку и бесполезен. Ответы, которые имеют смысл только в том случае, если вы уже знаете, так сказать, ответ, не являются полезными и служат только для того, чтобы показать авторам превосходное понимание терминологии.
JacquesB
2
@JacquesB: «Ответы, которые имеют смысл только в том случае, если вы уже знаете ответ, так сказать, бесполезны ...»: Это также можно сказать об ответах, которые только подтверждают то, что читатель уже знает или думает знать. Если ответ вводит терминологию, которая не ясна, можно написать комментарии, чтобы запросить более подробную информацию, прежде чем понизить голосование.
Джорджио
4
Петли не являются специальной формой рекурсии. Посмотрите на теорию вычислимости и, например, теоретический язык WHILE и µ-исчисление. Да, некоторые языки используют циклы в качестве синтаксического сахара для фактического использования рекурсии за кулисами, но они могут это делать, потому что рекурсия и итерация одинаково выразительны , а не потому, что они одинаковы.
Полигном
-1

Цикл while отличается от рекурсии.

Когда вызывается функция, происходит следующее:

  1. Кадр стека добавляется в стек.

  2. Указатель кода перемещается в начало функции.

Когда цикл while заканчивается, происходит следующее:

  1. Условие спрашивает, правда ли что-то.

  2. Если это так, код переходит в точку.

В общем, цикл while похож на следующий псевдокод:

 if (x)

 {

      Jump_to(y);

 }

Самое главное, что рекурсия и циклы имеют разные представления кода сборки и представления машинного кода. Это означает, что они не одинаковы. Они могут иметь одинаковые результаты, но другой машинный код доказывает, что они не на 100% одно и то же.

Великая утка
источник
2
Вы говорите о реализации вызова процедуры и цикла while, и, поскольку они реализованы по-разному, вы заключаете, что они разные. Однако концептуально они очень похожи.
Джорджио
1
В зависимости от компилятора оптимизированный встроенный рекурсивный вызов вполне может создать ту же сборку, что и простой цикл.
Хайд
@hyde ... что является лишь примером общеизвестного факта, что одно можно выразить через другое; не значит, что они идентичны Немного похоже на массу и энергию. Конечно, можно утверждать, что все способы получения одинакового результата «одинаковы». Если бы мир был конечным, все программы были бы constexpr, в конце концов.
Питер - Восстановить Монику
@ Джорджио Нет, это логическое описание, а не реализация. Мы знаем, что две вещи эквивалентны ; но эквивалентность - это не идентичность , потому что вопрос (и ответы) - это именно то, как мы получаем результат, т.е. они обязательно содержат алгоритмические описания (которые могут быть выражены в терминах стека, переменной и т. д.).
Питер - Восстановить Монику
1
@ PeterA.Schneider Да, но в этом ответе говорится: «Самое главное ... другой код сборки», что не совсем верно.
Хайд
-1

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

Я не уверен, почему это противоречиво. Рекурсия и итерация со стеком - это один и тот же вычислительный процесс. Это одно и то же «явление», так сказать.

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

Чтобы уточнить, ответ на вопрос Является ли цикл while по сути своей рекурсией? является определенным нет , или, по крайней мере, «нет, если у вас также есть стек».

Дейв Кузино
источник