Реальные примеры рекурсии [закрыто]

98

Каковы реальные проблемы, в которых рекурсивный подход является естественным решением помимо поиска в глубину (DFS)?

(Я не рассматриваю Ханойскую башню , число Фибоначчи или факториальные проблемы реального мира. В моем понимании они немного надуманы.)

Питер Мортенсен
источник
2
Спасибо за все предложения, но все предлагают обход дерева / сети. По сути, это все примеры поиска в глубину (или, я думаю, BFS). Я искал другие хорошо мотивированные алгоритмы / задачи.
redfood
10
Мне нравится этот вопрос! «Расскажите мне обо всех применениях техники X, ЗА ИСКЛЮЧЕНИЕМ основного практического применения техники X»
Джастин Стандарт
1
Я использую рекурсию все время, но обычно для математики и графики. Я пытаюсь найти примеры рекурсии, которые были бы значимы для непрограммистов.
redfood
6
Выбирайте собственные приключенческие романы! Я хочу прочитать все, и рекурсия - лучший способ сделать это.
Андрес
В реальном мире нет рекурсии. Рекурсия - это математическая абстракция. Используя рекурсию, вы можете моделировать множество вещей. В этом смысле Фибоначчи абсолютно реальный мир, поскольку есть немало реальных проблем, которые можно смоделировать таким образом. Если вы думаете, что Фибоначчи не является реальным миром, тогда я бы сказал, что все другие примеры также являются абстракциями, а не примерами из реального мира.
Зейн

Ответы:

43

Здесь много примеров математики, но вы хотели пример из реального мира , поэтому немного подумав, возможно, это лучшее, что я могу предложить:

Вы найдете человека, который заразился данной контагиозной инфекцией, которая не является смертельной и быстро излечивается (тип A), за исключением одного из пяти человек (мы назовем их типом B), который постоянно инфицирован ею и не показывает симптомы и просто действует как распространитель.

Это создает довольно раздражающие волны хаоса, когда когда-либо тип B заражает множество типов A.

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

То, как вы это сделаете, было бы социальным открытием, если бы инфицированный человек (тип A) выбрал все свои контакты за последнюю неделю, пометив каждый контакт в куче. Когда вы проверяете, что человек заражен, добавьте его в очередь для отслеживания. Если человек относится к типу B, добавьте его в «продолжение» во главе (потому что вы хотите остановить это быстро).

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

Повторяйте, пока очередь зараженных людей не станет равной 0, а затем дождитесь новой вспышки.

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

Кент Фредрик
источник
2
Спасибо - это все еще обход графа, но он хорошо мотивирован и имеет смысл для людей, не являющихся программистами.
redfood
Я считаю, что найти пациента 0 было бы лучшим примером. Определите все взаимодействия, которые могли вызвать инфекцию. Повторите для всех участников, которые были заразными во время взаимодействия, пока не будут обнаружены никакие заразные
Уильям ФитцПатрик
7
этот пример из реального мира теперь кажется таким знакомым :(
haroldolivieri
110

Пример рекурсии из реального мира

Подсолнух

оборота Hans Sjunnesson
источник
13
закодировано с рекурсией архитектором Матрицы :)
Марсель
3
Как это рекурсивно? Конечно, красиво. Но рекурсивный? Фрактальная капуста отлично подошла бы, но я не вижу самоподобия в этом цветке.
Clément
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 (узел) {каждый дочерний элемент в узле {посещение (дочерний); }}
Haoest
Для простого примера кода см., Например, stackoverflow.com/questions/126756/…
Джоник
В этом коде есть ошибка? Не следует ли getPrivateDirectoryContent () заменять на getDirectoryContent ()?
Shn_Android_Dev
16

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

Серафина Бросиус
источник
Я считаю, что это «компиляция деревьев синтаксического анализа» является разумным ответом, который включает деревья, но все же не является проблемой поиска, как того хочет спрашивающий. Его можно обобщить до некоторого общего понятия компиляции или интерпретации языка. Это также может быть «интерпретация» (понимание, слушание) естественного языка, например английского.
imz - Иван Захарящев
13

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

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

Сэм
источник
11

Конечно, многие компиляторы активно используют рекурсию. Компьютерные языки сами по себе рекурсивны (т. Е. Вы можете встраивать операторы if в другие операторы if и т. Д.).

Мартин Кот
источник
Встроенные операторы if не являются рекурсией.
Джон Мигер
Но для их анализа требуется рекурсия, Джон.
Apocalisp
2
Джон, тот факт, что вы можете вкладывать операторы if, означает, что определение языка (и, вероятно, синтаксический анализатор языка) является рекурсивным.
Дерек Парк
Рекурсивный спуск - один из самых простых способов вручную написать компилятор. Не так просто, как использовать такой инструмент, как 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))))

Вот определение слова apply:

(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

jfs
источник
7

Рекурсия используется в таких вещах, как BSP-деревья для обнаружения столкновений при разработке игр (и других подобных областях).

отметка
источник
7

Люди часто сортируют стопки документов, используя рекурсивный метод. Например, представьте, что вы сортируете 100 документов по именам. Сначала сложите документы в стопки по первой букве, затем отсортируйте каждую стопку.

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

В организациях начальники часто отдают команды начальникам отделов, а те, в свою очередь, отдают команды менеджерам и т. Д.

Tkerwin
источник
5

Требования реального мира, которые я получил недавно:

Требование А. Реализуйте эту функцию после полного понимания требования А.

Дракон
источник
4

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

Davenpcj
источник
4

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

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

  • древовидная структура (ветка похожа на дерево)
  • списки (часть списка остается списком)
  • контейнеры (матрешки)
  • последовательности (часть последовательности выглядит как следующая)
  • группы объектов (подгруппа - это все еще группа объектов)

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

Некоторые рекурсивные алгоритмы сортировки, алгоритмы обхода деревьев, алгоритмы сопоставления / сокращения, разделяй и властвуй - все это примеры этой техники.

В компьютерном программировании большинство языков типа «вызов-возврат» на основе стека уже имеют встроенные возможности для рекурсии: т.е.

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

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

Петер Мортенсен
источник
4

Некоторые отличные примеры рекурсии можно найти в языках функционального программирования . В языках функционального программирования ( Erlang , Haskell , ML / OCaml / F # и т. Д.) Очень часто при любой обработке списков используется рекурсия.

При работе со списками в типичных языках императивного ООП очень часто можно увидеть списки, реализованные как связанные списки ([item1 -> item2 -> item3 -> item4]). Однако в некоторых языках функционального программирования вы обнаружите, что сами списки реализуются рекурсивно, где «заголовок» списка указывает на первый элемент в списке, а «хвост» указывает на список, содержащий остальные элементы ( [элемент1 -> [элемент2 -> [элемент3 -> [элемент4 -> []]]]]). На мой взгляд, это довольно креативно.

Эта обработка списков в сочетании с сопоставлением с образцом ОЧЕНЬ эффективна. Скажем, я хочу суммировать список чисел:

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

По сути, это говорит: «если бы нас вызвали с пустым списком, верните 0» (что позволяет нам прервать рекурсию), иначе верните значение заголовка + значение Sum, вызванного с оставшимися элементами (следовательно, наша рекурсия).

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

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

Джейсон Олсон
источник
3

XML или обход всего, что является деревом. Хотя, честно говоря, я практически никогда не использую рекурсию в своей работе.

Чарльз Грэм
источник
Даже хвостовой рекурсии?
Apocalisp
Я использовал рекурсию однажды в своей карьере, и когда структура изменилась, я избавился от нее. 80% того, что мы делаем, - это CRUD.
Чарльз Грэм,
1
Упоминание «XML» в первую очередь довольно странно. Это неестественное, не то, с чем обычному человеку, которого вы собираетесь учить, приходится иметь дело в повседневной жизни. Но идея, конечно, вполне разумная.
imz - Иван Захарящев
3

Петли обратной связи в иерархической организации.

Начальник говорит топ-менеджменту собирать отзывы от всех в компании.

Каждый руководитель собирает своих прямых подчиненных и просит их собрать отзывы от своих подчиненных.

И дальше по линии.

Люди без прямых подчиненных - листовые узлы в дереве - оставляют свои отзывы.

Отзывы возвращаются вверх по дереву, и каждый менеджер добавляет свой отзыв.

В конце концов, вся обратная связь возвращается к высшему боссу.

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

отметка
источник
2

Предположим, вы создаете CMS для веб-сайта, где ваши страницы имеют древовидную структуру, при этом корень является домашней страницей.

Предположим также, что ваш {user | client | customer | boss} запрашивает, чтобы вы разместили цепочку навигации на каждой странице, чтобы показать, где вы находитесь в дереве.

Для любой данной страницы n вы можете рекурсивно пройти к родительскому элементу n, его родительскому элементу и т. Д., Чтобы создать список узлов до корня дерева страниц.

Конечно, в этом примере вы нажимаете db несколько раз на страницу, поэтому вы можете использовать некоторый псевдоним SQL, где вы просматриваете таблицу страниц как a, а таблицу страниц снова как b и присоединяетесь к a.id с b.parent, чтобы база данных выполняла рекурсивные соединения. Это было давно, поэтому мой синтаксис, вероятно, бесполезен.

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

В любом случае, это мои 0,02 доллара

Сэм Макафи
источник
2

У вас есть организационное дерево на N уровней. Проверены несколько узлов, и вы хотите расширить до только тех узлов, которые были проверены.

Это то, что я на самом деле закодировал. С рекурсией это легко и приятно.

MagicKat
источник
2

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

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

Томми
источник
2

Расчеты по финансам / физике, например, сложные средние.

Апокалисп
источник
2
  • Разбор XML- файла.
  • Эффективный поиск в многомерных пространствах. E. g. квад-деревья в 2D, окт-деревья в 3D, kd-деревья и т. д.
  • Иерархическая кластеризация.
  • Если подумать, обход любой иерархической структуры естественным образом поддается рекурсии.
  • Метапрограммирование шаблонов на C ++, где нет циклов и рекурсия - единственный способ.
Дима
источник
«XML» не является существенным для идеи этого ответа (и упоминание конкретно XML может быть отвратительным / скучным людям, которых вы обучаете). Любой типичный язык (компьютерный или естественный) может служить примером проблемы рекурсивного синтаксического анализа.
imz - Иван Захарящев
Плакат требовал «реальных проблем, для которых рекурсивный подход является естественным решением». Разбор xml-файла, безусловно, является реальной проблемой, и он, естественно, поддается рекурсии. Тот факт, что вы испытываете странное отвращение к XML, не отменяет того факта, что он очень широко используется.
Дима
2

Разбор дерева элементов управления в Windows Forms или WebForms (.NET Windows Forms / ASP.NET ).

Петер Мортенсен
источник
2

Лучший пример, который я знаю, - это быстрая сортировка , с рекурсией она намного проще. Взгляни на:

shop.oreilly.com/product/9780596510046.do

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

(Щелкните первый подзаголовок под главой 3: «Самый красивый код, который я когда-либо писал»).

Фабио Чеконелло
источник
1
И MergeSort тоже проще с рекурсией.
Мэтью Шинкель,
1
Ссылка не работает. Вы можете добавить название книги?
Питер Мортенсен
@PeterMortensen, это книга Грега Уилсона и Энди Орама Beautiful Code. Я обновил ссылку, хотя кажется, что О'Рейли больше не позволяет заглядывать внутрь. Но вы можете взглянуть на 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
Апокалисп
источник