Реальні приклади рекурсії [закрито]


97

У чому полягають реальні проблеми, коли рекурсивний підхід є природним рішенням, крім глибокого пошуку (DFS)?

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


2
Дякуємо за всі пропозиції, але всі пропонують обхід дерева / мережі. Тези є, по суті, усіма прикладами пошуку глибини-першого (або, здається, BFS). Я шукав інші добре мотивовані алгоритми / проблеми.
redfood

10
Мені це питання подобається! "Розкажіть мені про всі способи використання техніки X, Крім основного практичного використання техніки X"
Джастін Стандарт

1
Я постійно використовую рекурсію, але зазвичай для математики та графічних речей. Я намагаюся шукати приклади рекурсії, які мали б значення для непрограмістів.
redfood

6
Оберіть власні пригодницькі романи! Я хочу прочитати цілу справу, і рекурсія - найкращий спосіб це зробити.
Андрес

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

Відповіді:


41

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

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

Це створює досить надокучливі хвилі хаосу, коли тип В завжди заражає безліч типів А.

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

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

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

Повторюйте, поки черга заражених людей не стане 0, а потім почекайте чергового спалаху.

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


2
Дякую - це все-таки обхід графіків, але він добре мотивований і має сенс для людей, які не програмісти.
redfood

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

4
цей реальний приклад відчувається настільки звичним зараз :(
haroldolivieri

109

Реальний приклад рекурсії

Соняшник


12
кодовано рекурсією архітектором Матриці :)
Марсель

3
Як це рекурсивно? Звичайно, це гарно. Але рекурсивний? Фрактальна капуста добре би працювала, але я не бачу самоподібностей у цій квітці.
Клеман

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

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

65

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

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

import java.io.File;

public class DirectoryContentAnalyserOne implements DirectoryContentAnalyser {

    private static StringBuilder indentation = new StringBuilder();

    public static void main (String args [] ){
        // Here you pass the path to the directory to be scanned
        getDirectoryContent("C:\\DirOne\\DirTwo\\AndSoOn");
    }

    private static void getDirectoryContent(String filePath) {

        File currentDirOrFile = new File(filePath);

        if ( !currentDirOrFile.exists() ){
            return;
        }
        else if ( currentDirOrFile.isFile() ){
            System.out.println(indentation + currentDirOrFile.getName());
            return;
        }
        else{
            System.out.println("\n" + indentation + "|_" +currentDirOrFile.getName());
            indentation.append("   ");

            for ( String currentFileOrDirName : currentDirOrFile.list()){
                getPrivateDirectoryContent(currentDirOrFile + "\\" + currentFileOrDirName);
            }

            if (indentation.length() - 3 > 3 ){
                indentation.delete(indentation.length() - 3, indentation.length());
            }
        }       
    }

}

2
Файлова система забезпечує мотивацію (що добре, дякую), але це конкретний приклад DFS.
redfood

4
Я не отримав скорочення "DFS" - минув деякий час, відколи я сидів у класі.
Метт Діллард,

5
пошук по глибині: dfs (вузол) {foreach child in node {visit (child); }}
Haoest

Простий приклад коду див., Наприклад, stackoverflow.com/questions/126756/…
Jonik

Чи є помилка в цьому коді? Чи не слід замінити getPrivateDirectoryContent () на getDirectoryContent ()?
Shn_Android_Dev


16

Приклад Метта Ділларда хороший. Взагалі кажучи, будь-яка ходьба по дереву, як правило, може бути дуже легко рекурсивна. Наприклад, складання дерев синтаксичного аналізу, перегляд XML чи HTML тощо.


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


13

Рекурсія доцільна, коли проблему можна вирішити, розділивши її на підзадачі, які можуть використовувати той самий алгоритм їх вирішення. Алгоритми на деревах та відсортовані списки - це природна відповідність. Багато проблем в обчислювальній геометрії (та 3D-іграх) можна вирішити рекурсивно, використовуючи дерева двійкового простору (BSP), жирові підрозділи або інші способи поділу світу на підчастини.

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


11

Безумовно, що багато компіляторів активно використовують рекурсію. Комп’ютерні мови самі по собі є рекурсивними (тобто ви можете вбудовувати оператори if якщо в інші оператори if) тощо).


Вбудовано, якщо оператори не є рекурсією.
Джон Мігер

Але їх розбір вимагає рекурсії, Джон.
Апокалісп

2
Джон, той факт, що ти можеш вкладатись у оператори, означає, що визначення мови (і, ймовірно, парсер мови) є рекурсивним.
Дерек Парк

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

Відповідь Коді Брові, що згадує "складання дерев синтаксичного аналізу", також вказувала на цю сферу: мовний аналіз / інтерпретація / компіляція.
imz - Іван Захарящев

9

Вимкнення / налаштування лише для читання для всіх дочірніх елементів керування в елементі керування контейнером. Мені потрібно було це зробити, оскільки деякі елементи контролю дітей були самими контейнерами.

public static void SetReadOnly(Control ctrl, bool readOnly)
{
    //set the control read only
    SetControlReadOnly(ctrl, readOnly);

    if (ctrl.Controls != null && ctrl.Controls.Count > 0)
    {
        //recursively loop through all child controls
        foreach (Control c in ctrl.Controls)
            SetReadOnly(c, readOnly);
    }
}

8

Знаменитий цикл Eval / Apply від SICP

текст заміщення
(джерело: mit.edu )

Ось визначення eval:

(define (eval exp env)
  (cond ((self-evaluating? exp) exp)
        ((variable? exp) (lookup-variable-value exp env))
        ((quoted? exp) (text-of-quotation exp))
        ((assignment? exp) (eval-assignment exp env))
        ((definition? exp) (eval-definition exp env))
        ((if? exp) (eval-if exp env))
        ((lambda? exp)
         (make-procedure (lambda-parameters exp)
                         (lambda-body exp)
                         env))
        ((begin? exp) 
         (eval-sequence (begin-actions exp) env))
        ((cond? exp) (eval (cond->if exp) env))
        ((application? exp)
         (apply (eval (operator exp) env)
                (list-of-values (operands exp) env)))
        (else
         (error "Unknown expression type - EVAL" exp))))

Ось визначення застосувати:

(define (apply procedure arguments)
  (cond ((primitive-procedure? procedure)
         (apply-primitive-procedure procedure arguments))
        ((compound-procedure? procedure)
         (eval-sequence
           (procedure-body procedure)
           (extend-environment
             (procedure-parameters procedure)
             arguments
             (procedure-environment procedure))))
        (else
         (error
          "Unknown procedure type - APPLY" procedure))))

Ось визначення послідовності eval:

(define (eval-sequence exps env)
  (cond ((last-exp? exps) (eval (first-exp exps) env))
        (else (eval (first-exp exps) env)
              (eval-sequence (rest-exps exps) env))))

eval-> apply-> eval-sequence->eval


7

Рекурсія використовується в таких речах, як дерева BSP, для виявлення зіткнень при розробці ігор (та інших подібних областях).


7

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

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

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


5

Вимоги до реального світу, які я отримав нещодавно:

Вимога A: Впроваджуйте цю функцію після ретельного розуміння вимоги A.


4

Синтаксичні аналізатори та компілятори можуть бути записані методом рекурсивного спуску. Не найкращий спосіб це зробити, оскільки такі інструменти, як lex / yacc, генерують швидші та ефективніші парсери, але концептуально прості та легкі у реалізації, тому вони залишаються поширеними.


4

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

Хороші приклади того, де речі, які містять менші частини, схожі на нього самі:

  • деревоподібна структура (гілка схожа на дерево)
  • списки (частина списку все ще є списком)
  • контейнери (російські ляльки)
  • послідовності (частина послідовності виглядає як наступна)
  • групи об'єктів (підгрупа - це все-таки група об'єктів)

Рекурсія - це техніка, яка дозволяє продовжувати розбивати проблему на дедалі менші шматки, поки один із цих шматочків не стане достатньо дрібним, щоб стати шматочком пирога. Звичайно, після того, як ви їх розіб’єте, вам доведеться «зшити» результати разом у правильному порядку, щоб сформувати загальне рішення вашої початкової проблеми.

Деякі рекурсивні алгоритми сортування, алгоритми ходьби по дереву, алгоритми картографування / зменшення, поділ і володар - все це приклади цієї техніки.

У комп'ютерному програмуванні більшість мов типу зворотного виклику на основі стеку вже мають вбудовані можливості для рекурсії: тобто

  • розбити проблему на менші частини ==> викликати себе на меншій підмножині вихідних даних),
  • стежте за тим, як розділені фігури ==> стек викликів,
  • зшийте результати назад ==> повернення на основі стека


4

Деякі чудові приклади рекурсії можна знайти у функціональних мовах програмування . У функціональних мовах програмування ( Erlang , Haskell , ML / OCaml / F # та ін.) Дуже часто, коли будь-яка обробка списку використовує рекурсію.

Маючи справу зі списками типовими імперативними мовами стилю ООП, дуже часто можна побачити списки, реалізовані як пов'язані списки ([item1 -> item2 -> item3 -> item4]). Однак у деяких функціональних мовах програмування ви виявляєте, що самі списки реалізуються рекурсивно, де "заголовок" списку вказує на перший елемент у списку, а "хвіст" вказує на список, що містить решту елементів ( [item1 -> [item2 -> [item3 -> [item4 -> []]]]]). На мій погляд, це досить креативно.

Ця обробка списків у поєднанні із зіставленням шаблонів ДУЖЕ потужна. Скажімо, я хочу підсумувати список чисел:

let rec Sum numbers =
    match numbers with
    | [] -> 0
    | head::tail -> head + Sum tail

По суті, це говорить "якщо нас викликали з порожнім списком, поверніть 0" (що дозволяє нам розбити рекурсію), інакше повертає значення head + значення Sum, що викликається з рештою елементів (отже, наша рекурсія).

Наприклад, у мене може бути список URL-адрес , я думаю розбити всі URL-адреси, на які посилається кожна URL-адреса, а потім зменшую загальну кількість посилань на / з усіх URL-адрес, щоб генерувати "значення" для сторінки (підхід, який Google бере разом із PageRank і що ви можете знайти, визначені в оригінальній статті MapReduce ). Ви можете зробити це, щоб також генерувати кількість слів у документі. І багато, багато, багато іншого.

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


3

XML, або обхід будь-чого, що є деревом. Хоча, чесно кажучи, я майже ніколи не використовую рекурсію у своїй роботі.


Навіть хвоста-рекурсія?
Апокалісп

Я використовував рекурсію один раз у своїй кар'єрі, і коли рамки змінилися, я позбувся цього. 80% того, що ми робимо, - це CRUD.
Чарльз Грем

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

3

Цикли зворотного зв'язку в ієрархічній організації.

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

Кожен керівник збирає свої / її прямі звіти та каже їм збирати відгуки від своїх прямих звітів.

І далі по лінії.

Люди, які не мають прямих звітів - листкові вузли на дереві - дають свої відгуки.

Зворотній зв'язок рухається назад по дереву, кожен менеджер додає свій власний відгук.

Зрештою всі відгуки повертаються до головного начальника.

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


2

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

Припустимо, також ваш {користувач | клієнт | клієнт | бос} просить вас розмістити на всіх сторінках стежку, щоб показати, де ви знаходитесь у дереві.

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

Звичайно, ви натискаєте db кілька разів на сторінку в цьому прикладі, тому, можливо, ви захочете використати якийсь псевдонім SQL, де ви шукаєте таблицю сторінок як a, а таблицю сторінок знову як b, і приєднаєте a.id за допомогою b.parent, тому ви змушуєте базу даних виконувати рекурсивні об'єднання. Минув якийсь час, тому мій синтаксис, мабуть, не корисний.

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

У всякому разі, це мої $ .02


2

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

Це те, що я насправді закодував. Це приємно і легко з рекурсією.


2

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

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



2
  • Розбір файлу XML .
  • Ефективний пошук у багатовимірних просторах. E. g. чотирикутні дерева у 2D, октові дерева у 3D, kd-дерева тощо.
  • Ієрархічна кластеризація.
  • Якщо подумати, обхід будь-якої ієрархічної структури, природно, піддається рекурсії.
  • Метапрограмування шаблонів на C ++, де немає циклів, а рекурсія - єдиний спосіб.

"XML" не є суттєвим для ідеї цієї відповіді (а згадування конкретно XML може викликати огиду / нудьгу людей, яких ви навчаєте). Будь-яка типова мова (комп'ютерна або природна) може служити прикладом для рекурсивної проблеми розбору.
imz - Іван Захарящев

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


2

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

shop.oreilly.com/product/9780596510046.do

www.amazon.com/Beautiful-Code-Leading-Programmers-Practice/dp/0596510047

(Клацніть на перший підзаголовок під главою 3: "Найкрасивіший код, який я коли-небудь писав").


1
І MergeSort теж простіший з рекурсією.
Метью Шінкель,

1
Посилання порушено. Чи можете ви додати назву книги?
Пітер Мортенсен

@PeterMortensen, книга "Гарний код" Грега Вілсона та Енді Орама. Я оновив посилання, хоча, здається, О'Райлі більше не дозволяє заглядати всередину. Але ви можете поглянути на Amazon.
Фабіо Чеконелло

1

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

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


1

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


1

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


1

Множення натуральних чисел є реальним прикладом рекурсії:

To multiply x by y
  if x is 0
    the answer is 0
  if x is 1
    the answer is y
  otherwise
    multiply x - 1 by y, and add x
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.