Как думать рекурсивным образом?

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

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

Пожалуйста, помогите, ответив на следующие вопросы:

1) Можно ли заменить любой итеративный метод рекурсией и наоборот?

Например, как печатать элементы в массиве размера n рекурсивно?

for i 0 to n
 Print a[i]

2) Как решить проблему рекурсивно? Какие шаги? Есть ли подсказки, чтобы определить, что проблемы могут быть рекурсивно решены?

Например: если мне предлагается распечатать все подстроки строки

INPUT: CAT
OUTPUT: CAT,CA,A,AT,T

Я могу придумать итеративный способ fast.Using две петли могут решить проблему.

Но рекурсивно, как его решить. Как определить, что проблемы могут быть решены рекурсивно.

Если ответ на мой первый вопрос - да, использование двух рекурсий вместо итерации может быть решением моей проблемы?

3) Может ли кто-нибудь предложить мне некоторые материалы/ресурсы, чтобы полностью понять концепцию рекурсии?

Ответ 1

  • Да, в основном. В целом рекурсия выполняется для программиста, а не для компьютера. Существуют итеративные методы, которые в некоторых случаях могут работать быстрее, чем рекурсивные, но итеративный метод может принимать 300 строк кода и рекурсивный 3. Есть также некоторые случаи, когда легко понять, как программировать что-то рекурсивно, но очень трудно писать итеративно и наоборот.

  • Как правило, рекурсивное решение должно быть связано с функцией. Если мы используем что-то вроде С++, мы можем использовать решение, связанное со строковыми ссылками и вещами, медленно настраивая строки, передаваемые в качестве параметров. Однако точка в конце "двух рекурсий" ошибочна. Принцип здесь заключается в том, что вместо двух итераций мы можем сделать один рекурсивный подход.

  • http://introcs.cs.princeton.edu/java/23recursion/ этот сайт (высоко в поиске по Google) учит много математической теории рекурсии и включает в себя FAQ, который может дать вам более удовлетворительный ответ на номер один.

Ответ 2

Существует способ мышления о рекурсии, который делает ее такой же простой, как итерация.

В итерации у нас есть цикл. Подумайте об этом как о 4 частях:

  • Решение продолжить или остановить, основываясь на некоторых "управляющих" данных, оценивается как логическое условие.

  • Тело, где выполняется работа. Иногда тело сочетается со следующей частью.

  • Способ изменения "управляющих" данных. Часто меняя счетчик.

  • Способ вызова конструкции (в данном случае, цикла) снова. В языках c-стиля это обеспечивается синтаксисом for, while или do.

В рекурсии у нас есть функция (иногда несколько). Они имеют те же 4 части:

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

  • Тело, где выполняется работа. Иногда тело сочетается со следующей частью.

  • Способ изменения "управляющих" данных. Часто меняя счетчик.

  • Способ вызова конструкции (в данном случае, функции) снова - это означает вызов функции (и не забудьте передать измененные "управляющие" данные.

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

Ответ 3

Возьмем простую задачу. Печать чисел от 1 до 10. Я буду использовать Python2.7 здесь.

for i in range(1,11):
    print i

Теперь попробуйте сделать то же самое, используя рекурсию.

>>> def print_me(n):
    if n > 0:
        print_me(n - 1)
        print n
    else:
        return

>>> print_me(10)
1
2
3
4
5
6
7
8
9
10

Итак, как мы об этом думаем?

  • Шаг1. Подумайте о моих границах. Мне нужно два. 1 и 10. Далее.
  • Шаг2. Оператор печати. Это наш мотив. Печать номеров. А также мы хотим от 1 до 10. Поэтому мне нужно сначала напечатать 1.
  • Шаг 3. Начнем с написания функции, которая выполняет печать число, переданное в качестве аргумента. Подумайте, просто, главное Задача.

    def print_me (n):      print n

  • Шаг 4: я хочу, чтобы функция возвращалась, если n < 1.

    def print_me (n):   если n > 0:       print n   еще:       вернуться

  • Шаг 5. Теперь я хочу передать числа от 1 до 10 этой функции, но нам не нужен цикл от 1 до 10, а затем передать его нашей функции. Мы хотите, чтобы это было сделано рекурсивно.

Что такое рекурсия? Другими словами, повторное применение рекурсивной процедуры или определения.

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

def print_me(n):
    if n > 0:
        print_me(n - 1)
        print n
    else:
        return

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

  • Рекурсивный алгоритм, ДОЛЖЕН иметь базовый случай.
  • Рекурсивный алгоритм, ДОЛЖЕН изменить свое состояние и двигаться к основанию дело.
  • Рекурсивный алгоритм ДОЛЖЕН называть себя рекурсивно.

Источник: interactivepython

Еще одна программа в Javascript для поиска факториала:

function factorial(n){
    if (n == 1)
        {
        return 1;
        }
    else {
        return n * factorial(n-1);
        }
}

Ответ 4

@Test
public void testStrings() {
   TreeSet<String> finalTree = getSubStringsOf("STACK");
    for(String subString : finalTree){
        System.out.println(subString);
    }
}

public TreeSet<String> getSubStringsOf(String stringIn) {
    TreeSet<String> stringOut = new TreeSet<String>();
    if (stringIn.length() == 1) {
        stringOut.add(stringIn);
        return stringOut;
    } else {
        for (int i = 1; i < stringIn.length() ; i++) {
            String stringBefore = stringIn.substring(0, i);
            String stringAfter = stringIn.substring(i);
            stringOut.add(stringBefore);
            stringOut.add(stringAfter);
            stringOut.addAll(getSubStringsOf(stringBefore));
            stringOut.addAll(getSubStringsOf(stringAfter));
        }
        return stringOut;
    }
}

Я не знаю, нужно ли вам объяснение. Вы разбиваете строку по два раза каждый раз, когда это возможно. Таким образом, Cat разбивается на CA, T и C, AT, вы добавляете их в свой список подстроки, затем просматриваете каждую подстроку этих подстрок. Если String является единственным символом, вы добавляете одиночный символ в свое дерево.

EDIT: это вывод для STACK:

A AC ACK C CK K S ST STA STAC T TA TAC TACK

EDIT еще раз: как вы можете видеть, каждый раз, когда вы запускаете метод subString, вы будете использовать его дважды внутри него, за исключением случаев, когда это один символ String. Таким образом, сложность O (n²). Для "STACK" программа длится 0.200 мс, для "STACKSTACKSTACK" (3 раза в стеке) - 2 секунды, для "STACKSTACKSTACKSTACKSTACK" теоретически 2 ^ 10 раз больше, таким образом, 2000 секунд.

Ответ 5

Вот мой простой тест в Python:

def p(l, index):
    if index == len(l):
        return
    else:
        print l[index]
        index = index + 1
        p(l, index)

и вызовите:

p("123456", 0)