Простий англійською мовою, що таке рекурсія?


74

Ідея рекурсії не дуже поширена в реальному світі. Отже, початківцям програмістам це здається дещо заплутаним. Хоча, я думаю, вони звикають до поняття поступово. Отже, що може бути їм приємним поясненням, щоб зрозуміти ідею легко?


Більше інформації надається на цю тему на ресурсах для покращення вашого розуміння рекурсії?
Кеннет


1
Рекурсія - це коли функція може викликати себе. "Якщо ви повністю розумієте простори імен та сферу застосування та як параметри передаються функції, то ви вже знаєте рекурсію. Я можу показати приклади, але ви повинні мати можливість зрозуміти, як вони працюють самостійно." Студенти, як правило, борються з рекурсією не стільки тому, що це заплутано, а тому, що вони не мають чіткого розуміння змінної області / простору імен. Перш ніж зануритися в рекурсію, переконайтесь, що студенти зможуть правильно простежити програму, де ви навмисно давали змінні в різних областях однойменної назви, щоб їх плутати.
dspyz


1
Щоб зрозуміти рекурсію, ви повинні спершу зрозуміти рекурсію
Ґерман

Відповіді:


110

Для пояснення рекурсії я використовую комбінацію різних пояснень, як правило, обидва намагаються:

  • поясніть концепцію,
  • поясніть, чому це важливо,
  • поясніть, як це отримати.

Для початку Вольфрам | Альфа визначає це більш простими термінами, ніж Вікіпедія :

Вираз такий, що кожен доданок формується повторенням певної математичної операції.


Математика

Якщо ваш учень (або людина, яку ви теж пояснюєте, відтепер я скажу, що студент) має хоча б певну математичну основу, вони, очевидно, вже стикалися з рекурсією, вивчаючи серію та їх поняття рекурсивності та їх відношення .

Дуже хороший спосіб почати - це продемонструвати серію і сказати, що це просто просто рекурсія:

  • математична функція ...
  • ... що закликає себе обчислити значення, відповідне n-му елементу ...
  • ... і яка визначає деякі межі.

Зазвичай ви або отримуєте "так, вот," у кращому випадку, тому що вони все ще не користуються ним, або, швидше за все, дуже глибоким хропінням.


Приклади кодування

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

На цьому етапі мої учні зазвичай знають, як щось надрукувати на екрані. Припускаючи, що ми використовуємо C, вони знають, як надрукувати одну таблицю за допомогою writeабо printf. Вони також знають про петлі управління.

Зазвичай я вдаюсь до кількох повторюваних і простих проблем програмування, поки вони їх не отримають:

  • факторні операції,
  • принтер з алфавітом,
  • принтер із зворотним алфавітом,
  • зведення операції.

Факторський

Factorial - це дуже проста математична концепція, яку можна зрозуміти, а реалізація дуже близька до її математичного подання. Однак спочатку вони можуть не отримати.

Рекурсивне визначення факторної операції

Алфавіти

Версія алфавіту цікава, щоб навчити їх думати про впорядкування своїх рекурсивних висловлювань. Як і вказівники, вони просто кидатимуть лінії навмання. Сенс полягає в тому, щоб довести їх до усвідомлення того, що цикл можна перевернути, змінивши умови АБО , просто перевернувши порядок висловлювань у вашій функції. Тут допомагає друк алфавіту, оскільки це щось візуальне для них. Просто запропонуйте їм написати функцію, яка буде надрукувати один символ для кожного дзвінка, а сам дзвонить рекурсивно, щоб написати наступний (або попередній).

Фанати FP, пропустити той факт, що друк матеріалів на вихідний потік зараз є побічним ефектом ... Давайте не будемо занадто дратувати на фронті FP. (Але якщо ви використовуєте мову із підтримкою списку, не соромтеся приєднатись до списку під час кожної ітерації та просто надрукуйте кінцевий результат. Але зазвичай я починаю їх із C, що, на жаль, не найкраще для подібних проблем та концепцій) .

Експоненція

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

Його проста форма:

Проста форма операції експоненції

можна виразити так повторенням:

Відношення повторення для операції експоненції

Важче

Після того, як ці прості проблеми будуть показані та повторно реалізовані в навчальних посібниках, ви можете дати трохи складніші (але дуже класичні) вправи:

Примітка. Знову ж таки, деякі з них насправді не складніші ... Вони просто підходять до проблеми прямо з того самого кута або трохи іншого. Але практика робить ідеальною.


Помічники

Довідка

Деякі читання ніколи не шкодять. Добре це буде спочатку, і вони почуватимуться ще більше втраченими. Це така річ, яка росте на вас і яка сидить на потилиці, поки одного дня ви не зрозумієте, що нарешті це отримаєте. І тоді ти думаєш, що ти читаєш ці речі. На даний момент сторінки рекурсії , рекурсії в галузі інформатики та рекурсійних зв’язків у Вікіпедії.

Рівень / Глибина

Припустимо, що ваші студенти не мають великого досвіду кодування, надайте заглушки коду. Після перших спроб надайте їм функцію друку, яка може відображати рівень рекурсії. Друк числового значення рівня допомагає.

Діаграма "Склади як малюнки"

Введення друкованого результату (або вихідного рівня) також допомагає, оскільки воно дає ще одне візуальне уявлення про те, що робить ваша програма, відкриваючи та закриваючи контексти стеку, як ящики або папки в провіднику файлової системи.

Рекурсивні абревіатури

Якщо ваш учень вже трохи розбирається в комп'ютерній культурі, він може вже використовувати деякі проекти / програмні засоби з іменами з використанням рекурсивних абревіатур . Протягом певного часу існує традиція, особливо у проектах GNU. Деякі приклади включають:

Рекурсивна:

  • GNU - "GNU не Unix"
  • Нагіос - "Нагіос не наполягає на святі"
  • PHP - "PHP Hypertext Preprocessor" (і оригінальна "Персональна домашня сторінка")
  • Вино - "Вино не емулятор"
  • Zile - "Зіль втрачає Emacs"

Взаємно рекурсивні:

  • HURD - "HIRD Unix-замінюючих демонів" (де HIRD - "HURD інтерфейсів, що представляють глибину")

Нехай вони спробують придумати своє.

Так само є багато випадків рекурсивного гумору, як-от рекурсивна корекція пошуку 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 :

Чоловічі та жіночі послідовності Hofstadter

Однак, з точки зору коду, слід зазначити, що реалізація взаємно рекурсивного рішення часто призводить до дублювання коду і має бути скоріше упорядкована в єдину рекурсивну форму (див . Розв’язання кожної головоломки Судоку Пітера Норвіга .


5
Я читаю вашу відповідь, побачивши її майже після 5-го чи шостого разу. Думаю, це було приємно, але занадто довго для залучення інших користувачів. Тут я дізнався багато речей про викладання рекурсії. Як викладач, чи не могли б ви оцінити мою ідею викладання рекурсії- програмісти.stackexchange.com
questions/25052/…

9
@Gulshan, я думаю, що ця відповідь є настільки ж всеосяжною, як і будь-яка людина, і її легко «проціджують» випадкові читачі. Отже, це отримує static unsigned int vote = 1;від мене. Пробачте статичний гумор, якщо хочете :) Це найкраща відповідь поки.
Тим Пост

1
@Gulsan: тільки той, хто хоче вчитися, готовий витратити час на те, щоб зробити це належним чином :) Я не дуже проти. Іноді коротка відповідь є елегантною і передає багато корисної та необхідної інформації, щоб розпочати або пояснити загальну концепцію. Я просто хотів отримати більш довгу відповідь на це питання, і, розглядаючи ОП, згадує питання, за яке мені було присвоєно "правильну" відповідь і запитує аналогічну, я вважаю за доцільне надати такий самий варіант відповіді. Радий, що ти щось дізнався.
хайлем

@Gulshan: Зараз щодо вашої відповіді: перший пункт мене дуже збентежив, я мушу сказати. Мені подобається, що ви описуєте поняття рекурсивних функцій як щось, що змінюється стан поступово, але я вважаю, що ваш спосіб подання дещо дивний. Прочитавши ваші 3 бали, я не очікував, що багато студентів раптом зможуть вирішити Ханой. Але це може бути просто формулюванням.
хайлем

Я зіткнувся з гарним способом показати, що одна і та ж змінна може мати різні значення в різних глибинах рекурсії: розглянути можливість студентам записати кроки, які вони виконують, та значення змінних, дотримуючись деякого рекурсивного коду. Коли вони досягнуть рекурсії, запропонуйте їм почати знову новий твір. Як тільки вони досягнуть умови виходу, попросіть їх повернутися до попереднього твору. Це по суті емуляція стеку викликів, але це хороший спосіб показати ці відмінності.
Енді Хант

58

Виклик функції з тієї самої функції.


2
Це найкраще пояснення для початку. Просте і суттєве; Після того, як ви встановите цей підсумок, перейдіть до всіх детальних відомостей про подорож.
поштовх

27

Рекурсія - це функція, яка викликає себе.

Як користуватися ним, коли ним користуватися і як уникнути поганого дизайну, важливо знати, що вимагає, щоб ви спробували це на собі, і зрозуміти, що відбувається.

Найголовніше, що вам потрібно знати, - бути дуже обережним, щоб не отримати цикл, який ніколи не закінчується. Відповідь pramodc84 на ваше запитання має цю помилку: вона ніколи не закінчується ...
Рекурсивна функція повинна завжди перевіряти стан, щоб визначити, чи повинен він викликати себе ще раз чи ні.

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


Ви все одно можете реалізовувати останній абзац в ітераційному порядку, хоча очевидно, що рекурсивно було б краще. "Як його використовувати, коли ним користуватися і як уникнути поганого дизайну, важливо знати, що вимагає, щоб ви спробували це на собі, і зрозуміти, що відбувається". Я подумав, що суть питання - отримати пояснення для такі речі.
хайлем

@haylem: Ви маєте рацію щодо того, що відповідь на тему "Як користуватися ним, коли ним користуватися і як уникнути поганого дизайну .." буде більше на місці, ніж те, що просить ОП (не просто "спробуйте самі "як я вже говорив), але це вимагатиме більш широкої лекції більш викладацького виду, а не швидкої відповіді на питання тут. Ти зробив дуже гарну роботу зі своєю відповіддю . +1 для цього ... Тим, кому дійсно потрібно зрозуміти концепцію, виграють від того, щоб прочитати вашу відповідь.
трепет

як щодо пари функцій, які дзвонять один одному. Дзвінки B, які повторно дзвонять до тих пір, поки не буде досягнуто певної умови. це все ще вважатиметься рекурсією?
santiagozky

Так, функція aвсе ще викликає себе, просто побічно (за допомогою виклику b).
kindall

1
@santiagozky: Як сказав добродушний , це все ще рекурсія. Однак я б рекомендував цього не робити. Під час використання рекурсії в коді повинно бути чітко зрозуміло, що рекурсія існує. Якщо викликати себе опосередковано через іншу функцію, це набагато важче зрозуміти, що відбувається. Якщо ви не знаєте, що функція ефективно викликає себе, ви можете потрапити в ситуацію, коли ви (або хтось інший, хто не створив цю функцію) порушили певну умову для рекурсії (змінюючи функціональність у коді), і закінчите в мертвому замці з нескінченною петлею.
трепет

21

Рекурсивне програмування - це процес прогресивного зменшення проблеми, щоб легше вирішити версії самої себе.

Кожна рекурсивна функція має тенденцію до:

  1. взяти список для обробки або якусь іншу структуру або проблемний домен
  2. мати справу з поточною точкою / кроком
  3. зателефонувати собі на залишки / субдомен (и)
  4. об'єднати або використати результати роботи з піддоменом

Коли крок 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

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


Ключовим компонентом практичної рекурсії є ідея використання трохи меншої проблеми для вирішення більшої. Інакше у вас просто нескінченна рекурсія.
Баррі Браун

@Barry Brown: Цілком правильно. Звідси мій вислів "... зменшення проблеми до легшого вирішення самих версій"
Включення

Я б не обов'язково говорив так ... Це часто трапляється, особливо це стосується проблем розділення та перемоги або для ситуацій, коли ви дійсно визначаєте відношення рецидиву, яке зводиться до простого випадку. Але я б сказав, що це більше про те, щоб довести, що для кожної ітерації N вашої проблеми існує обчислюваний випадок N + 1.
haylem

1
@Sean McMillan: Рекурсія є потужним інструментом при використанні в областях, які їй підходять. Я занадто часто бачу, що це використовується як розумний спосіб вирішити якусь відносно тривіальну проблему, яка масово затуманює справжню суть задачі.
Орлінг

20

ОП заявила, що рекурсії не існує в реальному світі, але я прошу відрізнятися.

Візьмемо реальну "операцію" з нарізки піци. Ви вийняли піцу з духовки, і щоб її подати, ви повинні розрізати її навпіл, потім розрізати їх навпіл, а потім знову розрізати ці отримані половинки навпіл.

Операцію нарізки піци ви виконуєте знову і знову, поки не отримаєте потрібний результат (кількість скибочок). А для аргументів скажемо, що необрізана піца - це сам шматочок.

Ось приклад у 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 скибочок, він не буде працювати назавжди?


16

Гаразд, я намагаюся зберегти це просто і стисло.

Рекурсивна функція - це функції, які називають себе. Рекурсивна функція складається з трьох речей:

  1. Логіка
  2. Заклик до себе
  3. Коли потрібно припинити.

Найкращий спосіб записати рекурсивні методи - це думати про метод, який ви намагаєтеся написати як простий приклад, обробляючи лише одну петлю процесу, який ви хочете повторити, а потім додайте виклик до самого методу та додайте, коли ви хочете припинити. Найкращий спосіб вчитися - це практикувати, як усі речі.

Оскільки це веб-сайт програмістів, я не буду писати код, але ось хороше посилання

якщо ви отримали цей жарт, ви зрозуміли, що означає рекурсія.


Дивіться також цю відповідь: programmers.stackexchange.com/questions/25052/… (-:
Мерф

4
Строго, рекурсія не потребує умови припинення; гарантування припинення рекурсії обмежує типи проблем, які може вирішити рекурсивна функція, і є деякі типи семантичних презентацій, які взагалі не потребують припинення.
стипендіати доналу

6

Рекурсія - це інструмент, який програміст може використовувати для виклику функціонального виклику на себе. Послідовність Фібоначчі є прикладом підручника, як використовується рекурсія.

Більшість рекурсивних кодів, якщо не все можна виразити як ітеративну функцію, але зазвичай її безладно. Хорошими прикладами інших рекурсивних програм є структури даних, такі як дерева, бінарне дерево пошуку та навіть квакісорт.

Рекурсія використовується для того, щоб зробити код менш неохайним, майте на увазі, що він зазвичай повільніше і вимагає більше пам’яті.


Це буде повільніше або вимагає більше пам’яті, дуже залежить від використання.
Включення

5
Розрахунок послідовності Фібоначчі - це жахливо робити рекурсивно. Обхід дерева - це набагато природніше використання рекурсії. Зазвичай, коли рекурсія використовується добре, вона не повільніша і не потребує більше пам’яті, оскільки вам доведеться підтримувати власний стек замість стека викликів.
Девід Торнлі

1
@Dave: Я б не заперечував з цим, але думаю, що Фібоначчі - хороший приклад для початку.
Брайан Харрінгтон

5

Мені подобається використовувати цей:

Як ти ходиш до магазину?

Якщо ви біля входу в магазин, просто пройдіть його. В іншому випадку зробіть один крок, а потім пройдіть решту шляху до магазину.

Важливо включити три аспекти:

  • Тривіальний базовий випадок
  • Вирішення невеликого фрагмента проблеми
  • Вирішення решти проблеми рекурсивно

Ми реально багато використовуємо рекурсію в повсякденному житті; ми просто не думаємо про це так.


Це не рекурсія. Було б, якби ви розділили його на два: пройдіться половиною шляху до магазину, пройдіться другою половиною. Рекурсивні.

2
Це рекурсія. Це не розділяйте і не перемагайте, але це лише один тип рекурсії. Алгоритми графіків (як пошук шляху) рясніють рекурсивними поняттями.
deadalnix

2
Це рекурсія, але я вважаю це поганим прикладом, оскільки занадто легко подумки перекласти "зробіть один крок, потім пройдіть решту шляху до магазину" в ітеративний алгоритм. Я відчуваю, що це рівносильно перетворенню добре написаного forциклу в безглузду рекурсивну функцію.
Брайан

3

Найкращий приклад, на який я хотів би вказати вам, - це мова програмування на C & R. У цій книзі (і я цитую її з пам’яті) запис про рекурсію (окремо) на індексній сторінці перераховує фактичну сторінку, де вони говорять про рекурсію та вказівна сторінка.


2

Джош К уже згадував ляльок Матрошка . Припустимо, що ви хочете навчитися чогось, що знає лише найкоротша лялька. Проблема полягає в тому, що ви не можете реально спілкуватися з нею безпосередньо, адже вона спочатку живе всередині високої ляльки, яка на першій картинці розміщена зліва. Ця структура іде так (лялька живе всередині вище ляльки), поки не закінчується лише найвищою.

Тож єдине, що ви можете зробити - це поставити своє запитання найвищій ляльці. Найвищої ляльці (яка не знає відповіді) потрібно буде передати ваше питання коротшій ляльці (яка на першій картинці знаходиться праворуч). Оскільки у неї також немає відповіді, їй потрібно попросити наступну коротшу ляльку. Так буде йти, поки повідомлення не дійде до найкоротшої ляльки. Найкоротша лялька (яка єдина, хто знає таємну відповідь) передасть відповідь наступній ляльці з високим рівнем (знайдена зліва її), яка передасть її наступній ляльці вище ... і це триватиме, поки відповідь не буде досягає свого кінцевого пункту призначення, який є найвищою лялькою і нарешті ... ви :)

Ось що насправді робить рекурсія. Функція / метод викликає себе до отримання очікуваної відповіді. Ось чому, коли ви пишете рекурсивний код, дуже важливо вирішити, коли рекурсія повинна припинитися.

Не найкраще пояснення, але, сподіваємось, це допомагає.


2

Рекурсія 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);
}

Переваги:

  • Природне вираження - для багатьох типів алгоритмів це дуже природний спосіб вираження функції.
  • Знижений LOC - часто визначати функцію рекурсивно набагато більш стисло.
  • Швидкість - У певних випадках, залежно від мови та архітектури комп'ютера, рекурсія алгоритму відбувається швидше, ніж еквівалентне ітераційне рішення, як правило, тому що здійснення виклику функції - це швидша операція на апаратному рівні, ніж операції та доступ до пам'яті, необхідні для ітераційного циклу.
  • Подільність - багато рекурсивних алгоритмів мають менталітет "розділити і перемогти"; результат операції - це функція результату тієї ж операції, що виконується на кожній з двох половин входу. Це дозволяє розділити роботу надвоє на кожному рівні, а якщо є, ви можете надати іншу половину іншому "виконавчому блоку" для обробки. Зазвичай це важче або неможливо за допомогою ітеративного алгоритму.

Недоліки:

  • Вимагає розуміння - Ви просто повинні "зрозуміти" концепцію рекурсії, щоб зрозуміти, що відбувається, а тому написати та підтримувати ефективні рекурсивні алгоритми. Інакше це просто схоже на чорну магію.
  • Контекстно - залежність від того, чи є рекурсія хорошою ідеєю чи ні, залежить від того, наскільки витончено алгоритм можна визначити з точки зору самого себе. Хоча можливо побудувати, наприклад, рекурсивний SelectionSort, ітеративний алгоритм, як правило, є більш зрозумілим.
  • Доступ до оперативної пам'яті для стека викликів - як правило, виклики функцій дешевше, ніж доступ до кешу, що може зробити рекурсію швидшою, ніж ітерація. Але, як правило, існує обмеження глибини стека викликів, що може спричинити рекурсію до помилки, де буде працювати ітераційний алгоритм.
  • Нескінченна рекурсія - Ви повинні знати, коли зупинитись. Нескінченна ітерація також можлива, але петлі, що залучаються, зазвичай легше зрозуміти і, таким чином, налагодити.

1

Я використовую приклад - це проблема, з якою я стикався в реальному житті. У вас є контейнер (наприклад, великий рюкзак, який ви збираєтеся взяти в подорож) і хочете дізнатися загальну вагу. Ви маєте в контейнері два-три сипучих предмета та деякі інші контейнери (скажімо, мішки з речами.) Вага всього контейнера, очевидно, вага порожнього контейнера плюс вага всього, що знаходиться в ньому. Що стосується сипучих предметів, ви можете їх просто зважити, а для мішків з речовинами ви можете їх просто зважити або ви можете сказати, "а вага кожного пакета - це вага порожнього контейнера плюс вага всього в ньому". А потім ви продовжуєте перебирати контейнери в контейнери і так далі, поки не дістанетесь до точки, де в контейнері є просто сипучі речі. Це рекурсія.

Ви можете подумати, що цього в реальному житті ніколи не буває, але уявіть, що намагаєтесь порахувати або скласти зарплату людей у ​​певній компанії чи підрозділі, де є суміш людей, які просто працюють на компанію, людей у ​​підрозділах, а потім у підрозділи є відділи тощо. Або продажі в країні, в якій є регіони, в деяких з яких є субрегіони тощо. Такі проблеми виникають постійно у бізнесі.


0

Рекурсія може бути використана для вирішення багатьох проблем з підрахунком. Наприклад, скажіть, що у вас на вечірці група з 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


0

У житті (на відміну від комп'ютерної програми) рекурсія рідко трапляється під нашим безпосереднім контролем, тому що це може заплутати те, що відбувається. Крім того, сприйняття, як правило, стосується побічних ефектів, а не бути функціонально чистим, тому, якщо трапляється рекурсія, ви можете цього не помітити.

Рекурсія все ж відбувається тут, у світі. Багато.

Хорошим прикладом є (спрощена версія) кругообіг води:

  • Сонце гріє озеро
  • Вода піднімається в небо і утворює хмари
  • Хмари спускаються на гору
  • На горі повітря стає занадто холодним, щоб утримувати їх вологу
  • Падає дощ
  • Утворюється річка
  • Вода в річці стікає в озеро

Це цикл, який спричиняє повторення його самоврядування. Він є рекурсивним.

Ще одне місце, де ви можете отримати рекурсію, - це англійська (та людська мова взагалі). Ви можете його не розпізнати спочатку, але спосіб генерування речення є рекурсивним, оскільки правила дозволяють нам вставити один екземпляр символу в інший екземпляр того ж символу.

З мовного інстинкту Стівена Пінкера:

якщо або дівчина їсть морозиво, або дівчина їсть цукерки, то хлопчик їсть хот-доги

Це ціле речення, яке містить інші цілі речення:

дівчина їсть морозиво

дівчина їсть цукерки

хлопчик їсть хот-доги

Акт розуміння повного речення передбачає розуміння менших речень, які використовують той самий набір розумових хитрощів, щоб його розуміти як повне речення.

Щоб зрозуміти рекурсію з точки зору програмування, найпростіше подивитися на проблему, яку можна вирішити рекурсією, і зрозуміти, чому це має бути і що це означає, що потрібно робити.

Для прикладу я використовую найбільшу загальну функцію дільника, або 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
}

У програмі важливо мати стан зупинки, інакше функція буде повторюватися назавжди, що з часом спричинить переповнення стека!

Причиною для використання тут рекурсії, а не циклу часу чи іншої ітеративної конструкції, є те, що, читаючи код, він повідомляє вам, що він робить, і що буде далі, тому легше зрозуміти, чи працює він правильно .


1
Я знайшов приклад водного циклу ітераційним. Другий приклад мови здається більше поділом і перемогою, ніж рекурсією.
Гульшан

@Gulshan: Я б сказав, що водяний цикл є рекурсивним, тому що він змушує себе повторюватися, як і рекурсивна функція. Це не так, як фарбувати кімнату, де ви виконуєте один і той же набір кроків над декількома предметами (стінами, стелею тощо), як у петлі. Приклад мови використовує ділення та перемогу, але також "функція", яка називається, закликає його самому працювати над вкладеними реченнями, тому рекурсивна таким чином.
Метт Еллен

У водному циклі цикл запускається сонцем, і жоден інший елемент циклу не змушує сонце запускати його знову. Отже, де рекурсивний дзвінок?
Гульшан

Не існує рекурсивного дзвінка! Це не функція. : D Це рекурсивно, оскільки викликає рецидиви самості. Вода з озера повертається до озера, і цикл починається знову. Якби якась інша система подавала воду в озеро, то це було б ітеративно.
Метт Еллен

1
Водний цикл - це цикл часу. Звичайно, певний час цикл можна виразити за допомогою рекурсії, але це зробить стек. Будь ласка, не варто.
Брайан

0

Ось реальний приклад світу для рекурсії.

Дозвольте собі уявити, що у них є комічна колекція, і ви збираєтеся все це перемішати у велику купу. Обережно - якщо у них дійсно є колекція, вони можуть моментально вбити вас, коли ви просто згадаєте про ідею зробити це.

Тепер дозвольте сортувати цю велику несортовану купу коміксів за допомогою цього посібника:

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.

Приємно тут: Коли вони переходять до одиничних питань, вони мають повний "стек кадр" з локальними палі, видимими перед ними на землі. Дайте їм декілька роздруківків цього посібника та відкладіть по одному стовпчику з позначкою, де ви зараз перебуваєте на цьому рівні (тобто стан локальних змінних), щоб ви могли продовжуватись там у кожному Готово.

Ось, в чому полягає рекурсія: Виконуючи той самий процес, лише на більш тонкому рівні деталей, тим більше ви вступаєте в нього.


-1
  • Скасувати, якщо досягнуто умови виходу
  • зробити щось, щоб змінити стан речей
  • виконайте роботу, починаючи з теперішнього стану речей

Рекурсія - це дуже стислий спосіб виразити щось, що доводиться повторювати, поки щось не буде досягнуто.



-2

Приємне пояснення рекурсії - це буквально "дія, яка повторюється всередині себе".

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

Його функція paint () викликає себе знову і знову, щоб скласти свою більшу функцію paint_wall ().

Сподіваємось, у цього бідного художника є якась умова зупинки :)


6
Мені приклад здається більше схожим на повторювану процедуру, а не на рекурсивну.
Гульшан

@Gulshan: Рекурсія та ітерація роблять подібні речі, і часто все буде добре. Функціональні мови зазвичай використовують рекурсію замість ітерації. Є кращі приклади рекурсії, коли було б незручно писати одне і те ж повторно.
Девід Торнлі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.