Ресурсы для улучшения вашего понимания рекурсии? [закрыто]

13

Я знаю, что такое рекурсия (когда паттерн повторяется внутри себя, обычно это функция, которая вызывает себя на одной из своих строк после условного прорыва ... правильно?), И я могу понять рекурсивные функции, если внимательно изучу их. Моя проблема в том, что, когда я вижу новые примеры, я всегда изначально растерялся. Если я вижу цикл или отображение, архивирование, вложение, полиморфный вызов и т. Д., Я знаю, что происходит, просто посмотрев на него. Когда я вижу рекурсивный код, мой мыслительный процесс обычно звучит так: сопровождаемый 'о, это рекурсивно' сопровождаемое 'я предполагаю, что это должно работать, если они говорят, что это делает.'

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

Андрей М
источник
28
Чтобы понять рекурсию, вы должны сначала понять рекурсию.
Андреас Йоханссон
1
«Кошка в шляпе возвращается» доктора Сьюза, это может быть не совсем полезно, но рекурсивный вызов на кошку действительно избавляет от этого надоедливого пятна. :-) Он также очень быстро читается!
DKnight
2
Практика, практика, практика.
Дэвид Торнли
20
Уже ответили на этот вопрос: programmers.stackexchange.com/questions/57243/…
Грэм Борланд
3
@Graham Borland: это пример бесконечной рекурсии. В большинстве программ отсутствие базового варианта обычно приводит к переполнению стека или ошибке нехватки памяти. Для пользователей сайта это может привести к путанице. ;)
FrustratedWithFormsDesigner

Ответы:

10

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

Да, это странно и иногда нелогично, но как только он щелкает, когда вы говорите "Эврика!" Вы удивитесь, как не поняли этого раньше! ;) Я предложил деревья, потому что они (IMO) - самая простая структура для понимания в рекурсии, и с ними легко работать, используя карандаш и бумагу. ;)

FrustratedWithFormsDesigner
источник
1
+1 это то, как я его проглотил. Например, если вы используете OO, создайте несколько классов с родительскими дочерними отношениями, а затем попробуйте создать функцию / метод, который проверяет, есть ли у объекта определенный предок.
Альб
5

Я настоятельно рекомендую Схему, используя книгу «Маленький Лиспер». После того, как вы закончите с этим, вы будете понимать рекурсию, глубоко вниз. Почти гарантировано.

jcomeau_ictx
источник
1
+1 Эта книга действительно сделала это для меня. Но он был переименован в «Маленький интриган»
mike30
4

Я определенно предлагаю SICP. Кроме того, вы должны проверить видео вводного курса авторов здесь ; они невероятно открывают разум.

Другой путь, не столь тесно связанный с программированием, - это чтение Гёделя, Эшера, Баха: Вечная золотая коса Хофштадтера. Как только вы это сделаете, рекурсия будет выглядеть так же естественно, как арифметика. Кроме того, вы будете убеждены, что P = nP, и вам захочется создавать мыслительные машины - но это побочный эффект, который настолько мал по сравнению с преимуществами.

cbrandolino
источник
В любом случае, ГЕБ стоит прочитать; даже если некоторые из вещей, о которых он говорит, немного устарели (некоторый прогресс в фундаментальных исследованиях КС был достигнут за последние 40 лет), базовое понимание не таково.
Donal Fellows
2

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

Например, быстрая сортировка работает в том, что она перемещает элемент в списке на две половины, а затем снова применяет себя к каждой из этих половин. Когда происходит первоначальная сортировка, не нужно беспокоиться о сортировке двух половинок в этой точке. Скорее он берет элемент поворота и помещает все элементы, меньшие, чем этот элемент, с одной стороны, и все элементы, большие или равные с другой стороны. Имеет ли это смысл, как он может рекурсивно вызывать себя в этой точке для сортировки двух новых меньших списков? Они тоже списки. Просто меньше. Но они все еще должны быть отсортированы.

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

РЕДАКТИРОВАТЬ: Я также должен добавить, что ключевым элементом рекурсии является гарантированная способность функции быть в состоянии остановить. Это означает, что разбивка исходной задачи должна постоянно уменьшаться, и в конечном итоге должна быть гарантированная точка остановки (точка, в которой новая подзадача либо решаема, либо уже решена).

Кеннет
источник
Да, я думаю, что раньше видел быстрое объяснение, я могу представить, как оно работает, из вашего напоминания выше. Насколько выразительной / гибкой является рекурсия - можно ли привести большинство проблем к рекурсивному подходу (даже если он не оптимален)? Я видел, как люди решают в сети кодовые загадки, которые большинство людей решают процедурно, как если бы они могли использовать рекурсию, когда захотели, черт побери. Я также однажды прочитал, я думаю, что некоторые языки используют рекурсию для замены конструкции цикла. И вы упоминаете гарантированную точку остановки. Я чувствую, что одна из этих вещей может быть ключом.
Андрей М
Хорошая простая начальная задача для вас - написать рекурсивную программу, которая находит факториал числа.
Кеннет
Любая циклическая структура может быть помещена в рекурсивную структуру. Любая рекурсивная структура может быть зациклена ... более или менее. Требуется время и практика, чтобы понять, когда и когда не следует использовать рекурсию, потому что вы должны помнить, что когда вы используете рекурсию, возникает много накладных расходов с точки зрения ресурсов, используемых на аппаратном уровне.
Кеннет
Например, я мог видеть, что выполнимо создать циклическую структуру, которая выполняет быструю сортировку ... НО это наверняка, потому что чертовски сложно, и в зависимости от того, как это было сделано, могло бы использовать больше системных ресурсов в конце, чем рекурсивная функция для больших массивов.
Кеннет
так вот моя попытка факториала. чтобы быть справедливым, я видел это раньше, и хотя я написал это с нуля, а не по памяти, это, вероятно, все еще проще, чем было бы. Пробовал в JS, но имел ошибку разбора, но работает в Python def factorial(number): """return factorial of number""" if number == 0: return 0 elif number == 1: return 1 else: return number * factorial(number - 1)
Эндрю М
2

Лично я думаю, что ваш лучший выбор - через практику.

Я изучил рекурсию с логотипом. Вы могли бы использовать LISP. На этих языках рекурсия естественна. В противном случае вы можете сравнить его с изучением математических наборов и рядов, где вы выражаете то, что будет дальше, на основе того, что было раньше, то есть u (n + 1) = f (u (n)), или более сложных рядов, где у вас есть несколько переменных и множественные зависимости, например, u (n) = g (u (n-1), u (n-2), v (n), v (n-1)); v (n) = h (u (n-1), u (n-2), v (n), v (n-1)) ...

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

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

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

asoundmove
источник
2

Структура и интерпретация компьютерных программ

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

dietbuddha
источник
0

Как бы мне ни нравились SICP и Гёдель, Эшер, Бах: Вечная золотая коса, LISP Турецкого : Нежное введение в символические вычисления также хорошо справляется с внедрением рекурсии.

Основная концепция такова: во-первых, вы должны знать, когда ваша рекурсивная функция завершена, чтобы она могла вернуть результат. Затем вы должны знать, как взять незаконченное дело и свести его к тому, что вы можете повторить. Для традиционного факториального примера (N) вы закончите, когда N <= 1, и незавершенный случай - N * факториал (N-1).

Для более уродливого примера есть функция Акерманна A (m, n).

A(0,n) = n+1.                                   This is the terminal case.
A(m,0) = A(m-1,1) if m > 0.                     This is a simple recursion.
A(m,n) = A(m-1, A(m, n-1)) if m > 0 and n > 0.  This one is ugly.
Джон Р. Штром
источник
0

Я предлагаю поиграть с некоторыми функциональными языками в стиле ML, такими как OCaml или Haskell. Я обнаружил, что синтаксис сопоставления с образцом действительно помог мне понять даже относительно сложные рекурсивные функции, безусловно, намного лучше, чем схемы ifи condоператоры. (Я выучил Хаскель и Схему одновременно.)

Вот тривиальный пример для контраста:

(define (fib n)
   (cond [(= n 0) 0]
         [(= n 1) 1]
         [else (+ (fib (- n 1)) (fib (- n 2)))]))

и с сопоставлением с образцом:

fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)

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

Я особенно рекомендую Haskell, потому что это простой язык с очень хорошим синтаксисом. Это также значительно облегчает работу с более продвинутыми идеями, такими как corecursion :

fibs = 0 : 1 : zipWith (+) fibs (drop 1 fibs)
fib n = fibs !! n

(Вы не поймете приведенный выше код, пока не поиграете немного с Haskell, но будьте уверены, что он в основном магический: P.) Конечно, вы можете сделать то же самое с потоками в Scheme, но в Haskell это гораздо естественнее.

Тихон Джелвис
источник
0

Его нет в наличии, но если вы можете его найти, «Рекурсивные алгоритмы» Ричарда Лоренца - это не что иное, как рекурсия. Он охватывает основы рекурсии, а также конкретные рекурсивные алгоритмы.

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

Уэйн Конрад
источник