Одна из тем, которая, кажется, регулярно поднимается в списках рассылки и онлайн-обсуждениях, - это достоинства (или их отсутствие) получения степени по информатике. Аргумент, который, кажется, снова и снова возникает в пользу отрицательной стороны, состоит в том, что они кодировали уже несколько лет и никогда не использовали рекурсию.
Итак, вопрос:
- Что такое рекурсия?
- Когда я буду использовать рекурсию?
- Почему люди не используют рекурсию?
recursion
computer-science
Майк Минутилло
источник
источник
Ответы:
В этом потоке есть несколько хороших объяснений рекурсии , этот ответ о том, почему вы не должны использовать ее на большинстве языков. * В большинстве основных реализаций императивного языка (т.е. во всех основных реализациях C, C ++, Basic, Python , Ruby, Java и C #) итерация намного предпочтительнее рекурсии.
Чтобы понять, почему, пройдите по шагам, которые используются в указанных выше языках для вызова функции:
Выполнение всех этих шагов требует времени, обычно немного больше, чем требуется для прохождения цикла. Однако настоящая проблема находится на шаге №1. Когда многие программы запускаются, они выделяют один фрагмент памяти для своего стека, а когда у них заканчивается эта память (часто, но не всегда из-за рекурсии), программа вылетает из-за переполнения стека .
Таким образом, на этих языках рекурсия медленнее и делает вас уязвимыми для сбоев. Тем не менее, есть некоторые аргументы в пользу его использования. В общем, рекурсивный код становится короче и немного элегантнее, если вы знаете, как его читать.
Существует метод, который могут использовать разработчики языка, называемые оптимизацией хвостового вызова, которая может устранить некоторые классы переполнения стека. Короче говоря: если возвращаемое выражение функции является просто результатом вызова функции, тогда вам не нужно добавлять новый уровень в стек, вы можете повторно использовать текущий для вызываемой функции. К сожалению, немногие императивные языковые реализации имеют встроенную оптимизацию хвостового вызова.
* Я люблю рекурсию. Мой любимый статический язык вообще не использует циклы, рекурсия - единственный способ делать что-то многократно. Я просто не думаю, что рекурсия вообще хорошая идея для языков, которые для нее не настроены.
** Между прочим, Марио, типичное имя для вашей функции ArrangeString - «join», и я был бы удивлен, если на выбранном вами языке еще нет ее реализации.
источник
Простой английский пример рекурсии.
источник
В самом общем смысле информатики рекурсия - это функция, которая вызывает сама себя. Скажем, у вас есть связанная структура списка:
И вы хотите узнать, какой длины есть связанный список, вы можете сделать это с помощью рекурсии:
(Это, конечно, можно сделать и с помощью цикла for, но это полезно в качестве иллюстрации концепции)
источник
length(list->next)
все еще нужно вернуться к,length(list)
чтобы последний мог добавить 1 к результату. Если бы оно было написано так, чтобы передать всю длину, только тогда мы могли бы забыть о существовании вызывающего. Нравитсяint length(const Node* list, int count=0) { return (!list) ? count : length(list->next, count + 1); }
.Всякий раз, когда функция вызывает саму себя, создавая цикл, возникает рекурсия. Как и все, у рекурсии есть хорошие и плохие применения.
Самый простой пример - хвостовая рекурсия, где самая последняя строка функции - это вызов самой себя:
Однако это хромой, почти бессмысленный пример, потому что его можно легко заменить более эффективной итерацией. В конце концов, рекурсия страдает от накладных расходов на вызов функции, которые в приведенном выше примере могут быть существенными по сравнению с операцией внутри самой функции.
Таким образом, вся причина, по которой нужно делать рекурсию, а не итерацию, должна заключаться в том, чтобы использовать стек вызовов для выполнения некоторых умных вещей. Например, если вы вызываете функцию несколько раз с разными параметрами внутри одного цикла, это способ выполнить ветвление . Классический пример - треугольник Серпинского .
Вы можете очень просто нарисовать один из них с помощью рекурсии, где стек вызовов разветвляется в трех направлениях:
Если вы попытаетесь сделать то же самое с итерацией, я думаю, вы обнаружите, что для выполнения требуется намного больше кода.
Другие распространенные варианты использования могут включать обход иерархий, например поисковые роботы веб-сайтов, сравнение каталогов и т. Д.
Вывод
На практике рекурсия имеет наибольший смысл, когда вам нужно итеративное ветвление.
источник
Рекурсия - это метод решения проблем, основанный на менталитете «разделяй и властвуй». Основная идея состоит в том, что вы берете исходную проблему и разделяете ее на более мелкие (более легко решаемые) экземпляры самой себя, решаете эти более мелкие экземпляры (обычно снова используя тот же алгоритм), а затем повторно собираете их в окончательное решение.
Канонический пример - это процедура создания факториала числа n. Факториал n вычисляется путем умножения всех чисел от 1 до n. Итеративное решение на C # выглядит так:
В итеративном решении нет ничего удивительного, и оно должно иметь смысл для всех, кто знаком с C #.
Рекурсивное решение находится путем распознавания того, что n-й факториал равен n * Fact (n-1). Или, говоря другими словами, если вы знаете, что такое конкретное факториальное число, вы можете вычислить следующее. Вот рекурсивное решение на C #:
Первая часть этой функции известна как базовый случай (или иногда защитная оговорка), и именно она предотвращает выполнение алгоритма вечно. Он просто возвращает значение 1 всякий раз, когда функция вызывается со значением 1 или меньше. Вторая часть более интересна и известна как рекурсивный шаг . Здесь мы вызываем тот же метод с немного измененным параметром (мы уменьшаем его на 1), а затем умножаем результат на нашу копию n.
При первом обнаружении это может сбить с толку, поэтому поучительно изучить, как это работает при запуске. Представьте, что мы вызываем FactRec (5). Мы входим в рутину, базовый случай не подхватывает нас, и в итоге мы получаем следующее:
Если мы повторно войдем в метод с параметром 4, мы снова не остановимся из-за предложения защиты, и поэтому мы окажемся на:
Если мы заменим это возвращаемое значение на возвращаемое значение выше, мы получим
Это должно дать вам представление о том, как прийти к окончательному решению, поэтому мы быстро проследим и покажем каждый шаг на пути вниз:
Эта последняя замена происходит при срабатывании базового варианта. На данный момент у нас есть простая алгебраическая формула для решения, которая, в первую очередь, напрямую приравнивается к определению факториалов.
Поучительно отметить, что каждый вызов метода приводит либо к срабатыванию базового случая, либо к вызову того же метода, параметры которого ближе к базовому варианту (часто называемый рекурсивным вызовом). Если это не так, метод будет работать вечно.
источник
FactRec()
должен быть умножен наn
перед возвратом.Рекурсия - это решение проблемы с функцией, которая вызывает сама себя. Хорошим примером этого является факториальная функция. Факториал - это математическая задача, в которой факториал 5, например, равен 5 * 4 * 3 * 2 * 1. Эта функция решает эту проблему в C # для положительных целых чисел (не проверено - может быть ошибка).
источник
Рекурсия относится к методу, который решает проблему, решая меньшую версию проблемы, а затем используя этот результат плюс некоторые другие вычисления для формулирования ответа на исходную проблему. Часто в процессе решения меньшей версии метод решает еще меньшую версию проблемы и так далее, пока не будет достигнут «базовый случай», который легко решить.
Например, чтобы вычислить факториал числа
X
, его можно представить какX times the factorial of X-1
. Таким образом, метод «рекурсивно повторяет», чтобы найти факториалX-1
, а затем умножает полученное значение,X
чтобы дать окончательный ответ. Конечно, чтобы найти факториалX-1
, он сначала вычислит факториалX-2
и так далее. Базовым случаем будетX
0 или 1, и в этом случае он знает, что нужно вернуть,1
поскольку0! = 1! = 1
.источник
Рассмотрим старую, хорошо известную проблему :
Определение gcd на удивление простое:
где mod - оператор по модулю (то есть остаток после целочисленного деления).
На английском языке это определение гласит, что наибольший общий делитель любого числа и ноль - это это число, а наибольший общий делитель двух чисел m и n - наибольший общий делитель числа n и остатка после деления m на n .
Если вы хотите узнать, почему это работает, см. Статью в Википедии об алгоритме Евклида .
Давайте в качестве примера вычислим gcd (10, 8). Каждый шаг равен предыдущему:
На первом этапе 8 не равно нулю, поэтому применяется вторая часть определения. 10 mod 8 = 2, потому что 8 переходит в 10 один раз с остатком 2. На шаге 3 снова применяется вторая часть, но на этот раз 8 mod 2 = 0, потому что 2 делит 8 без остатка. На шаге 5 второй аргумент равен 0, поэтому ответ равен 2.
Вы заметили, что gcd появляется как слева, так и справа от знака равенства? Математик сказал бы, что это определение является рекурсивным, потому что определяемое вами выражение повторяется внутри его определения.
Рекурсивные определения обычно выглядят элегантно. Например, рекурсивное определение суммы списка:
где
head
- первый элемент в списке, аtail
это остальная часть списка. Обратите внимание, чтоsum
повторяется внутри определения в конце.Возможно, вы вместо этого предпочтете максимальное значение в списке:
Вы можете определить умножение неотрицательных целых чисел рекурсивно, чтобы превратить его в серию сложений:
Если этот рассказ о преобразовании умножения в серию сложений не имеет смысла, попробуйте расширить несколько простых примеров, чтобы увидеть, как это работает.
Сортировка слиянием имеет прекрасное рекурсивное определение:
Рекурсивные определения есть повсюду, если вы знаете, что искать. Обратите внимание, что все эти определения имеют очень простые базовые случаи, например , gcd (m, 0) = m. Рекурсивные случаи сводят на нет проблему, чтобы перейти к простым ответам.
Понимая это, теперь вы можете оценить другие алгоритмы в статье Википедии о рекурсии !
источник
Канонический пример - факториал, который выглядит так:
В общем, рекурсия не обязательно быстрая (накладные расходы на вызов функции имеют тенденцию быть высокими, потому что рекурсивные функции имеют тенденцию быть небольшими, см. Выше) и могут страдать от некоторых проблем (у кого-нибудь есть переполнение стека?). Некоторые говорят, что их трудно получить «правильно» в нетривиальных случаях, но я на это не верю. В некоторых ситуациях рекурсия имеет наибольший смысл и является наиболее элегантным и понятным способом написания конкретной функции. Следует отметить, что некоторые языки предпочитают рекурсивные решения и гораздо больше их оптимизируют (на ум приходит LISP).
источник
Рекурсивная функция - это функция, которая вызывает сама себя. Самая частая причина, по которой я его использую, - это обход древовидной структуры. Например, если у меня есть TreeView с флажками (подумайте об установке новой программы, странице «выберите компоненты для установки»), мне может понадобиться кнопка «проверить все», которая будет примерно такой (псевдокод):
Итак, вы можете видеть, что checkRecursively сначала проверяет переданный узел, а затем вызывает себя для каждого из дочерних узлов этого узла.
С рекурсией нужно быть немного осторожнее. Если вы попадете в бесконечный рекурсивный цикл, вы получите исключение Stack Overflow :)
Я не могу придумать причину, по которой люди не должны использовать его, когда это необходимо. В одних обстоятельствах это полезно, в других - нет.
Я думаю, что из-за того, что это интересная техника, некоторые программисты, возможно, в конечном итоге будут использовать ее чаще, чем следовало бы, без реального оправдания. Это дало рекурсию плохую репутацию в некоторых кругах.
источник
Рекурсия - это выражение, прямо или косвенно ссылающееся на себя.
Рассмотрим в качестве простого примера рекурсивные сокращения:
Больше примеров в Википедии
источник
Рекурсия лучше всего работает с тем, что я называю «фрактальными проблемами», когда вы имеете дело с большой проблемой, состоящей из меньших версий этой большой вещи, каждая из которых является еще меньшей версией большой вещи, и так далее. Если вам когда-либо придется перемещаться или искать что-то вроде дерева или вложенных идентичных структур, у вас есть проблема, которая может быть хорошим кандидатом для рекурсии.
Люди избегают рекурсии по ряду причин:
Большинство людей (в том числе и я) нарезают свои зубы программированием именно на процедурном или объектно-ориентированном программировании, а не на функциональном программировании. Таким людям итеративный подход (обычно с использованием циклов) кажется более естественным.
Тем из нас, кто нарезал себе зубы на процедурное или объектно-ориентированное программирование, часто советуют избегать рекурсии, поскольку она подвержена ошибкам.
Нам часто говорят, что рекурсия медленная. Вызов и возврат из подпрограммы многократно включают в себя большое количество выталкиваний и выталкиваний стека, что медленнее, чем цикл. Я думаю, что некоторые языки справляются с этим лучше, чем другие, и эти языки, скорее всего, не те, в которых доминирующая парадигма является процедурной или объектно-ориентированной.
По крайней мере, для пары языков программирования, которые я использовал, я помню, что слышал рекомендации не использовать рекурсию, если она выходит за пределы определенной глубины, потому что ее стек не такой глубокий.
источник
Рекурсивный оператор - это оператор, в котором вы определяете процесс того, что делать дальше, как комбинацию входных данных и того, что вы уже сделали.
Например, возьмите факториал:
Но легко увидеть, что факториал (6) также:
Итак, в общем:
Конечно, сложность рекурсии заключается в том, что если вы хотите определить вещи в терминах того, что вы уже сделали, нужно с чего начать.
В этом примере мы просто делаем особый случай, определяя factorial (1) = 1.
Теперь мы видим это снизу вверх:
Поскольку мы определили factorial (1) = 1, мы достигаем «дна».
Вообще говоря, рекурсивные процедуры состоят из двух частей:
1) Рекурсивная часть, которая определяет некоторую процедуру с точки зрения новых входных данных в сочетании с тем, что вы «уже сделали» с помощью той же процедуры. (т.е.
factorial(n) = n*factorial(n-1)
)2) Базовая часть, которая гарантирует, что процесс не повторяется вечно, давая ему место для начала (т.е.
factorial(1) = 1
)Сначала может показаться немного запутанным, но просто посмотрите на кучу примеров, и все должно получиться. Если вы хотите глубже понять эту концепцию, изучите математическую индукцию. Кроме того, имейте в виду, что некоторые языки оптимизируют рекурсивные вызовы, а другие - нет. Довольно легко сделать безумно медленные рекурсивные функции, если вы не будете осторожны, но есть также методы, чтобы сделать их работоспособными в большинстве случаев.
Надеюсь это поможет...
источник
Мне нравится это определение:
в рекурсии процедура сама решает небольшую часть проблемы, делит проблему на более мелкие части, а затем вызывает себя для решения каждой из более мелких частей.
Мне также нравится обсуждение Стивом МакКоннеллом рекурсии в Code Complete, где он критикует примеры, используемые в книгах по информатике по рекурсии.
Я подумал, что это очень интересный вопрос, который может быть причиной того, что рекурсию часто неправильно понимают.
РЕДАКТИРОВАТЬ: Это не было копанием в ответе Дава - я не видел этого ответа, когда размещал это
источник
1.) Метод рекурсивен, если он может вызывать сам себя; либо напрямую:
или косвенно:
2.) Когда использовать рекурсию
3.) Люди используют рекурсию только тогда, когда очень сложно написать итеративный код. Например, методы обхода дерева, такие как предварительный и последующий порядок, могут быть как итеративными, так и рекурсивными. Но обычно мы используем рекурсию из-за ее простоты.
источник
Вот простой пример: сколько элементов в наборе. (есть способы посчитать лучше, но это хороший простой рекурсивный пример.)
Во-первых, нам понадобятся два правила:
Предположим, у вас есть такой набор: [xxx]. посчитаем, сколько там предметов.
Мы можем представить это как:
При применении рекурсивного решения у вас обычно есть как минимум 2 правила:
Если перевести приведенное выше в псевдокод, мы получим:
Есть гораздо больше полезных примеров (например, обход дерева), которые, я уверен, другие люди охватят.
источник
Что ж, у вас есть довольно приличное определение. И в Википедии тоже есть хорошее определение. Так что я добавлю вам еще одно (возможно, худшее) определение.
Когда люди говорят о «рекурсии», они обычно говорят о написанной ими функции, которая вызывает себя неоднократно, пока не завершит свою работу. Рекурсия может быть полезна при обходе иерархий в структурах данных.
источник
Пример: рекурсивное определение лестницы: Лестница состоит из: - одной ступени и лестницы (рекурсия) - или только одной ступени (завершение)
источник
Чтобы вернуться к решенной проблеме: ничего не делайте, все готово.
Чтобы вернуться к открытой проблеме: выполните следующий шаг, а затем повторите все остальное.
источник
Проще говоря: предположим, вы можете делать 3 вещи:
Перед вами на столе много яблок, и вы хотите знать, сколько там яблок.
Процесс повторения одного и того же, пока вы не закончите, называется рекурсией.
Я надеюсь, что это именно тот ответ на "простом английском", который вы ищете!
источник
Рекурсивная функция - это функция, которая вызывает саму себя. Рекурсивная структура - это структура, которая содержит экземпляр самой себя. Вы можете объединить два как рекурсивный класс. Ключевой частью рекурсивного элемента является то, что он содержит экземпляр / вызов самого себя.
Представьте, что два зеркала обращены друг к другу. Мы видели аккуратный эффект бесконечности, который они производят. Каждое отражение является экземпляром зеркала, которое содержится в другом экземпляре зеркала и т. Д. Зеркало, содержащее собственное отражение, является рекурсией.
Бинарное дерево является хорошим примером программирования рекурсии. Структура рекурсивна, и каждый узел содержит 2 экземпляра узла. Функции для работы с двоичным деревом поиска также рекурсивны.
источник
Это старый вопрос, но я хочу добавить ответ с точки зрения логистики (т.е. не с точки зрения корректности алгоритма или точки зрения производительности).
Я использую Java для работы, а Java не поддерживает вложенные функции. Таким образом, если я хочу выполнить рекурсию, мне, возможно, придется определить внешнюю функцию (которая существует только потому, что мой код натыкается на бюрократическое правило Java), или мне, возможно, придется полностью реорганизовать код (что я действительно ненавижу делать).
Таким образом, я часто избегаю рекурсии и вместо этого использую операцию стека, потому что сама рекурсия по сути является операцией стека.
источник
Вы хотите использовать его в любое время, когда у вас есть древовидная структура. Это очень полезно при чтении XML.
источник
Рекурсия применительно к программированию - это, по сути, вызов функции из ее собственного определения (внутри себя) с различными параметрами для выполнения задачи.
источник
«Если у меня есть молоток, пусть все будет похоже на гвоздь».
Рекурсия - это стратегия решения огромных проблем, где на каждом шагу просто «превращайте две маленькие вещи в одну большую» каждый раз одним и тем же молотком.
пример
Предположим, ваш стол завален беспорядочной беспорядком из 1024 бумаг. Как с помощью рекурсии сделать одну аккуратную чистую стопку бумаг из беспорядка?
Обратите внимание, что это довольно интуитивно понятно, не считая подсчета всего (что не является строго необходимым). На самом деле вы не можете полностью опуститься до стопки по 1 листу, но вы можете, и это все равно будет работать. Важная часть - это молоток: руками вы всегда можете положить одну стопку на другую, чтобы получилась большая стопка, и не имеет значения (в пределах разумного), насколько велика каждая стопка.
источник
Рекурсия - это процесс, в котором метод вызывает сам по себе, чтобы иметь возможность выполнить определенную задачу. Это уменьшает повторяемость кода. Большинство рекурсивных функций или методов должны иметь условие, чтобы прервать рекурсивный вызов, то есть остановить его от вызова самого себя, если условие выполнено - это предотвращает создание бесконечного цикла. Не все функции подходят для рекурсивного использования.
источник
эй, извините, если мое мнение с кем-то согласуется, я просто пытаюсь объяснить рекурсию простым английским языком.
Предположим, у вас есть три менеджера - Джек, Джон и Морган. Джек управляет двумя программистами, Джон - тремя, а Морган - 5. Вы собираетесь дать каждому менеджеру по 300 долларов и хотите знать, сколько это будет стоить. Ответ очевиден - а что, если двое сотрудников Моргана также являются менеджерами?
ЗДЕСЬ идет рекурсия. вы начинаете с вершины иерархии. летняя стоимость 0 $. вы начинаете с Джека, а затем проверьте, есть ли у него менеджеры в качестве сотрудников. если вы обнаружите, что кто-то из них есть, проверьте, есть ли у них менеджеры в качестве сотрудников и так далее. Добавляйте 300 $ к летней стоимости каждый раз, когда найдете менеджера. Когда вы закончите с Джеком, идите к Джону, его сотрудникам, а затем к Моргану.
Вы никогда не узнаете, сколько циклов вы пройдете, прежде чем получите ответ, хотя вы знаете, сколько у вас менеджеров и сколько бюджета вы можете потратить.
Рекурсия - это дерево с ветвями и листьями, которые называются родителями и потомками соответственно. Когда вы используете алгоритм рекурсии, вы более или менее сознательно строите дерево из данных.
источник
На простом английском языке рекурсия означает повторение чего-то снова и снова.
В программировании один пример - вызов функции внутри себя.
Посмотрите на следующий пример вычисления факториала числа:
источник
Любой алгоритм демонстрирует структурную рекурсию для типа данных, если в основном состоит из оператора switch с регистром для каждого случая типа данных.
например, когда вы работаете над шрифтом
структурный рекурсивный алгоритм будет иметь вид
это действительно самый очевидный способ написать любой алгоритм, который работает со структурой данных.
теперь, когда вы смотрите на целые числа (ну, натуральные числа), как они определены с помощью аксиом Пеано
вы видите, что структурный рекурсивный алгоритм для целых чисел выглядит так
слишком хорошо известная факториальная функция - один из самых тривиальных примеров такой формы.
источник
функция вызывает сама себя или использует собственное определение.
источник