Примеры рекурсивных функций

Можно ли предложить примеры программирования, иллюстрирующие рекурсивные функции? Обычные старые лошади, такие как серия Фибоначчи и Башни Ханоя, но ничего, кроме них, было бы весело.

Ответ 1

Эта иллюстрация на английском языке, а не на самом языке программирования, но полезна для объяснения процесса нетехническим способом:

A child couldn't sleep, so her mother told a story about a little frog,
  who couldn't sleep, so the frog mother told a story about a little bear,
     who couldn't sleep, so bear mother told a story about a little weasel
       ...who fell asleep.
     ...and the little bear fell asleep;
  ...and the little frog fell asleep;
...and the child fell asleep.

Ответ 3

Эмпирическое правило для рекурсии: "Используйте рекурсию, если и только если на каждой итерации ваша задача разбивается на две или более схожие задачи".

Итак, Фибоначчи не является хорошим примером рекурсивного приложения, а Ханой - хорошим.

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

Например: трассировка графика - требование, что посетивший node никогда не будет посещаться снова, эффективно делает граф деревом (дерево - это связанный граф без простых циклов)

делить и побеждать алгоритмы (быстрая сортировка, сортировка слияния) - части после "деления" составляют дочерние узлы, "побеждать" формирует ребра из родительского node в дочерние узлы.

Ответ 4

Как насчет тестирования строки для палиндрома?

bool isPalindrome(char* s, int len)
{
  if(len < 2)
    return TRUE;
  else
    return s[0] == s[len-1] && isPalindrome(&s[1], len-2);
}

Конечно, вы можете сделать это с помощью цикла более эффективно.

Ответ 6

Другая пара "обычных подозреваемых" Quicksort и MergeSort

Ответ 7

Из мира математики существует функция Ackermann:

Ackermann(m, n)
{
  if(m==0)
    return n+1;
  else if(m>0 && n==0)
    return Ackermann(m-1, 1);
  else if(m>0 && n>0)
    return Ackermann(m-1, Ackermann(m, n-1));
  else
    throw exception; //not defined for negative m or n
}

Он всегда заканчивается, но он дает очень большие результаты даже для очень маленьких входов. Например, Акерманн (4, 2) возвращает 2 65536 - 3.

Ответ 8

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

class List {
    public List(params object[] items) {
        foreach (object o in items)
            this.Add(o);
    }

    // Most of the implementation omitted …
    public override string ToString() {
        var ret = new StringBuilder();
        ret.Append("( ");
        foreach (object o in this) {
            ret.Append(o);
            ret.Append(" ");
        }
        ret.Append(")");
        return ret.ToString();
    }
}

var lst = new List(1, 2, new List(3, 4), new List(new List(5), 6), 7);
Console.WriteLine(lst);
// yields:
// ( 1 2 ( 3 4 ) ( ( 5 ) 6 ) 7 )

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

Ответ 9

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

Здесь сказано рекурсивный способ найти элемент управления во вложенном дереве (например, ASP.NET или Winforms):

public Control FindControl(Control startControl, string id)
{
      if (startControl.Id == id)
           return startControl

      if (startControl.Children.Count > 0)
      {
           foreach (Control c in startControl.Children)
           {
                return FindControl(c, id);
           }
      }
      return null;
}

Ответ 10

Здесь приведен прагматичный пример из мира файловых систем. Эта утилита рекурсивно подсчитывает файлы в указанной директории. (Я не помню, почему, но у меня действительно была потребность в чем-то подобном давно...)

public static int countFiles(File f) {
    if (f.isFile()){
        return 1;
    }

    // Count children & recurse into subdirs:
    int count = 0;
    File[] files = f.listFiles();
    for (File fileOrDir : files) {
        count += countFiles(fileOrDir);
    }
    return count;
}

(Обратите внимание, что в Java экземпляр File может представлять собой обычный файл или каталог. Эта утилита исключает каталоги из счета. )

Обычным примером реального мира будет, например, FileUtils.deleteDirectory() из библиотеки Commons IO; см. API doc и источник.

Ответ 11

Реальный пример - это проблема с калькуляцией стоимости материалов.

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

Учитывая стандартную стоимость рабочей силы в минуту, сколько стоит производство каждого из наших продуктов?

Кстати, некоторые части (например, шнур) покупаются, поэтому мы знаем их стоимость напрямую.

Но мы фактически сами производим некоторые части. Мы делаем двигатель из корпуса, статора, ротора, вала и подшипников, и это занимает 15 минут.

И мы делаем статор и ротор из штамповок и проводов,...

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

Ответ 12

Самый увлекательный пример, который я знаю, - это Knuth Тест для мужчин или мальчиков. Как и рекурсия, он использует функции Алгола определения вложенных функций (замыканий), ссылок на функции и дуализма констант/функций (вызов по имени).

Ответ 13

Как уже говорили другие, многие примеры канонической рекурсии являются академическими.

Некоторые практические применения, с которыми я столкнулся в прошлом:

1 - Навигация по древовидной структуре, такой как файловая система или реестр

2 - Управление контейнерами, которые могут содержать другие элементы управления контейнером (например, панели или групповые окна)

Ответ 14

Мой личный фаворит Двоичный поиск

Изменить: Также, обход дерева. Например, прокрутка файловой структуры папки.

Ответ 15

Реализация графов от Guido van Rossum имеет некоторые рекурсивные функции в Python для поиска путей между двумя узлами в графах.

Ответ 16

Мой любимый сорт, Merge Sort

(Любимый, так как я помню алгоритм и, это не так уж плохо по производительности)

Ответ 17

  • Факториал
  • Перемещение дерева по глубине (в файловой системе, игровом пространстве или в любом другом случае)

Ответ 18

Как изменить направление строки?

void rev(string s) {
  if (!s.empty()) {
    rev(s[1..s.length]);
  }
  print(s[0]);
}

Понимание этого помогает понять рекурсию.

Ответ 19

Вот пример, который я опубликовал на этом сайте некоторое время назад для рекурсивного генерации дерева меню: Рекурсивный пример

Ответ 21

Когда-то давно, и не так давно, ученики начальной школы учились рекурсии с использованием логотипа и черепаховой графики. http://en.wikipedia.org/wiki/Turtle_graphics

Рекурсия также полезна для решения головоломок путем исчерпывающего судебного разбирательства. Существует своего рода головоломка, называемая "заполнить" (Google it), в которой вы получаете сетку, такую ​​как кроссворд, и слова, но никаких подсказок, никаких нумерованных квадратов. Однажды я написал программу, использующую рекурсию для издателя головоломок, чтобы решить головоломки, чтобы убедиться, что известное решение было уникальным.

Ответ 22

Рекурсивные функции отлично подходят для работы с рекурсивно определенными типами данных:

  • Естественное число равно нулю или преемнику другого натурального числа
  • Список - это пустой список или другой список с элементом впереди
  • Дерево - это node с некоторыми данными и ноль или более других поддеревьев

Etc.

Ответ 23

Перевести индекс столбца таблицы на имя столбца.

Это сложнее, чем кажется, потому что столбцы таблицы не обрабатывают цифру "0" правильно. Например, если вы берете A-Z в качестве цифр при увеличении с Z до AA, это будет похоже на переход от 9 до 11 или от 9 до 00 вместо 10 (в зависимости от того, является ли A 1 или 0). Даже пример поддержки Microsoft неверно для чего-то большего, чем AAA!

Рекурсивное решение работает, потому что вы можете рекурсивно отреагировать на эти новые символы. Эта реализация находится в VB.Net и обрабатывает первый столбец ( "A" ) как индекс 1.

Function ColumnName(ByVal index As Integer) As String
    Static chars() As Char = {"A"c, "B"c, "C"c, "D"c, "E"c, "F"c, "G"c, "H"c, "I"c, "J"c, "K"c, "L"c, "M"c, "N"c, "O"c, "P"c, "Q"c, "R"c, "S"c, "T"c, "U"c, "V"c, "W"c, "X"c, "Y"c, "Z"c}

    index -= 1 'adjust index so it matches 0-indexed array rather than 1-indexed column'

    Dim quotient As Integer = index \ 26 'normal / operator rounds. \ does integer division'
    If quotient > 0 Then
        Return ColumnName(quotient) & chars(index Mod 26)
    Else
        Return chars(index Mod 26)
    End If
End Function