Что такое рекурсия и когда я должен ее использовать?

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

Итак, вопрос:

  • Что такое рекурсия?
  • Когда я буду использовать рекурсию?
  • Почему люди не используют рекурсию?

Ответ 1

В этом потоке есть несколько хороших объяснений recursion, этот ответ объясняется тем, почему вы не должны использовать его в большинстве Языки. * В большинстве основных императивных языковых реализаций (т.е. каждая основная реализация C, С++, Basic, Python, Ruby, Java и С#) iteration значительно предпочтительнее рекурсии.

Чтобы узнать, почему, перейдите к шагам, которые используют вышеупомянутые языки для вызова функции:

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

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

Итак, на этих языках рекурсия медленнее, и это делает вас уязвимыми для сбоев. Тем не менее, есть некоторые аргументы в пользу его использования. В общем, код, написанный рекурсивно, короче и немного более изящный, как только вы знаете, как его читать.

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

* Я люблю рекурсию. Мой любимый статический язык не использует циклы вообще, рекурсия - это единственный способ сделать что-то многократно. Я просто не думаю, что рекурсия, как правило, хорошая идея на языках, которые не настроены на нее.

** Кстати, Марио, типичное имя для вашей функции ArrangeString является "join", и я был бы удивлен, если ваш язык выбора еще не имеет его реализации.

Ответ 2

Простой английский пример рекурсии.

A child couldn't sleep, so her mother told her a story about a little frog,
    who couldn't sleep, so the frog mother told her a story about a little bear,
         who couldn't sleep, so the bear mother told her 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

В самом основном смысле в компьютерной науке рекурсия - это функция, которая называет себя. Скажем, у вас есть связанная структура списка:

struct Node {
    Node* next;
};

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

int length(const Node* list) {
    if (!list->next) {
        return 1;
    } else {
        return 1 + length(list->next);
    }
}

(Конечно, это можно сделать и для цикла for, но полезно в качестве иллюстрации концепции)

Ответ 4

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

Самый простой пример - хвостовая рекурсия, где самая последняя строка функции - это вызов самому себе:

int FloorByTen(int num)
{
    if (num % 10 == 0)
        return num;
    else
        return FloorByTen(num-1);
}

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

Таким образом, вся причина делать рекурсию, а не итерацию, должна заключаться в том, чтобы использовать "noreferrer" > стек вызовов, чтобы сделать некоторые умные вещи. Например, если вы вызываете функцию несколько раз с разными параметрами внутри одного и того же цикла, то это способ выполнить ветвление. Классическим примером является треугольник Серпинского.

enter image description here

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

private void BuildVertices(double x, double y, double len)
{
    if (len > 0.002)
    {
        mesh.Positions.Add(new Point3D(x, y + len, -len));
        mesh.Positions.Add(new Point3D(x - len, y - len, -len));
        mesh.Positions.Add(new Point3D(x + len, y - len, -len));
        len *= 0.5;
        BuildVertices(x, y + len, len);
        BuildVertices(x - len, y - len, len);
        BuildVertices(x + len, y - len, len);
    }
}

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

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

Заключение

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

Ответ 5

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

Канонический пример - это подпрограмма для создания Факториала n. Факториал n вычисляется путем умножения всех чисел между 1 и n. Итеративное решение в С# выглядит так:

public int Fact(int n)
{
  int fact = 1;

  for( int i = 2; i <= n; i++)
  {
    fact = fact * i;
  }

  return fact;
}

Нет ничего удивительного в итеративном решении, и это должно иметь смысл для всех, кто знаком с С#.

Рекурсивное решение найдено, признавая, что n-й Факториал n * Fact (n-1). Или, говоря иначе, если вы знаете, какой конкретный факториальный номер вы можете рассчитать следующий. Вот рекурсивное решение в С#:

public int FactRec(int n)
{
  if( n < 2 )
  {
    return 1;
  }

  return n * FactRec( n - 1 );
}

Первая часть этой функции известна как Base Case (или иногда Guard), и это то, что мешает алгоритму работать навсегда. Он просто возвращает значение 1 при вызове функции со значением 1 или меньше. Вторая часть интереснее и известна как Рекурсивный шаг. Здесь мы называем тот же метод со слегка измененным параметром (мы уменьшаем его на 1), а затем умножаем результат на нашу копию n.

Когда вы впервые сталкиваетесь, это может быть путаным, поэтому поучительно исследовать, как это работает при запуске. Представьте, что мы называем FactRec (5). Мы вводим процедуру, не подбираем базовый регистр, и поэтому мы заканчиваем так:

// In FactRec(5)
return 5 * FactRec( 5 - 1 );

// which is
return 5 * FactRec(4);

Если мы снова вводим метод с параметром 4, мы снова не останавливаемся в предложении guard, и поэтому мы заканчиваем на:

// In FactRec(4)
return 4 * FactRec(3);

Если мы подставим это возвращаемое значение в возвращаемое выше значение, получим

// In FactRec(5)
return 5 * (4 * FactRec(3));

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

return 5 * (4 * FactRec(3));
return 5 * (4 * (3 * FactRec(2)));
return 5 * (4 * (3 * (2 * FactRec(1))));
return 5 * (4 * (3 * (2 * (1))));

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

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

Ответ 6

Рекурсия решает проблему с функцией, которая вызывает себя. Хорошим примером этого является факторная функция. Факториал - математическая проблема, где факториал 5, например, равен 5 * 4 * 3 * 2 * 1. Эта функция решает это в С# для целых положительных чисел (не тестируется - может быть ошибка).

public int Factorial(int n)
{
    if (n <= 1)
        return 1;

    return n * Factorial(n - 1);
}

Ответ 7

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

Канонический пример - это факториал, который выглядит так:

int fact(int a) 
{
  if(a==1)
    return 1;

  return a*fact(a-1);
}

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

Ответ 8

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

Например, чтобы вычислить факториал для числа X, его можно представить как X times the factorial of X-1. Таким образом, метод "рекурсирует", чтобы найти факториал X-1, а затем умножает все, что он получил на X, чтобы дать окончательный ответ. Конечно, чтобы найти факториал X-1, он сначала вычислит факториал X-2 и так далее. Основной случай был бы когда X равен 0 или 1, и в этом случае он знает, что он возвращает 1 с 0! = 1! = 1.

Ответ 9

Рассмотрим старую, хорошо известную проблему:

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

Определение gcd удивительно просто:

gcd definition

где mod - modulo operator (то есть остаток после целочисленного деления).

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

Если вы хотите знать, почему это работает, см. статью Википедии о Евклидовой алгоритм.

Позвольте вычислить gcd (10, 8) в качестве примера. Каждый шаг равен тому, который был перед ним:

  • gcd (10, 8)
  • gcd (10, 10 mod 8)
  • gcd (8, 2)
  • gcd (8, 8 mod 2)
  • gcd (2, 0)
  • 2

На первом шаге 8 не равен нулю, поэтому применяется вторая часть определения. 10 mod 8 = 2, потому что 8 переходит в 10 один раз с остатком 2. На шаге 3 вторая часть применяется снова, но на этот раз 8 mod 2 = 0, потому что 2 делит 8 без остатка. На шаге 5 второй аргумент равен 0, поэтому ответ равен 2.

Вы заметили, что gcd появляется как на левой, так и на правой сторонах знака равенства? Математик сказал бы, что это определение рекурсивно, потому что выражение, которое вы определяете повторяется внутри его определения.

Рекурсивные определения, как правило, элегантны. Например, рекурсивное определение суммы списка

sum l =
    if empty(l)
        return 0
    else
        return head(l) + sum(tail(l))

где head - это первый элемент в списке, а tail - остальная часть списка. Обратите внимание, что sum повторяется внутри его определения в конце.

Возможно, вы предпочтете максимальное значение в списке:

max l =
    if empty(l)
        error
    elsif length(l) = 1
        return head(l)
    else
        tailmax = max(tail(l))
        if head(l) > tailmax
            return head(l)
        else
            return tailmax

Вы можете рекурсивно определить умножение неотрицательных целых чисел, чтобы превратить его в ряд дополнений:

a * b =
    if b = 0
        return 0
    else
        return a + (a * (b - 1))

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

Объединить сортировку имеет прекрасное рекурсивное определение:

sort(l) =
    if empty(l) or length(l) = 1
        return l
    else
        (left,right) = split l
        return merge(sort(left), sort(right))

Рекурсивные определения все вокруг, если вы знаете, что искать. Обратите внимание, что все эти определения имеют очень простые базовые случаи, например, gcd (m, 0) = m. Рекурсивные случаи устраняют проблему, чтобы перейти к легким ответам.

При таком понимании вы можете теперь оценить другие алгоритмы в Статья в Википедии о рекурсии!

Ответ 10

Рекурсивная функция - это та, которая вызывает себя. Самая распространенная причина, по которой я нашел ее использовать, - это перемещение древовидной структуры. Например, если у меня есть TreeView с флажками (подумайте над установкой новой программы, "выберите функции для установки" ), мне может понадобиться кнопка "Проверить все", которая будет примерно такой (псевдокод):

function cmdCheckAllClick {
    checkRecursively(TreeView1.RootNode);
}

function checkRecursively(Node n) {
    n.Checked = True;
    foreach ( n.Children as child ) {
        checkRecursively(child);
    }
}

Итак, вы можете видеть, что checkRecursively сначала проверяет node, который он передал, затем называет себя для каждого из этих node детей.

Вам нужно быть немного осторожным с рекурсией. Если вы попадаете в бесконечный рекурсивный цикл, вы получите исключение:)

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

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

Ответ 11

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

Люди избегают рекурсии по ряду причин:

  • Большинство людей (включая меня) сокращают свои программные зубы на процедурном или объектно-ориентированном программировании, а не на функциональном программировании. Для таких людей итеративный подход (обычно с использованием петель) чувствует себя более естественным.

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

  • Нам часто говорят, что рекурсия медленная. Вызов и возврат из процедуры повторяется много раз, когда происходит много нажатий и выскакиваний стека, что медленнее, чем цикл. Я думаю, что некоторые языки справляются с этим лучше, чем другие, и эти языки, скорее всего, не те, где доминирующая парадигма является процедурной или объектно-ориентированной.

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

Ответ 12

Вот простой пример: сколько элементов в наборе. (есть лучшие способы подсчета вещей, но это хороший простой рекурсивный пример.)

Во-первых, нам нужны два правила:

  • Если набор пуст, количество элементов в наборе равно нулю (duh!).
  • Если набор не пуст, счетчик равен числу плюс в наборе после удаления одного элемента.

Предположим, что у вас есть такой набор: [x x x]. пусть подсчитывает количество элементов.

  • набор [x x x], который не является пустым, поэтому мы применяем правило 2. количество элементов равно единице плюс количество элементов в [x x] (т.е. мы удалили элемент).
  • множество [x x], поэтому мы снова применяем правило 2: одно + количество элементов в [x].
  • множество [x], которое по-прежнему соответствует правилу 2: одно + количество элементов в [].
  • Теперь набор [], который соответствует правилу 1: счетчик равен нулю!
  • Теперь, когда мы знаем ответ на шаге 4 (0), мы можем решить шаг 3 (1 + 0)
  • Аналогично, теперь, когда мы знаем ответ на шаге 3 (1), мы можем решить шаг 2 (1 + 1)
  • И, наконец, теперь, когда мы знаем ответ на шаге 2 (2), мы можем решить шаг 1 (1 + 2) и получить количество элементов в [x x x], которое равно 3. Ура!

Мы можем представить это как:

count of [x x x] = 1 + count of [x x]
                 = 1 + (1 + count of [x])
                 = 1 + (1 + (1 + count of []))
                 = 1 + (1 + (1 + 0)))
                 = 1 + (1 + (1))
                 = 1 + (2)
                 = 3

При применении рекурсивного решения у вас обычно есть как минимум 2 правила:

  • основа, простой случай, который указывает, что происходит, когда вы "исчерпали" все свои данные. Обычно это вариация "если у вас нет данных для обработки, ваш ответ X"
  • рекурсивное правило, в котором говорится, что происходит, если у вас все еще есть данные. Обычно это правило, которое гласит: "Сделайте что-то, чтобы уменьшить ваши данные и повторно примените свои правила к меньшему набору данных".

Если мы переведем приведенное выше в псевдокод, получим:

numberOfItems(set)
    if set is empty
        return 0
    else
        remove 1 item from set
        return 1 + numberOfItems(set)

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

Ответ 13

Рекурсия - это выражение, прямо или косвенно ссылающееся на себя.

Рассмотрим рекурсивные акронимы как простой пример:

  • GNU означает GNU Not Unix
  • PHP означает PHP: препроцессор гипертекста
  • YAML означает YAML - это не язык разметки
  • WINE означает Wine не является эмулятором
  • VISA означает Международная ассоциация обслуживания Visa

Дополнительные примеры в Википедии

Ответ 14

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

Мне также нравится дискуссия Стив Макконнелл о рекурсии в Code Complete, где он критикует примеры, используемые в книгах Computer Science по рекурсии.

Не используйте рекурсию для факториалов или чисел Фибоначчи

Одна проблема с учебники по компьютерным наукам - это то, что они представляют собой глупые примеры рекурсии. Типичными примерами являются вычисление факториала или вычисление Последовательность Фибоначчи. Рекурсия - это мощный инструмент, и он действительно тупой используйте его в любом из этих случаев. Если программист, который работал у меня рекурсия для вычисления факториала, я бы нанять кого-то другого.

Я думал, что это очень интересный момент для поднятия и может быть причиной, почему рекурсия часто неправильно понимается.

EDIT: Это было не раскопки в ответе Дава - я не видел ответа, когда я разместил это

Ответ 15

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

Например, возьмите факториал:

factorial(6) = 6*5*4*3*2*1

Но легко видеть, что факториал (6) также равен:

6 * factorial(5) = 6*(5*4*3*2*1).

Итак, обычно:

factorial(n) = n*factorial(n-1)

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

В этом примере мы просто делаем специальный случай, определяя factorial (1) = 1.

Теперь мы видим это снизу вверх:

factorial(6) = 6*factorial(5)
                   = 6*5*factorial(4)
                   = 6*5*4*factorial(3) = 6*5*4*3*factorial(2) = 6*5*4*3*2*factorial(1) = 6*5*4*3*2*1

Поскольку мы определили факториал (1) = 1, мы достигнем "дна".

Вообще говоря, рекурсивные процедуры имеют две части:

1) Рекурсивная часть, которая определяет некоторую процедуру в терминах новых входных данных в сочетании с тем, что вы "уже сделали" с помощью той же процедуры. (т.е. factorial(n) = n*factorial(n-1))

2) Базовая часть, которая гарантирует, что процесс не повторяется навсегда, давая ему место для начала (т.е. factorial(1) = 1)

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

Надеюсь, что это поможет...

Ответ 16

1). Метод рекурсивный, если он может называть себя; либо напрямую:

void f() {
   ... f() ... 
}

или косвенно:

void f() {
    ... g() ...
}

void g() {
   ... f() ...
}

2.) Когда использовать рекурсию

Q: Does using recursion usually make your code faster? 
A: No.
Q: Does using recursion usually use less memory? 
A: No.
Q: Then why use recursion? 
A: It sometimes makes your code much simpler!

3.) Люди используют рекурсию только тогда, когда очень сложно писать итеративный код. Например, методы обхода дерева, такие как preorder, postorder, могут быть сделаны итеративными и рекурсивными. Но обычно мы используем рекурсивную из-за своей простоты.

Ответ 17

Чтобы решить проблему: ничего не делайте, все готово.
Чтобы ответить на открытую проблему: сделайте следующий шаг, а затем отпустите остальные.

Ответ 18

Хорошо, это довольно приличное определение, которое у вас есть. И у википедии есть хорошее определение. Поэтому я добавлю еще одно (возможно, худшее) определение.

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

Ответ 19

Пример: рекурсивное определение лестницы: Лестница состоит из: - один шаг и лестница (рекурсия) - или только один шаг (завершение)

Ответ 20

На простом английском языке: Предположим, вы можете сделать 3 вещи:

  • Возьмите одно яблоко
  • Записать отметки о трате
  • Графические метки подсчета

У вас есть много яблок перед вами на столе, и вы хотите знать, сколько там яблок.

start
  Is the table empty?
  yes: Count the tally marks and cheer like it your birthday!
  no:  Take 1 apple and put it aside
       Write down a tally mark
       goto start

Процесс повторения одного и того же до тех пор, пока вы не закончите, называется рекурсией.

Я надеюсь, что это "простой английский" ответ, который вы ищете!

Ответ 21

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

Рассмотрим два зеркала, обращенные друг к другу. Мы видели аккуратный эффект бесконечности, который они создают. Каждое отражение является экземпляром зеркала, которое содержится в другом экземпляре зеркала и т.д. Зеркало, содержащее отражение самого себя, является рекурсией.

A дерево двоичного поиска - хороший пример программирования рекурсии. Структура рекурсивна с каждым Node, содержащим 2 экземпляра Node. Функции для работы с деревом двоичного поиска также являются рекурсивными.

Ответ 22

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

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

Таким образом, я часто избегаю рекурсии и вместо этого использую операцию стека, потому что сама рекурсия по существу является операцией стека.

Ответ 23

Марио, я не понимаю, почему вы использовали рекурсию для этого примера. Почему бы просто не пропустить каждую запись? Что-то вроде этого:

String ArrangeString(TStringList* items, String separator)
{
    String result = items->Strings[0];

    for (int position=1; position < items->count; position++) {
        result += separator + items->Strings[position];
    }

    return result;
}

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

Ответ 24

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

Ответ 25

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

Ответ 26

"Если у меня есть молоток, сделайте все как гвоздь".

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

Пример

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

  • Разделить: Распаковать все листы, так что у вас есть только один лист в каждом "стеке".
  • Conquer:
    1. Пойдите, положив каждый лист поверх другого листа. Теперь у вас есть стеки из 2.
    2. Пойдите, помещая каждый 2-стоп поверх другого 2-стека. Теперь у вас есть стопки 4.
    3. Пойдите, положив каждый 4-стоп поверх другого 4-стека. Теперь у вас есть стопки 8.
    4. ... и далее...
    5. Теперь у вас есть один огромный стек из 1024 листов!

Обратите внимание, что это довольно интуитивно, кроме подсчета всего (что не является строго необходимым). На самом деле, вы могли бы не дойти до стоп-листов в 1 лист, но вы могли бы и все равно работать. Важной частью является молоток. С руками вы всегда можете положить один стек поверх другого, чтобы сделать более крупный стек, и не имеет значения (в пределах разумного), насколько велика любая из них.

Ответ 27

его способ делать вещи бесконечно бесконечно, чтобы каждый вариант использовался.

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

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

в С# у вас будет что-то вроде этого:

private void findlinks(string URL, int reccursiveCycleNumb)    {
   if (reccursiveCycleNumb == 0)
        {
            return;
        }

        //recursive action here
        foreach (LinkItem i in LinkFinder.Find(URL))
        {
            //see what links are being caught...
            lblResults.Text += i.Href + "<BR>";

            findlinks(i.Href, reccursiveCycleNumb - 1);
        }

        reccursiveCycleNumb -= reccursiveCycleNumb;
}

Ответ 28

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

Ответ 29

В простом английском языке рекурсия означает повторение сокета снова и снова.

В программировании одним примером является вызов функции внутри себя.

Посмотрите следующий пример вычисления факториала числа:

public int fact(int n)
{
    if (n==0) return 1;
    else return n*fact(n-1)
}

Ответ 30

Эй, извините, если мое мнение соглашается с кем-то, я просто пытаюсь объяснить рекурсию на простом английском языке.

Предположим, у вас есть три менеджера - Джек, Джон и Морган. Джек управляет 2 программистами, John - 3 и Morgan - 5. вы собираетесь дать каждому менеджеру 300 $и хотите знать, что это будет стоить. Ответ очевиден - но что, если 2 сотрудника Morgan также являются менеджерами?

Здесь идет рекурсия. вы начинаете с вершины иерархии. сумма летних расходов равна 0 $. вы начинаете с Джека, Затем проверьте, есть ли у него какие-либо менеджеры в качестве сотрудников. если вы найдете какой-либо из них, проверьте, есть ли у них какие-либо менеджеры в качестве сотрудников и так далее. Добавляйте 300 $к летней стоимости каждый раз, когда вы находите менеджера. когда вы закончите с Джеком, идите к Джону, его сотрудникам, а затем к Моргану.

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

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