web-dev-qa-db-fra.com

Quelle est la différence entre la récursivité, la mémorisation et la programmation dynamique?

Dupliquer possible:
Programmation dynamique et mémorisation: approches descendante ou ascendante

J'ai lu beaucoup d'articles à ce sujet, mais je n'arrive pas à comprendre. Parfois, la récursivité et la programmation dynamique se ressemblent et les autres se ressemblent. Quelqu'un peut-il m'expliquer quelle est la différence?

P.S. Il serait également utile que vous me signaliez du code utilisant les trois approches du même problème. (Par exemple, le problème de la série de Fibonacci, je pense que chaque article que j'ai lu utilisait la récursivité, mais l'appelait programmation dynamique)

37
Sakibul Alam

Envisagez de calculer la séquence fibonacci:

Pure récursion:

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

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

entraîne un nombre d'appels exponentiel.

Récursion avec mémoization/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;
}

Nous avons maintenant un nombre d'appels linéaire la première fois et constant par la suite.

La méthode ci-dessus est appelée "paresseuse". Nous calculons les termes antérieurs lors de la première utilisation.

Les éléments suivants seraient également considérés comme DP, mais sans récursivité:

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]; }

Ce moyen peut être décrit comme "impatient", "précaché" ou "itératif". C'est globalement plus rapide mais nous devons déterminer manuellement l'ordre dans lequel les sous-problèmes doivent être calculés. C'est facile pour fibonacci, mais pour les problèmes de DP plus complexes, cela devient plus difficile, et nous retombons donc dans la méthode récursive paresseuse si elle est rapide assez.

Aussi ce qui suit n'est ni récursivité ni 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;
}

Il utilise un espace constant et un temps linéaire.

Je mentionnerai également, par souci d'exhaustivité, qu'il existe une forme fermée pour fibonacci qui utilise la récursion ni la DP ni qui nous permet de calculer en temps constant le terme fibonacci en utilisant une formule mathématique basée sur le nombre d'or:

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

41
Andrew Tomazos

Eh bien, récursivité + mémoisation est précisément une "saveur" spécifique de programmation dynamique : programmation dynamique conforme à approche top-down .

Plus précisément, il n'y a aucune obligation d'utiliser récursion spécifiquement. Toute solution Divide & Conquer associée à la mémorisation est une programmation dynamique descendante. (La récursivité est la saveur LIFO de division et conquête, alors que vous pouvez également utiliser FIFO division/conquête ou tout autre type de division et conquête).

Donc, il est plus correct de dire que

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

De même, d’un point de vue très formel, si vous implémentez une solution diviser & conquérir pour un problème qui ne génère pas de solutions partielles répétitives exemple dégénéré de "programmation dynamique".

Cependant, la programmation dynamique est un concept plus général. La programmation dynamique peut utiliser une approche ascendante, différente de Divide & Conquer + Memoization.

21
AnT

Je suis sûr que vous pouvez trouver une définition détaillée sur Internet. Voici ma tentative de simplifier les choses.

La récursion s'appelle à nouveau. 

La programmation dynamique est un moyen de résoudre des problèmes présentant une structure spécifique (sous-structure optimale) dans laquelle un problème peut être décomposé en sous-problèmes similaires au problème initial. Clairement, on peut invoquer la récursivité pour résoudre un DP. Mais ce n'est pas nécessaire. On peut résoudre un DP sans récursivité.

La mémorisation est un moyen d'optimiser les algorithmes DP qui reposent sur la récursivité. Le but n'est pas de résoudre à nouveau le problème secondaire qui a déjà été résolu. Vous pouvez le voir comme un cache de solutions aux problèmes secondaires.

7
Ankush

Ce sont des concepts différents. Ils se chevauchent assez souvent, mais ils sont différents.

La récursivité se produit chaque fois qu'une fonction s'appelle elle-même, directement ou indirectement. C'est tout.

Exemple:

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

Programmation dynamique est lorsque vous utilisez des solutions à des sous-problèmes plus petits afin de résoudre un problème plus important. C'est plus facile à implémenter de manière récursive car vous pensez généralement à de telles solutions en termes de fonction récursive. Une implémentation itérative est généralement préférable, car elle prend moins de temps et de mémoire. 

La mémorisation est utilisée pour empêcher la mise en œuvre récursive de PD de prendre beaucoup plus de temps que nécessaire. La plupart du temps, un algorithme DP utilisera le même sous-problème pour résoudre plusieurs gros problèmes. Dans une implémentation récursive, cela signifie que nous recalculerons la même chose plusieurs fois. La mémorisation implique de sauvegarder les résultats de ces sous-problèmes dans un tableau. Lors de la saisie d'un appel récursif, nous vérifions si son résultat existe dans la table: si oui, nous le renvoyons, sinon, nous le calculons, l'enregistrons dans la table, puis nous le renvoyons.

5
IVlad

La récursion n'a absolument rien à voir avec la mémoisation et la programmation dynamique; c'est un concept complètement séparé.

Sinon, la question est double: Programmation dynamique et mémorisation: approches ascendante et descendante

0
ninjagecko