Ідея рекурсії не дуже поширена в реальному світі. Отже, початківцям програмістам це здається дещо заплутаним. Хоча, я думаю, вони звикають до поняття поступово. Отже, що може бути їм приємним поясненням, щоб зрозуміти ідею легко?
Ідея рекурсії не дуже поширена в реальному світі. Отже, початківцям програмістам це здається дещо заплутаним. Хоча, я думаю, вони звикають до поняття поступово. Отже, що може бути їм приємним поясненням, щоб зрозуміти ідею легко?
Відповіді:
Для пояснення рекурсії я використовую комбінацію різних пояснень, як правило, обидва намагаються:
Для початку Вольфрам | Альфа визначає це більш простими термінами, ніж Вікіпедія :
Вираз такий, що кожен доданок формується повторенням певної математичної операції.
Якщо ваш учень (або людина, яку ви теж пояснюєте, відтепер я скажу, що студент) має хоча б певну математичну основу, вони, очевидно, вже стикалися з рекурсією, вивчаючи серію та їх поняття рекурсивності та їх відношення .
Дуже хороший спосіб почати - це продемонструвати серію і сказати, що це просто просто рекурсія:
Зазвичай ви або отримуєте "так, вот," у кращому випадку, тому що вони все ще не користуються ним, або, швидше за все, дуже глибоким хропінням.
В іншому, насправді це докладний варіант того , що я представив в додаванні в моїй обороні на питання , який ви вказали щодо покажчиків (поганий каламбур).
На цьому етапі мої учні зазвичай знають, як щось надрукувати на екрані. Припускаючи, що ми використовуємо C, вони знають, як надрукувати одну таблицю за допомогою write
або printf
. Вони також знають про петлі управління.
Зазвичай я вдаюсь до кількох повторюваних і простих проблем програмування, поки вони їх не отримають:
Факторський
Factorial - це дуже проста математична концепція, яку можна зрозуміти, а реалізація дуже близька до її математичного подання. Однак спочатку вони можуть не отримати.
Алфавіти
Версія алфавіту цікава, щоб навчити їх думати про впорядкування своїх рекурсивних висловлювань. Як і вказівники, вони просто кидатимуть лінії навмання. Сенс полягає в тому, щоб довести їх до усвідомлення того, що цикл можна перевернути, змінивши умови АБО , просто перевернувши порядок висловлювань у вашій функції. Тут допомагає друк алфавіту, оскільки це щось візуальне для них. Просто запропонуйте їм написати функцію, яка буде надрукувати один символ для кожного дзвінка, а сам дзвонить рекурсивно, щоб написати наступний (або попередній).
Фанати FP, пропустити той факт, що друк матеріалів на вихідний потік зараз є побічним ефектом ... Давайте не будемо занадто дратувати на фронті FP. (Але якщо ви використовуєте мову із підтримкою списку, не соромтеся приєднатись до списку під час кожної ітерації та просто надрукуйте кінцевий результат. Але зазвичай я починаю їх із C, що, на жаль, не найкраще для подібних проблем та концепцій) .
Експоненція
Проблема експоненції дещо складніше ( на цьому етапі навчання). Очевидно, що концепція точно така ж, як і для факторіалу, і немає додаткової складності ... за винятком того, що у вас є кілька параметрів. І цього зазвичай достатньо, щоб заплутати людей і скинути їх на початку.
Його проста форма:
можна виразити так повторенням:
Важче
Після того, як ці прості проблеми будуть показані та повторно реалізовані в навчальних посібниках, ви можете дати трохи складніші (але дуже класичні) вправи:
Примітка. Знову ж таки, деякі з них насправді не складніші ... Вони просто підходять до проблеми прямо з того самого кута або трохи іншого. Але практика робить ідеальною.
Довідка
Деякі читання ніколи не шкодять. Добре це буде спочатку, і вони почуватимуться ще більше втраченими. Це така річ, яка росте на вас і яка сидить на потилиці, поки одного дня ви не зрозумієте, що нарешті це отримаєте. І тоді ти думаєш, що ти читаєш ці речі. На даний момент сторінки рекурсії , рекурсії в галузі інформатики та рекурсійних зв’язків у Вікіпедії.
Рівень / Глибина
Припустимо, що ваші студенти не мають великого досвіду кодування, надайте заглушки коду. Після перших спроб надайте їм функцію друку, яка може відображати рівень рекурсії. Друк числового значення рівня допомагає.
Діаграма "Склади як малюнки"
Введення друкованого результату (або вихідного рівня) також допомагає, оскільки воно дає ще одне візуальне уявлення про те, що робить ваша програма, відкриваючи та закриваючи контексти стеку, як ящики або папки в провіднику файлової системи.
Рекурсивні абревіатури
Якщо ваш учень вже трохи розбирається в комп'ютерній культурі, він може вже використовувати деякі проекти / програмні засоби з іменами з використанням рекурсивних абревіатур . Протягом певного часу існує традиція, особливо у проектах GNU. Деякі приклади включають:
Рекурсивна:
Взаємно рекурсивні:
Нехай вони спробують придумати своє.
Так само є багато випадків рекурсивного гумору, як-от рекурсивна корекція пошуку Google . Для отримання додаткової інформації про рекурсію читайте цю відповідь .
Деякі питання, з якими люди зазвичай борються, і на які потрібно знати відповіді.
Чому, о Боже, чому ???
Навіщо ти це робив? Хороша, але не очевидна причина полягає в тому, що часто простіше висловити проблему таким чином. Не надто гарна, але очевидна причина полягає в тому, що часто потрібно менше вводити текст (не змушуйте їх відчувати soooo l33t за просто використання рекурсії, хоча ...).
Деякі проблеми, безумовно, легше вирішити, використовуючи рекурсивний підхід. Зазвичай будь-яка проблема, яку ви можете вирішити за допомогою парадигми Divide and Conquer , відповідатиме багатогалузевому алгоритму рекурсії.
Що знову N?
Чому мій n
або (незалежно від назви вашої змінної) кожен раз відрізняється? У початківців зазвичай виникає проблема з розумінням, що таке змінна та параметр, і як речі, названі n
у вашій програмі, можуть мати різні значення. Отже, якщо це значення знаходиться в циклі управління або рекурсії, це ще гірше! Будьте приємні і не використовуйте однакових імен змінних скрізь і дайте зрозуміти, що параметри - це лише змінні .
Кінцеві умови
Як визначити свій кінцевий стан? Це просто, просто запропонуйте їм вимовити кроки вголос. Наприклад, для заводу починаються з 5, потім 4, потім ... до 0.
Диявол у деталях
Чи не говорити рано примикають такі речі , як оптимізація хвостового виклику . Я знаю, я знаю, TCO приємно, але спочатку їм все одно. Дайте їм трохи часу, щоб обернути голову навколо процесу таким чином, щоб вони працювали для них. Сміліш згодом знову зруйнувати їхній світ, але відпочити.
Так само не говоріть прямо з першої лекції про стек викликів та його споживання пам'яті і ... ну ... стек переповнюється . Я часто навчаю студентів приватно, які показують мені лекції, де вони мають 50 слайдів про все, що потрібно знати про рекурсію, коли на цьому етапі вони ледве можуть правильно записати цикл. Це хороший приклад того, як посилання допоможе пізніше, але зараз просто вас глибоко бентежить .
Але, будь ласка, вчасно уточніть, що є причини перейти до ітеративного чи рекурсивного маршруту .
Взаємна рекурсія
Ми бачили, що функції можуть бути рекурсивними, і навіть вони можуть мати декілька точок виклику (8-королеви, Ханой, Фібоначчі або навіть алгоритм розвідки тральщика). А як щодо взаємно рекурсивних дзвінків ? Почніть з математики і тут. f(x) = g(x) + h(x)
де g(x) = f(x) + l(x)
і h
і l
просто робити речі.
Починаючи з лише математичних рядів, це спрощує написання та реалізацію, оскільки контракт чітко визначений виразами. Наприклад, жіночі та чоловічі послідовності Hofstadter :
Однак, з точки зору коду, слід зазначити, що реалізація взаємно рекурсивного рішення часто призводить до дублювання коду і має бути скоріше упорядкована в єдину рекурсивну форму (див . Розв’язання кожної головоломки Судоку Пітера Норвіга .
static unsigned int vote = 1;
від мене. Пробачте статичний гумор, якщо хочете :) Це найкраща відповідь поки.
Виклик функції з тієї самої функції.
Як користуватися ним, коли ним користуватися і як уникнути поганого дизайну, важливо знати, що вимагає, щоб ви спробували це на собі, і зрозуміти, що відбувається.
Найголовніше, що вам потрібно знати, - бути дуже обережним, щоб не отримати цикл, який ніколи не закінчується. Відповідь pramodc84 на ваше запитання має цю помилку: вона ніколи не закінчується ...
Рекурсивна функція повинна завжди перевіряти стан, щоб визначити, чи повинен він викликати себе ще раз чи ні.
Найбільш класичний приклад використання рекурсії - це робота з деревом без статичних обмежень по глибині. Це завдання, яке потрібно використовувати рекурсію.
a
все ще викликає себе, просто побічно (за допомогою виклику b
).
Рекурсивне програмування - це процес прогресивного зменшення проблеми, щоб легше вирішити версії самої себе.
Кожна рекурсивна функція має тенденцію до:
Коли крок 2 дорівнює 3 і коли крок 4 є тривіальним (конкатенація, сума чи нічого), це дає можливість рецидиву хвоста . Крок 2 часто повинен відбуватися після кроку 3, оскільки результати з піддомену (ів) проблеми можуть знадобитися для завершення поточного кроку.
Візьміть обхід прямого бінарного дерева прямого напрямку. Переїзд може бути виконаний у попередньому порядку, на замовлення або після замовлення, залежно від необхідного.
B
A C
Попереднє замовлення: BAC
traverse(tree):
visit the node
traverse(left)
traverse(right)
На замовлення: ABC
traverse(tree):
traverse(left)
visit the node
traverse(right)
Після замовлення: ACB
traverse(tree):
traverse(left)
traverse(right)
visit the node
Дуже багато рекурсивних проблем - це конкретні випадки операції з картою , або складка - розуміння саме цих двох операцій може призвести до значного розуміння випадків гарного використання для рекурсії.
ОП заявила, що рекурсії не існує в реальному світі, але я прошу відрізнятися.
Візьмемо реальну "операцію" з нарізки піци. Ви вийняли піцу з духовки, і щоб її подати, ви повинні розрізати її навпіл, потім розрізати їх навпіл, а потім знову розрізати ці отримані половинки навпіл.
Операцію нарізки піци ви виконуєте знову і знову, поки не отримаєте потрібний результат (кількість скибочок). А для аргументів скажемо, що необрізана піца - це сам шматочок.
Ось приклад у Ruby:
визначити cut_pizza (існуючі_слізи, бажані_слізи) якщо існуючі_слізи! = потрібні_слізи # у нас ще недостатньо скибочок, щоб годувати всіх, тож # ми ріжемо шматочки піци, тим самим збільшуючи їхню кількість new_slices = існуючі_slices * 2 # і це рекурсивний виклик cut_pizza (new_slices, desire_slices) ще # у нас є бажана кількість скибочок, тому ми повертаємось # тут замість того, щоб продовжувати повторюватись повернути існуючі_slices кінець кінець піца = 1 # ціла піца, "один шматочок" cut_pizza (піца, 8) # => отримаємо 8
Тож реальна операція у світі - це різання піци, і рекурсія робить те саме, що відбувається знову і знову, поки у вас немає того, чого ви хочете.
Операції, які ви знайдете, що з'являється, що ви можете реалізувати за допомогою рекурсивних функцій:
Я рекомендую написати програму, щоб шукати файл на основі його імені файлу, і спробувати написати функцію, яка викликає себе, поки її не знайдуть, підпис виглядатиме так:
find_file_by_name(file_name_we_are_looking_for, path_to_look_in)
Тож ви можете назвати це так:
find_file_by_name('httpd.conf', '/etc') # damn it i can never find apache's conf
На мою думку, це просто програмування механіки, спосіб розумного видалення дублювання. Ви можете переписати це за допомогою змінних, але це "приємніше" рішення. У цьому немає нічого загадкового чи складного. Ви напишете пару рекурсивних функцій, вона натисне та заштрихує ще один механічний трюк у вікні інструменту програмування.
Додатковий кредит Наведений cut_pizza
вище приклад надасть вам занадто глибоку помилку рівня стека, якщо ви запитаєте його на кількість фрагментів, що не є потужністю 2 (тобто 2 або 4 або 8 або 16). Чи можете ви змінити це так, що якщо хтось попросить 10 скибочок, він не буде працювати назавжди?
Гаразд, я намагаюся зберегти це просто і стисло.
Рекурсивна функція - це функції, які називають себе. Рекурсивна функція складається з трьох речей:
Найкращий спосіб записати рекурсивні методи - це думати про метод, який ви намагаєтеся написати як простий приклад, обробляючи лише одну петлю процесу, який ви хочете повторити, а потім додайте виклик до самого методу та додайте, коли ви хочете припинити. Найкращий спосіб вчитися - це практикувати, як усі речі.
Оскільки це веб-сайт програмістів, я не буду писати код, але ось хороше посилання
якщо ви отримали цей жарт, ви зрозуміли, що означає рекурсія.
Рекурсія - це інструмент, який програміст може використовувати для виклику функціонального виклику на себе. Послідовність Фібоначчі є прикладом підручника, як використовується рекурсія.
Більшість рекурсивних кодів, якщо не все можна виразити як ітеративну функцію, але зазвичай її безладно. Хорошими прикладами інших рекурсивних програм є структури даних, такі як дерева, бінарне дерево пошуку та навіть квакісорт.
Рекурсія використовується для того, щоб зробити код менш неохайним, майте на увазі, що він зазвичай повільніше і вимагає більше пам’яті.
Мені подобається використовувати цей:
Якщо ви біля входу в магазин, просто пройдіть його. В іншому випадку зробіть один крок, а потім пройдіть решту шляху до магазину.
Важливо включити три аспекти:
Ми реально багато використовуємо рекурсію в повсякденному житті; ми просто не думаємо про це так.
for
циклу в безглузду рекурсивну функцію.
Джош К уже згадував ляльок Матрошка . Припустимо, що ви хочете навчитися чогось, що знає лише найкоротша лялька. Проблема полягає в тому, що ви не можете реально спілкуватися з нею безпосередньо, адже вона спочатку живе всередині високої ляльки, яка на першій картинці розміщена зліва. Ця структура іде так (лялька живе всередині вище ляльки), поки не закінчується лише найвищою.
Тож єдине, що ви можете зробити - це поставити своє запитання найвищій ляльці. Найвищої ляльці (яка не знає відповіді) потрібно буде передати ваше питання коротшій ляльці (яка на першій картинці знаходиться праворуч). Оскільки у неї також немає відповіді, їй потрібно попросити наступну коротшу ляльку. Так буде йти, поки повідомлення не дійде до найкоротшої ляльки. Найкоротша лялька (яка єдина, хто знає таємну відповідь) передасть відповідь наступній ляльці з високим рівнем (знайдена зліва її), яка передасть її наступній ляльці вище ... і це триватиме, поки відповідь не буде досягає свого кінцевого пункту призначення, який є найвищою лялькою і нарешті ... ви :)
Ось що насправді робить рекурсія. Функція / метод викликає себе до отримання очікуваної відповіді. Ось чому, коли ви пишете рекурсивний код, дуже важливо вирішити, коли рекурсія повинна припинитися.
Не найкраще пояснення, але, сподіваємось, це допомагає.
Рекурсія n. - Шаблон проектування алгоритму, де операція визначається з точки зору самої себе.
Класичний приклад - знаходження факторіалу числа, n !. 0! = 1, а для будь-якого іншого натурального числа N множина N - добуток усіх натуральних чисел, менших або рівних N. Отже, 6! = 6 * 5 * 4 * 3 * 2 * 1 = 720. Це базове визначення дозволить створити просте ітеративне рішення:
int Fact(int degree)
{
int result = 1;
for(int i=degree; i>1; i--)
result *= i;
return result;
}
Однак вивчіть операцію ще раз. 6! = 6 * 5 * 4 * 3 * 2 * 1. За тим самим визначенням 5! = 5 * 4 * 3 * 2 * 1, це означає, що ми можемо сказати 6! = 6 * (5!). У свою чергу 5! = 5 * (4!) Тощо. Цим ми зводимо проблему до операції, виконаної за результатом усіх попередніх операцій. Це зрештою зводиться до точки, що називається базовим випадком, де результат відомий за визначенням. У нашому випадку 0! = 1 (ми можемо в більшості випадків також сказати, що 1! = 1). Обчислюючи, нам часто дозволяють визначати алгоритми дуже подібним чином, маючи метод виклику себе і передавати менший вхід, таким чином зменшуючи проблему через багато рекурсій до базового випадку:
int Fact(int degree)
{
if(degree==0) return 1; //the base case; 0! = 1 by definition
else return degree * Fact(degree -1); //the recursive case; N! = N*(N-1)!
}
На багатьох мовах це може бути спрощено за допомогою потрійного оператора (іноді розглядається як функція Iif у мовах, які не надають оператора як такого):
int Fact(int degree)
{
//reads equivalently to the above, but is concise and often optimizable
return degree==0 ? 1: degree * Fact(degree -1);
}
Переваги:
Недоліки:
Я використовую приклад - це проблема, з якою я стикався в реальному житті. У вас є контейнер (наприклад, великий рюкзак, який ви збираєтеся взяти в подорож) і хочете дізнатися загальну вагу. Ви маєте в контейнері два-три сипучих предмета та деякі інші контейнери (скажімо, мішки з речами.) Вага всього контейнера, очевидно, вага порожнього контейнера плюс вага всього, що знаходиться в ньому. Що стосується сипучих предметів, ви можете їх просто зважити, а для мішків з речовинами ви можете їх просто зважити або ви можете сказати, "а вага кожного пакета - це вага порожнього контейнера плюс вага всього в ньому". А потім ви продовжуєте перебирати контейнери в контейнери і так далі, поки не дістанетесь до точки, де в контейнері є просто сипучі речі. Це рекурсія.
Ви можете подумати, що цього в реальному житті ніколи не буває, але уявіть, що намагаєтесь порахувати або скласти зарплату людей у певній компанії чи підрозділі, де є суміш людей, які просто працюють на компанію, людей у підрозділах, а потім у підрозділи є відділи тощо. Або продажі в країні, в якій є регіони, в деяких з яких є субрегіони тощо. Такі проблеми виникають постійно у бізнесі.
Рекурсія може бути використана для вирішення багатьох проблем з підрахунком. Наприклад, скажіть, що у вас на вечірці група з n людей (n> 1), і всі потискають руку всіх інших рівно один раз. Скільки відбувається рукостискань? Ви можете знати, що рішення C (n, 2) = n (n-1) / 2, але ви можете вирішити рекурсивно таким чином:
Припустимо, всього дві людини. Тоді (оглядом) відповідь очевидно 1.
Припустимо, у вас троє людей. Виділіть одну людину та зверніть увагу, що він / вона потискує руку двом іншим людям. Після цього вам потрібно порахувати лише рукостискання між двома іншими людьми. Ми це вже зробили зараз, і це 1. Отже, відповідь 2 + 1 = 3.
Припустимо, у вас n людей. Дотримуючись тієї ж логіки, що і раніше, це (n-1) + (кількість рукостискань між n-1 людьми). Розширюючись, отримуємо (n-1) + (n-2) + ... + 1.
Виражений як рекурсивна функція,
f (2) = 1
f (n) = n-1 + f (n-1), n> 2
У житті (на відміну від комп'ютерної програми) рекурсія рідко трапляється під нашим безпосереднім контролем, тому що це може заплутати те, що відбувається. Крім того, сприйняття, як правило, стосується побічних ефектів, а не бути функціонально чистим, тому, якщо трапляється рекурсія, ви можете цього не помітити.
Рекурсія все ж відбувається тут, у світі. Багато.
Хорошим прикладом є (спрощена версія) кругообіг води:
Це цикл, який спричиняє повторення його самоврядування. Він є рекурсивним.
Ще одне місце, де ви можете отримати рекурсію, - це англійська (та людська мова взагалі). Ви можете його не розпізнати спочатку, але спосіб генерування речення є рекурсивним, оскільки правила дозволяють нам вставити один екземпляр символу в інший екземпляр того ж символу.
З мовного інстинкту Стівена Пінкера:
якщо або дівчина їсть морозиво, або дівчина їсть цукерки, то хлопчик їсть хот-доги
Це ціле речення, яке містить інші цілі речення:
дівчина їсть морозиво
дівчина їсть цукерки
хлопчик їсть хот-доги
Акт розуміння повного речення передбачає розуміння менших речень, які використовують той самий набір розумових хитрощів, щоб його розуміти як повне речення.
Щоб зрозуміти рекурсію з точки зору програмування, найпростіше подивитися на проблему, яку можна вирішити рекурсією, і зрозуміти, чому це має бути і що це означає, що потрібно робити.
Для прикладу я використовую найбільшу загальну функцію дільника, або gcd для короткого.
Ви маєте свої два числа a
і b
. Щоб знайти їх gcd (якщо при цьому немає 0), вам потрібно перевірити, чи a
воно поділяється рівномірно b
. Якщо це тоді, b
це gcd, інакше вам потрібно перевірити наявність gcd b
та залишок a/b
.
Ви вже повинні мати можливість бачити, що це рекурсивна функція, оскільки у вас функція gcd викликає функцію gcd. Просто забити його додому, ось він знаходиться в c # (знову ж таки, якщо припустимо, що 0 ніколи не передається як параметр):
int gcd(int a, int b)
{
if (a % b == 0) //this is a stopping condition
{
return b;
}
return (gcd(b, a % b)); //the call to gcd here makes this function recursive
}
У програмі важливо мати стан зупинки, інакше функція буде повторюватися назавжди, що з часом спричинить переповнення стека!
Причиною для використання тут рекурсії, а не циклу часу чи іншої ітеративної конструкції, є те, що, читаючи код, він повідомляє вам, що він робить, і що буде далі, тому легше зрозуміти, чи працює він правильно .
Ось реальний приклад світу для рекурсії.
Дозвольте собі уявити, що у них є комічна колекція, і ви збираєтеся все це перемішати у велику купу. Обережно - якщо у них дійсно є колекція, вони можуть моментально вбити вас, коли ви просто згадаєте про ідею зробити це.
Тепер дозвольте сортувати цю велику несортовану купу коміксів за допомогою цього посібника:
Manual: How to sort a pile of comics
Check the pile if it is already sorted. If it is, then done.
As long as there are comics in the pile, put each one on another pile,
ordered from left to right in ascending order:
If your current pile contains different comics, pile them by comic.
If not and your current pile contains different years, pile them by year.
If not and your current pile contains different tenth digits, pile them
by this digit: Issue 1 to 9, 10 to 19, and so on.
If not then "pile" them by issue number.
Refer to the "Manual: How to sort a pile of comics" to separately sort each
of the new piles.
Collect the piles back to a big pile from left to right.
Done.
Приємно тут: Коли вони переходять до одиничних питань, вони мають повний "стек кадр" з локальними палі, видимими перед ними на землі. Дайте їм декілька роздруківків цього посібника та відкладіть по одному стовпчику з позначкою, де ви зараз перебуваєте на цьому рівні (тобто стан локальних змінних), щоб ви могли продовжуватись там у кожному Готово.
Ось, в чому полягає рекурсія: Виконуючи той самий процес, лише на більш тонкому рівні деталей, тим більше ви вступаєте в нього.
Не звичайні англійські, не дуже реальні приклади з життя, але два способи навчання рекурсії, граючи:
Приємне пояснення рекурсії - це буквально "дія, яка повторюється всередині себе".
Розгляньте художника, який малює стіну, це рекурсивно, оскільки дія полягає в тому, щоб "намалювати смугу від стелі до підлоги, ніж прорізати трохи праворуч і (пофарбувати смугу від стелі до підлоги, ніж замазати трохи вправо і (намалювати смугу від стелі до підлоги, ніж просуньте трохи праворуч і (тощо))) ".
Його функція paint () викликає себе знову і знову, щоб скласти свою більшу функцію paint_wall ().
Сподіваємось, у цього бідного художника є якась умова зупинки :)