Какая разница между рекурсией, мемоизации и динамического программирования? [дубликат]

Возможные Дубликаты:
динамическое программирование и запоминание: сверху вниз и снизу вверх подходы

Я прошел через много статей об этом, но, похоже, не могу понять этого. Иногда рекурсия и динамическое программирование выглядят одинаково, а в других случаях memoization & dynamic programming выглядят одинаково. Может кто-нибудь объяснить мне, в чем разница?

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

5 ответов


рассмотрим вычисление последовательности Фибоначчи:

чистой воды рекурсию:

int fib(int x)
{
    if (x < 2)
        return 1;

    return fib(x-1) + fib(x-2);
}

приводит к экспоненциальному числу вызовов.

рекурсия с memoization/DP:

void fib(int x)
{
    static vector<int> cache(N, -1);

    int& result = cache[x];

    if (result == -1)
    {
        if (x < 2)
            result = 1;
        else
            result = fib(x-1) + fib(x-2);
    }

    return result;
}

теперь у нас есть линейное количество звонков в первый раз, и неизменными.

вышеупомянутый метод называется "ленивый". Мы вычисляем более ранние термины при первом запросе.

следующее также было бы рассмотрено DP, но без рекурсия:

int fibresult[N];

void setup_fib()
{
    fibresult[0] = 1;
    fibresult[1] = 1;
    for (int i = 2; i < N; i++)
       fibresult[i] = fibresult[i-1] + fibresult[i-2];
}

int fib(int x) { return fibresult[x]; }

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

также следующее не является ни рекурсией, ни DP:

int fib(int x)
{
    int a = 1;
    int b = 1;
    for (int i = 2; i < x; i++)
    {
        a = a + b;
        swap(a,b);
    }
    return b;
}

он использует постоянн космос и линейный время.

также я упомяну для полноты существует замкнутая форма для Фибоначчи, которая использует пустотную рекурсию или DP, что позволяет нам вычислять в постоянное время член Фибоначчи, используя математическую формулу, основанную на Золотом сечении:

http://www.dreamincode.net/forums/topic/115550-fibonacci-closed-form/


Ну рекурсия+мемоизация это именно специфический "аромат"динамическое программирование: динамическое программирование в соответствии с сверху вниз подход.

точнее, нет требования использовать рекурсия в частности. Любое решение divide & conquer в сочетании с memoization-это динамическое программирование сверху вниз. (Рекурсия - это аромат LIFO divide & conquer, в то время как вы также можете использовать FIFO divide & conquer или любой другой вид divide & conquer).

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

divide & conquer + memoization == top-down dynamic programming

кроме того, с очень формальной точки зрения, если вы реализуете решение divide & conquer для проблемы, которая не генерирует повторяющиеся частичные решения (что означает, что нет никакой пользы в memoization), то вы можете утверждать, что это решение divide & conquer является вырожденным примером "динамического программирования".

однако, динамическое программирование - это более общие концепция. Динамическое программирование может использовать подход снизу вверх, который отличается от divide & conquer+memoization.


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

рекурсия снова вызывает себя.

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

Memoization-это способ оптимизации алгоритмов DP, которые полагаются на рекурсию. Дело не в том, чтобы снова решить подзадачу, которая уже решена. Вы можете посмотреть его как кэш решений sub проблем.


это разные понятия. Они пересекаются довольно часто, но они разные.

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

пример:

a -> call a
a -> call b, b -> call c, c -> call a

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

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


рекурсия не имеет абсолютно ничего общего с запоминанием и динамическим программированием; это совершенно отдельная концепция.

в противном случае, это дубликат вопрос: динамическое программирование и memoization: снизу вверх против сверху вниз подходов