Понимание того, как работают рекурсивные функции
как объясняет название, у меня есть очень фундаментальный вопрос программирования, который я просто еще не смог грокнуть. Отфильтровывая все (чрезвычайно умные) "для того, чтобы понять рекурсию, вы должны сначала понять рекурсию."ответы из различных онлайн-потоков я все еще не совсем понимаю.
понимая, что когда мы сталкиваемся с незнанием того, чего мы не знаем, мы можем задавать неправильные вопросы или неправильно задавать правильные вопросы, я поделюсь тем, что я "подумайте" мой вопрос в надежде, что кто-то с подобным мировоззрением может поделиться некоторыми знаниями, которые помогут включить рекурсивную лампочку для меня!
вот функция (синтаксис написан на Swift):
func sumInts(a: Int, b: Int) -> Int {
if (a > b) {
return 0
} else {
return a + sumInts(a: a + 1, b: b)
}
}
мы будем использовать 2 и 5 в качестве аргументов:
println(sumInts(a: 2, b: 5))
очевидно, что ответ 14. Но я не совсем понимаю, как эта ценность достигается.
это мои заморочки 2:
функция вызывается рекурсивно, пока не будет выполнено условие. Что условие а > б. Когда это условие выполняется, то возвращает 0. На первый взгляд, я ожидал бы, что возвращаемое значение будет 0, что явно неверно.
распечатка значения ' a ' на каждой итерации дает значение, которое я ожидал бы: 2, 3, 4, 5 (в этот момент 5+1 > b, который соответствует первому условию: a > b), но я все еще не вижу, как достигается значение 14.
моя первая мысль что-то похожее на следующее происходит волшебным образом:
var answer = a;
answer += a+1 until a > b;
return answer;
таким образом, исключив магию, я просто не получаю что-то. Я хотел бы понять, что происходит больше, чем просто неявно.
если бы кто-нибудь мог любезно объяснить, что технически происходит во время такого рода функции и почему результат не равен 0 и как, в конце концов,a + sumInts(a: a + 1, b: b) = 14
, Я буду вечно в долгу.
17 ответов:
Я думаю путаница проистекает из мысли о том, что "одна и та же функция" вызывается много раз. Если вы думаете об этом как "вызывается много копий одной и той же функции", то это может быть яснее:
только одна копия функции всегда возвращает 0, и это не первый (он же последний). Таким образом, результат вызова первого не равен 0.
для второго бита путаницы, я думаю, что будет легче изложить рекурсию на английском языке. Прочитайте эту строку:
return a + sumInts(a + 1, b: b)
как "возвращать значение" а " плюс (возвращаемое значение другой экземпляр функции, которая есть копия значений 'а' плюс (возвращаемое значение другой экземпляр функции, которая является второй копии значение 'а' плюс (...", с каждой копией функции порождающей новую копию самой себя с увеличением на 1, пока условие a > b не будет выполнено.
к тому времени, когда вы достигнете условия A > b, которое истинно, у вас есть a (потенциально произвольно) длинный стек копий функции все в середине выполнения, все ждут результата следующей копии, чтобы узнать, что они должны добавить к 'a'.
(edit: кроме того, следует помнить, что стек копий функции, о которой я упоминаю, является реальной вещью, которая занимает реальную память и приведет к сбою вашей программы, если она станет слишком большой. Компилятор может оптимизировать его в некоторых случаях, но исчерпание пространства стека является значительным и неудачным ограничением рекурсивных функций на многих языках)
1.Функция вызывается рекурсивно до тех пор, пока не будет выполнено условие. Это условие
a > b
. При выполнении этого условия возвращает 0. На первый взгляд, я ожидал бы, что возвращаемое значение будет 0, что явно неверно.вот что такое компьютерные вычисления
sumInts(2,5)
подумал бы, если бы он мог:I want to compute sumInts(2, 5) for this, I need to compute sumInts(3, 5) and add 2 to the result. I want to compute sumInts(3, 5) for this, I need to compute sumInts(4, 5) and add 3 to the result. I want to compute sumInts(4, 5) for this, I need to compute sumInts(5, 5) and add 4 to the result. I want to compute sumInts(5, 5) for this, I need to compute sumInts(6, 5) and add 5 to the result. I want to compute sumInts(6, 5) since 6 > 5, this is zero. The computation yielded 0, therefore I shall return 5 = 5 + 0. The computation yielded 5, therefore I shall return 9 = 4 + 5. The computation yielded 9, therefore I shall return 12 = 3 + 9. The computation yielded 12, therefore I shall return 14 = 2 + 12.
как видите, некоторые вызывают функцию
sumInts
на самом деле возвращает 0, однако это не окончательное значение, потому что компьютер по-прежнему имеет чтобы добавить 5 к этому 0, затем 4 к результату, затем 3, затем 2, как описано в четырех последних предложениях мыслей нашего компьютера. Обратите внимание, что в рекурсии компьютер должен не только вычислить рекурсивный вызов, но и запомнить, что делать со значением, возвращенным рекурсивным вызовом. Существует специальная область памяти компьютера под названием стек где этот вид информации сохраняется, это пространство ограничено и функции, которые являются слишком рекурсивными может исчерпать стек: это переполнение стека давая свое название нашему самому любимому сайту.ваше утверждение, кажется, делает неявное предположение, что компьютер забывает, что он был при выполнении рекурсивного вызова, но это не так, поэтому ваш вывод не соответствует вашим наблюдениям.
2.Распечатка значения 'a' на каждой итерации дает значение, которое я ожидал бы: 2, 3, 4, 5 (в этот момент 5+1 > b, который соответствует первому условие: a > b) но я все еще не вижу, как достигается значение 14.
это потому, что возвращаемое значение не является
a
сам но сумма значенияa
и значение, возвращаемое рекурсивным вызовом.
чтобы понять рекурсию, вы должны думать о проблеме по-другому. Вместо большой логической последовательности шагов, которая имеет смысл в целом, вы вместо этого берете большую проблему и разбиваетесь на более мелкие проблемы и решаете их, как только у вас есть ответ на подзадачи, вы объединяете результаты подзадач, чтобы решить большую проблему. Подумайте о том, что вам и вашим друзьям нужно подсчитать количество шариков в огромном ведре. Вы делаете каждый взять меньшее ведро и пойти подсчитать их по отдельности, и когда вы закончите вы добавляете итоги вместе.. Ну а теперь, если каждый из вас найдет своего друга и разделит ведра дальше, то вам просто нужно подождать, пока эти другие друзья выяснят свои итоги, вернут их каждому из вас, вы сложите их. И так далее. Особый случай - когда вы получаете только 1 мрамор для подсчета, тогда вы просто возвращаете его обратно и говорите 1. пусть другие люди выше вас сделать добавление вы сделали.
вы должны помнить каждый раз функция вызывает себя рекурсивно, она создает новый контекст с подмножеством проблемы, как только эта часть будет решена, она возвращается, чтобы предыдущая итерация могла завершиться.
позвольте мне показать вам действия:
sumInts(a: 2, b: 5) will return: 2 + sumInts(a: 3, b: 5) sumInts(a: 3, b: 5) will return: 3 + sumInts(a: 4, b: 5) sumInts(a: 4, b: 5) will return: 4 + sumInts(a: 5, b: 5) sumInts(a: 5, b: 5) will return: 5 + sumInts(a: 6, b: 5) sumInts(a: 6, b: 5) will return: 0
после sumInts(гостей: 6, Б: 5) выполняется, то результаты могут быть получены, возвращаясь по цепочке с результатами, которые вы получаете:
sumInts(a: 6, b: 5) = 0 sumInts(a: 5, b: 5) = 5 + 0 = 5 sumInts(a: 4, b: 5) = 4 + 5 = 9 sumInts(a: 3, b: 5) = 3 + 9 = 12 sumInts(a: 2, b: 5) = 2 + 12 = 14.
другой способ представления структуры рекурсия:
sumInts(a: 2, b: 5) = 2 + sumInts(a: 3, b: 5) sumInts(a: 2, b: 5) = 2 + 3 + sumInts(a: 4, b: 5) sumInts(a: 2, b: 5) = 2 + 3 + 4 + sumInts(a: 5, b: 5) sumInts(a: 2, b: 5) = 2 + 3 + 4 + 5 + sumInts(a: 6, b: 5) sumInts(a: 2, b: 5) = 2 + 3 + 4 + 5 + 0 sumInts(a: 2, b: 5) = 14
рекурсия-сложная тема для понимания, и я не думаю, что могу полностью отдать ей должное здесь. Вместо этого я попытаюсь сосредоточиться на конкретном фрагменте кода, который у вас есть здесь, и попытаться описать как интуицию, почему решение работает, так и механику того, как код вычисляет свой результат.
приведенный здесь код решает следующую проблему: вы хотите знать сумму всех целых чисел от a до b включительно. Для вашего примера вы хотите получить сумму чисел от 2 до 5 включительно, что составляет
2 + 3 + 4 + 5
2 + (3 + 4 + 5)
здесь, (3 + 4 + 5) бывает сумма всех целых чисел от 3 до 5 включительно. Другими словами, если вы хотите знать сумму всех целых чисел между 2 и 5, Начните с вычисления суммы всех целых чисел между 3 и 5, затем добавьте 2.
так как же вычислить сумму всех целых чисел от 3 до 5 включительно? Ну, эта сумма
3 + 4 + 5
что можно придумать вместо этого как
3 + (4 + 5)
здесь! Если вы хотите вычислить сумму целых чисел между a и b включительно, вы можете сделать следующее. Во-первых, вычислить сумму целых чисел между a + 1 и B включительно. Затем добавьте к этой сумме. Вы заметите, что" вычислить сумму целых чисел между a + 1 и b, включительно", оказывается, в значительной степени той же проблемой, которую мы уже пытаемся решить, но с немного другими параметрами. Вместо вычисления от a до b включительно, мы вычисляем от A + 1 до b включительно. Это рекурсивный шаг-чтобы решить большую проблему ("сумма от a до b, включительно"), мы сводим проблему к меньшей версии самой себя ("сумма от a + От 1 до b включительно.").
если вы посмотрите на код выше, вы заметите, что этот шаг в это:
return a + sumInts(a + 1, b: b)
этот код является просто переводом приведенной выше логики - если вы хотите суммировать от a до b, включительно, начните с суммирования a + 1 до b, включительно (это рекурсивный вызов
sumInt
s), затем добавитьa
.в рекурсивном решении проблем должен быть какой-то способ остановить упрощение проблема и вместо этого просто пойти решить ее напрямую. Как правило, вы найдете простой случай, когда ответ может быть определен немедленно, а затем структурируйте свое решение, чтобы решить простые случаи непосредственно, когда они возникают. Это обычно называется базовый или рекурсивные основе.
Итак, каков базовый случай в этой конкретной проблеме? Когда вы суммируете целые числа от a до b включительно, если a больше b, то ответ 0-там нет никаких чисел в диапазоне! Поэтому мы структурируем наше решение следующим образом:
- если a > b, то ответ-0.
- в противном случае (a ≤ b), получить ответ следующим образом:
- вычислить сумму целых чисел между a + 1 и b.
- добавьте a, чтобы получить ответ.
теперь сравните этот псевдокод с вашим фактическим кодом:
func sumInts(a: Int, b: Int) -> Int { if (a > b) { return 0 } else { return a + sumInts(a + 1, b: b) } }
обратите внимание, что есть почти точно взаимно однозначное отображение между решением, описанным в псевдокоде, и этим фактическим кодом. Первый шаг-это базовый случай-в случае, если вы просите сумму пустого диапазона чисел, вы получаете 0. В противном случае вычислите сумму между a + 1 и b, а затем добавьте a.
до сих пор я дал только идею высокого уровня за кодом. Но у вас были еще два очень хороших вопроса. Во-первых, почему это не всегда возвращает 0, учитывая, что функция говорит, чтобы вернуть 0, если a > b? Во-вторых, где же 14 на самом деле пришли? Давайте посмотрим на них по очереди.
давайте попробуем очень простой случай. Что произойдет, если вы позвоните
sumInts(6, 5)
? В этом случае, прослеживая код, вы видите, что функция просто возвращает 0. Это правильно, что в диапазоне нет никаких чисел. А теперь попробуй что-нибудь получше. Что происходит, когда вы звонитеsumInts(5, 5)
? Ну, вот что происходит:
- вы называете
sumInts(5, 5)
. Мы попадаем вelse
ветвь, которая возвращает значение `a + sumInts(6, 5).- для того чтобы
sumInts(5, 5)
чтобы определить, какиеsumInts(6, 5)
это, нам нужно приостановить то, что мы делаем, и позвонитьsumInts(6, 5)
.sumInts(6, 5)
вызывается. Он входит вif
филиала и возвращает0
. Однако, этот экземплярsumInts
позвонилаsumInts(5, 5)
, поэтому возвращаемое значение передается обратно вsumInts(5, 5)
, не для вызывающего абонента верхнего уровня.sumInts(5, 5)
теперь можем вычислить5 + sumInts(6, 5)
вернуться5
. Затем он возвращает его звонящий с верхнего уровня.обратите внимание, как здесь было сформировано значение 5. Мы начали с одного активного вызова
sumInts
. Это вызвало еще один рекурсивный вызов, и значение, возвращенное этим вызовом, передало информацию обратноsumInts(5, 5)
. Звонок вsumInts(5, 5)
затем, в свою очередь, сделал некоторые вычисления и возвращает значение в вызывающую программу.если вы попробуете это с
sumInts(4, 5)
, вот что произойдет:
sumInts(4, 5)
пытается вернуть4 + sumInts(5, 5)
. Для этого он вызываетsumInts(5, 5)
.
sumInts(5, 5)
пытается вернуть5 + sumInts(6, 5)
. Для этого он вызываетsumInts(6, 5)
.sumInts(6, 5)
возвращает 0 обратно вsumInts(5, 5).</li> <li>
sumInts(5, 5)now has a value for
sumInts(6, 5), namely 0. It then returns
5 + 0 = 5`.- имеет значение
sumInts(5, 5)
, а именно 5. Затем он возвращает4 + 5 = 9
.другими словами, значение, возвращаемое формируется путем суммирования значений по одному за раз, каждый раз принимая одно значение возвращается определенным рекурсивным вызовом
sumInts
и добавление текущего значенияa
. Когда рекурсия заканчивается, самый глубокий вызов возвращает 0. Однако это значение не сразу выходит из рекурсивной цепочки вызовов; вместо этого оно просто возвращает значение рекурсивному вызову на один слой выше него. Таким образом, каждый рекурсивный вызов просто добавляет еще одно число и возвращает его выше в цепочке, завершаясь общим суммированием. В качестве упражнения попробуйте проследить за этимsumInts(2, 5)
, С чего вы и хотели начать.надеюсь, что это помогает!
у вас есть несколько хороших ответов здесь до сих пор, но я добавлю еще один, который принимает другую тактику.
во-первых, я написал много статей о простых рекурсивных алгоритмах, которые вы можете найти интересными; см.
http://ericlippert.com/tag/recursion/
http://blogs.msdn.com/b/ericlippert/archive/tags/recursion/
те в самом новом-на-верхнем заказе, поэтому начните от дно.
активация функции. Что каждый, каждый вызов делает новый активация, и рекурсивный вызов выполняется в контексте этой активации. Это хороший способ думать об этом, но есть и другой, эквивалентный способ:smart text seach-and-replace.позвольте мне переписать вашу функцию в немного более компактную форму; не думайте это как в каком-то конкретном языке.
s = (a, b) => a > b ? 0 : a + s(a + 1, b)
я надеюсь, что имеет смысл. Если вы не знакомы с условным оператором, он имеет вид
condition ? consequence : alternative
и это значение станет ясным.теперь мы хотим оценить
s(2,5)
мы делаем это, делая текстовую замену вызова с телом функции, а затем заменитьa
С2
иb
С5
:s(2, 5) ---> 2 > 5 ? 0 : 2 + s(2 + 1, 5)
теперь оцените условное. Мы текстуально заменяем
2 > 5
Сfalse
.---> false ? 0 : 2 + s(2 + 1, 5)
теперь текстуально замените все ложные условные обозначения альтернативой и все истинные условные обозначения следствием. У нас есть только ложные условности, поэтому мы текстуально замените это выражение альтернативой:
---> 2 + s(2 + 1, 5)
теперь, чтобы спасти меня все
+
знаки, текстуально заменяют постоянную арифметику на ее значение. (Это немного чит, но я не хочу, чтобы отслеживать все круглые скобки!)---> 2 + s(3, 5)
теперь поиск и замену, на этот раз с телом для вызова
3
наa
и5
для b. мы поставим замену для вызова в скобках:---> 2 + (3 > 5 ? 0 : 3 + s(3 + 1, 5))
и теперь мы просто продолжаем делать те же самые шаги текстовой замены:
---> 2 + (false ? 0 : 3 + s(3 + 1, 5)) ---> 2 + (3 + s(3 + 1, 5)) ---> 2 + (3 + s(4, 5)) ---> 2 + (3 + (4 > 5 ? 0 : 4 + s(4 + 1, 5))) ---> 2 + (3 + (false ? 0 : 4 + s(4 + 1, 5))) ---> 2 + (3 + (4 + s(4 + 1, 5))) ---> 2 + (3 + (4 + s(5, 5))) ---> 2 + (3 + (4 + (5 > 5 ? 0 : 5 + s(5 + 1, 5)))) ---> 2 + (3 + (4 + (false ? 0 : 5 + s(5 + 1, 5)))) ---> 2 + (3 + (4 + (5 + s(5 + 1, 5)))) ---> 2 + (3 + (4 + (5 + s(6, 5)))) ---> 2 + (3 + (4 + (5 + (6 > 5 ? 0 : s(6 + 1, 5))))) ---> 2 + (3 + (4 + (5 + (true ? 0 : s(6 + 1, 5))))) ---> 2 + (3 + (4 + (5 + 0))) ---> 2 + (3 + (4 + 5)) ---> 2 + (3 + 9) ---> 2 + 12 ---> 14
все, что мы сделали здесь, было просто простой текстовой заменой. На самом деле я не должен был заменять "3" на "2+1" и так далее, пока мне не пришлось, но с педагогической точки зрения это было бы трудно читать.
активация функции-это не что иное, как замена вызова функции телом вызова и замена формальных параметров их соответствующими аргументами. Вы должны быть осторожны с введением скобок разумно, но помимо этого, это просто замена текста.
Конечно, большинство языков на самом деле не реализовать активация в качестве замены текста, но логически вот что это такое.
так что же такое неограниченная рекурсия? Рекурсия, где текстовая подстановка не останавливается! Обратите внимание, как в конце концов мы добрались до ступеньки, где больше не было
s
чтобы заменить, и мы могли бы тогда просто применить правила для арифметики.
способ, которым я обычно выясняю, как работает рекурсивная функция, - это смотреть на базовый случай и работать в обратном направлении. Вот эта техника применяется к этой функции.
первый базовый вариант:
sumInts(6, 5) = 0
тогда вызов чуть выше этого в стек вызовов:
sumInts(5, 5) == 5 + sumInts(6, 5) sumInts(5, 5) == 5 + 0 sumInts(5, 5) == 5
затем вызов чуть выше этого в стеке вызовов:
sumInts(4, 5) == 4 + sumInts(5, 5) sumInts(4, 5) == 4 + 5 sumInts(4, 5) == 9
и так:
sumInts(3, 5) == 3 + sumInts(4, 5) sumInts(3, 5) == 3 + 9 sumInts(3, 5) == 12
и так:
sumInts(2, 5) == 2 + sumInts(3, 5) sumInts(4, 5) == 2 + 12 sumInts(4, 5) == 14
уведомления это мы пришли к нашему первоначальному вызову функции
sumInts(2, 5) == 14
порядок, в котором выполняются эти вызовы:
sumInts(2, 5) sumInts(3, 5) sumInts(4, 5) sumInts(5, 5) sumInts(6, 5)
порядок, в котором эти вызовы возвращают:
sumInts(6, 5) sumInts(5, 5) sumInts(4, 5) sumInts(3, 5) sumInts(2, 5)
обратите внимание, что мы пришли к выводу о том, как работает функция, отслеживая вызовы в том порядке, что они возвращение.
Я дам ему идти.
выполняя уравнение a + sumInts (a+1, b), я покажу, как окончательный ответ равен 14.
//the sumInts function definition func sumInts(a: Int, b: Int) -> Int { if (a > b) { return 0 } else { return a + sumInts(a + 1, b) } } Given: a = 2 and b = 5 1) 2 + sumInts(2+1, 5) 2) sumInts(3, 5) = 12 i) 3 + sumInts(3+1, 5) ii) 4 + sumInts(4+1, 5) iii) 5 + sumInts(5+1, 5) iv) return 0 v) return 5 + 0 vi) return 4 + 5 vii) return 3 + 9 3) 2 + 12 = 14.
дайте нам знать, если у вас возникли дополнительные вопросы.
вот еще один пример рекурсивных функций в следующем примере.
человек только что закончили колледж.
t-это количество времени в годах.
общее фактическое количество лет, отработанных до выхода на пенсию, можно рассчитать как следует:
public class DoIReallyWantToKnow { public int howLongDoIHaveToWork(int currentAge) { const int DESIRED_RETIREMENT_AGE = 65; double collectedMoney = 0.00; //remember, you just graduated college double neededMoneyToRetire = 1000000.00 t = 0; return work(t+1); } public int work(int time) { collectedMoney = getCollectedMoney(); if(currentAge >= DESIRED_RETIREMENT_AGE && collectedMoney == neededMoneyToRetire { return time; } return work(time + 1); } }
и этого должно быть достаточно, чтобы подавить любого, лол. ;- P
рекурсия. В информатике рекурсия подробно рассматривается в рамках темы конечных автоматов.
в самой простой форме это самоназвание. Например, утверждение "моя машина-это машина" является рекурсивным утверждением. Проблема в том, что это утверждение является бесконечной рекурсией в том, что оно никогда не закончится. Определение в заявлении " автомобиль "заключается в том, что это" автомобиль", поэтому его можно заменить. Однако конца нет, потому что в случае подмены он все равно становится "моя машина-это машина".
это могло бы быть по-другому, если бы заявление было "моя машина-bentley. моя машина синяя."В этом случае замена во второй ситуации для автомобиля может быть "bentley", в результате чего "мой bentley синий". Эти типы подстановок математически объясняются в информатике через Контекстно-Свободные Грамматики.
фактическая замена является производственным правилом. Учитывая, что заявление представлено и автомобиль переменная, которая может быть "Бентли" этот оператор может быть рекурсивно реконструирован.
S -> "my"S | " "S | CS | "is"S | "blue"S | ε C -> "bentley"
это может быть построено несколькими способами, так как каждый
|
значит есть выбор.S
может быть заменен любым из этих вариантов, и S всегда начинается пустым. Элементε
означает прекращение производства. Так же, какS
может быть заменен, так же как и другие переменные (есть только один, и этоC
который будет представлять "Бентли").Итак, начнем с
S
будучи пустым, и заменив его на первый выбор"my"S
S
становится"my"S
S
все еще может быть заменен, поскольку он представляет переменную. Мы могли бы снова выбрать " мой " или ε, чтобы закончить его, но давайте продолжим наше первоначальное утверждение. Мы выбираем пространство, которое означаетS
заменяется" "S
"my "S
далее позволяет выбрать C
"my "CS
и C имеет только один выбор для замены
"my bentley"S
и пространство снова для S
"my bentley "S
и так далее
"my bentley is"S
,"my bentley is "S
,"my bentley is blue"S
,"my bentley is blue"
(замена S на ε завершает производство), и мы рекурсивно построили наше утверждение "мой bentley синий".подумайте о рекурсии как об этих производствах и заменах. Каждый шаг в процессе заменяет своего предшественника, чтобы получить конечный результат. В точном примере рекурсивной суммы от 2 до 5, вы в конечном итоге с производством
S -> 2 + A A -> 3 + B B -> 4 + C C -> 5 + D D -> 0
этот становится
2 + A 2 + 3 + B 2 + 3 + 4 + C 2 + 3 + 4 + 5 + D 2 + 3 + 4 + 5 + 0 14
я думаю, что лучший способ понять рекурсивные функции-это понять, что они сделаны для обработки рекурсивных структур данных. Но в вашей оригинальной функции
sumInts(a: Int, b: Int)
который вычисляет рекурсивно сумму чисел изa
доb
, это, кажется, не рекурсивная структура данных... Давайте попробуем немного измененную версиюsumInts(a: Int, n: Int)
здесьn
сколько чисел вы добавите.теперь, sumInts является рекурсивным за
n
, натуральное число. Все еще не рекурсивные данные, верно? Ну, натуральное число можно считать рекурсивной структурой данных, используя аксиомы Пеано:enum Natural = { case Zero case Successor(Natural) }
Итак, 0 = ноль, 1 = правопреемником(ноль), 2 = правопреемником(правопреемником(ноль)), и так далее.
если у вас есть рекурсивная структура данных, у вас есть шаблон для этой функции. Для каждого нерекурсивного случая можно вычислить значение напрямую. Для рекурсивных случаев можно предположить что рекурсивная функция уже работает и использовать его для расчета случай, но деконструкция аргумента. В случае естественного, это означает, что вместо
Succesor(n)
мы будем использоватьn
, или, что эквивалентно, вместоn
мы будем использоватьn - 1
.// sums n numbers beginning from a func sumInts(a: Int, n: Int) -> Int { if (n == 0) { // non recursive case } else { // recursive case. We use sumInts(..., n - 1) } }
теперь рекурсивную функцию проще запрограммировать. Во-первых, базовый случай,
n=0
. Что мы должны вернуть, если мы не хотим добавлять числа? Ответ, конечно, 0.как насчет рекурсивного случая? Если мы хотим добавить
n
числа, начиная сa
и у нас уже есть работаетsumInts
функция, которая работает дляn-1
? Ну, нам нужно добавитьa
и затем вызватьsumInts
Сa + 1
, поэтому мы заканчиваем:// sums n numbers beginning from a func sumInts(a: Int, n: Int) -> Int { if (n == 0) { return 0 } else { return a + sumInts(a + 1, n - 1) } }
приятно то, что теперь вам не нужно думать в низком уровне рекурсии. Вам просто нужно проверить, что:
- для базовых случаев рекурсивных данных он вычисляет ответ без использования рекурсии.
- для рекурсивных случаев рекурсивных данных он вычисляет ответ использование рекурсии над деструктурированными данными.
вас могут заинтересовать Нисан и Шокен реализация функции. Связанный pdf является частью бесплатного онлайн-курса. Он описывает вторую часть реализации виртуальной машины, в которой учащийся должен написать компилятор языка виртуальной машины на машинном языке. Реализация функции, которую они предлагают, способна к рекурсии, потому что она основана на стеке.
чтобы познакомить вас с реализацией функции: рассмотрим следующее код виртуальной машины:
если Swift скомпилирован на этом языке виртуальной машины, то следующий блок кода Swift:
mult(a: 2, b: 3) - 4
будет составлять до
push constant 2 // Line 1 push constant 3 // Line 2 call mult // Line 3 push constant 4 // Line 4 sub // Line 5
язык виртуальных машин разработан вокруг глобальный стек.
push constant n
помещает целое число в этот глобальный стек.после выполнения строк 1 и 2 стек выглядит следующим образом:
256: 2 // Argument 0 257: 3 // Argument 1
256
и257
являются адресами памяти.
call mult
помещает номер строки возврата (3) в стек и выделяет пространство для локальных переменных функции.256: 2 // argument 0 257: 3 // argument 1 258: 3 // return line number 259: 0 // local 0
...и он идет-к этикетке
function mult
. Код внутриmult
выполняется. В результате выполнения этого кода мы вычисляем произведение 2 и 3, которое хранится в 0-й локальной переменной функции.256: 2 // argument 0 257: 3 // argument 1 258: 3 // return line number 259: 6 // local 0
перед
return
ing от mult, вы заметите линия:push local 0 // push result
мы будем толкать продукт на стек.
256: 2 // argument 0 257: 3 // argument 1 258: 3 // return line number 259: 6 // local 0 260: 6 // product
когда мы возвращаемся, происходит следующее:
- поместите последнее значение в стеке в адрес памяти 0-го аргумента (в данном случае 256). Это самое удобное место, чтобы положить его.
- отбросить все в стеке до адреса 0-го аргумента.
- перейти-к номеру строки возврата (3 в данном случае), а затем продвижение.
после возвращения мы готовы выполнить строке 4, и наш стек выглядит так:
256: 6 // product that we just returned
теперь мы нажимаем 4 на стек.
256: 6 257: 4
sub
является примитивной функцией языка виртуальных машин. Он принимает два аргумента и возвращает свой результат в обычном адресе: адрес 0-го аргумента.теперь у нас есть
256: 2 // 6 - 4 = 2
теперь, когда вы знаете, как работает вызов функции, это относительно просто понять, как работает рекурсия. никакой магии, просто стек.
я реализовал свой
sumInts
функция на этом языке виртуальной машины:function sumInts 0 // `0` means it has no local variables. label IF push argument 0 push argument 1 lte if-goto ELSE_CASE push constant 0 return label ELSE_CASE push constant 2 push argument 0 push constant 1 add push argument 1 call sumInts // Line 15 add // Line 16 return // Line 17 // End of function
теперь я назову его:
push constant 2 push constant 5 call sumInts // Line 21
код выполняется, и мы добираемся до точки остановки, где
lte
возвращаетfalse
. Вот как выглядит стек в этот момент:// First invocation 256: 2 // argument 0 257: 5 // argument 1 258: 21 // return line number 259: 2 // augend // Second 260: 3 // argument 0 261: 5 // argument 1 262: 15 // return line number 263: 3 // augend // Third 264: 4 // argument 0 265: 5 // argument 1 266: 15 // return line number 267: 4 // augend // Fourth 268: 5 // argument 0 269: 5 // argument 1 270: 15 // return line number 271: 5 // augend // Fifth 272: 6 // argument 0 273: 5 // argument 1 274: 15 // return line number 275: 0 // return value
теперь давайте "раскрутим" нашу рекурсию.
return
0 и Гото линия 15 и продвижение.271: 5 272: 0
строка 16:
add
271: 5
строка 17:
return
5 и Гото линия 15 и заранее.267: 4 268: 5
строка 16:
add
267: 9
строка 17:
return
9 и Гото линия 15 и заранее.263: 3 264: 9
строка 16:
add
263: 12
строка 17:
return
12 и Гото линия 15 и заранее.259: 2 260: 12
строка 16:
add
259: 14
строка 17:
return
14 и перейти линию 21 и вперед.256: 14
вот оно. Рекурсия: Прославленные
goto
.
один действительно хороший совет, с которым я столкнулся в изучении и действительно понимании рекурсии, - это потратить некоторое время на изучение языка, который не имеет никакой формы построения цикла, кроме рекурсии. Таким образом, вы получите отличное представление о том, как использовать рекурсию через практику.
Я последовал http://www.htdp.org/ который, помимо того, что является учебником по схеме, также является отличным введением о том, как проектировать программы с точки зрения архитектуры и дизайн.
но в основном, вам нужно инвестировать некоторое время. Без "твердого" понимания рекурсии некоторые алгоритмы, такие как обратный путь, всегда будут казаться вам "трудными" или даже "волшебными". Итак, упорствуйте. :- D
Я надеюсь, что это помогает и удачи!
подумайте о рекурсии как несколько клонов делаешь то же самое...
вы просите клонировать[1]:"сумма чисел между 2 и 5"
+ clone[1] knows that: result is 2 + "sum numbers between 3 and 5". so he asks to clone[2] to return: "sum numbers between 3 and 5" | + clone[2] knows that: result is 3 + "sum numbers between 4 and 5". so he asks to clone[3] to return: "sum numbers between 4 and 5" | | + clone[3] knows that: result is 4 + "sum numbers between 5 and 5". so he asks to clone[4] to return: "sum numbers between 5 and 5" | | | + clone[4] knows that: result is 5 + "sum numbers between 6 and 5". so he asks to clone[5] to return: "sum numbers between 6 and 5" | | | | clone[5] knows that: he can't sum, because 6 is larger than 5. so he returns 0 as result. | | | + clone[4] gets the result from clone[5] (=0) and sums: 5 + 0, returning 5 | | + clone[3] gets the result from clone[4] (=5) and sums: 4 + 5, returning 9 | + clone[2] gets the result from clone[3] (=9) and sums: 3 + 9, returning 12 + clone[1] gets the result from clone[2] (=12) and sums: 2 + 12, returning 14
и вуаля!!
уже есть много хороших ответов. Тем не менее я даю попробовать.
При вызове функция получает значение memory-space отведено, которое укладывается на memory-space функции абонента. В этом пространстве памяти функция сохраняет переданные ей параметры, переменные и их значения. Это memory-space исчезает вместе с окончанием обратного вызова функции. Как идея стека идет, memory-space из функция вызывающего абонента теперь становится активной.для рекурсивных вызовов одна и та же функция получает несколько memory-space сложены один на другой. Вот и все. Простая идея как стек работа в памяти компьютера должна помочь вам понять, как рекурсия происходит в реализации.
немного не по теме, я знаю, но... попробуйте посмотреть вверх рекурсия в Google... Вы увидите на примере, что это значит :-)
более ранние версии Google нашел следующий текст (цитируется по памяти):
рекурсия
посмотреть рекурсия
10 сентября 2014 года шутка о рекурсии была обновлена:
рекурсия
ты значит:рекурсия
для другого ответа см. ответ.
многие из ответов выше очень хороши. Однако полезный метод решения рекурсии заключается в том, чтобы сначала сформулировать, что мы хотим сделать, и кодировать, как человек решил бы это . В приведенном выше случае мы хотим суммировать последовательность последовательных целых чисел (используя числа сверху):
2, 3, 4, 5 //adding these numbers would sum to 14
теперь обратите внимание, что эти строки сбивают с толку (не неправильно, но запутанно).
if (a > b) { return 0 }
почему тест
a>b
? и почемуreturn 0
Давайте изменим код, чтобы отразить точнее то, что делает человек
func sumInts(a: Int, b: Int) -> Int { if (a == b) { return b // When 'a equals b' I'm at the most Right integer, return it } else { return a + sumInts(a: a + 1, b: b) } }
можем ли мы сделать это еще более по-человечески? Да! Обычно мы подводим слева направо (2+3+...). Но выше рекурсия суммируется справа налево (...+4+5). Измените код, чтобы отразить его (
-
может быть немного пугающим, но не сильно)func sumInts(a: Int, b: Int) -> Int { if (a == b) { return b // When I'm at the most Left integer, return it } else { return sumInts(a: a, b: b - 1) + b } }
мне было трудно понять рекурсию, тогда я нашел этот блог и я уже видел этот вопрос, поэтому я подумал, что должен поделиться. Вы должны прочитать этот блог я нашел это очень полезно это объяснить со стеком и даже объяснить, как две рекурсии работает со стеком шаг за шагом. Я рекомендую вам сначала понять, как работает стек, который он очень хорошо объясняет здесь:путешествие в стек
then now you will understand how recursion works now take a look of this post
:понять рекурсию шаг шаг за шагомего программы :
def hello(x): if x==1: return "op" else: u=1 e=12 s=hello(x-1) e+=1 print(s) print(x) u+=1 return e hello(3)
рекурсия начала иметь смысл для меня, когда я перестал читать то, что другие говорят об этом или видеть это как то, чего я могу избежать, и просто написал код. Я нашел проблему с решением и попытался дублировать решение, не глядя. Я только посмотрел на решение, когда я беспомощно застрял. Затем я вернулся к попытке повторить его. Я сделал это снова по нескольким проблемам, пока не разработал свое собственное понимание и чувство того, как идентифицировать рекурсивную проблему и решить ее. Когда я получил до этого уровня я начал придумывать проблемы и решать их. Это помогло мне больше. Иногда вещи можно узнать, только попробовав его самостоятельно и изо всех сил; пока вы "не получите его".