Parfois, lorsque l'on programme de manière récurrente, on appelle la fonction avec les mêmes paramètres plusieurs fois, ce qui n'est pas nécessaire.
Le célèbre exemple des nombres de Fibonacci :
index: 1,2,3,4,5,6...
Fibonacci number: 1,1,2,3,5,8...
function F(n) {
if (n < 3)
return 1
else
return F(n-1) + F(n-2)
}
Lançons F(5) :
F(5) = F(4) + F(3)
= {F(3)+F(2)} + {F(2)+F(1)}
= {[F(2)+F(1)]+1} + {1+1}
= 1+1+1+1+1
Nous avons donc appelé : 1 fois F(4) 2 fois F(3) 3 fois F(2) 2 fois F(1)
Approche de la programmation dynamique : si vous appelez une fonction avec le même paramètre plus d'une fois, sauvegardez le résultat dans une variable pour y accéder directement la fois suivante. La méthode itérative :
if (n==1 || n==2)
return 1
else
f1=1, f2=1
for i=3 to n
f = f1 + f2
f1 = f2
f2 = f
Appelons à nouveau F(5) :
fibo1 = 1
fibo2 = 1
fibo3 = (fibo1 + fibo2) = 1 + 1 = 2
fibo4 = (fibo2 + fibo3) = 1 + 2 = 3
fibo5 = (fibo3 + fibo4) = 2 + 3 = 5
Comme vous pouvez le voir, chaque fois que vous avez besoin de l'appel multiple, il suffit d'accéder à la variable correspondante pour obtenir la valeur au lieu de la recalculer.
Par ailleurs, la programmation dynamique ne signifie pas la conversion d'un code récursif en un code itératif. Vous pouvez également sauvegarder les sous-résultats dans une variable si vous voulez un code récursif. Dans ce cas, la technique est appelée mémorisation. Pour notre exemple, cela ressemble à ceci :
// declare and initialize a dictionary
var dict = new Dictionary<int,int>();
for i=1 to n
dict[i] = -1
function F(n) {
if (n < 3)
return 1
else
{
if (dict[n] == -1)
dict[n] = F(n-1) + F(n-2)
return dict[n]
}
}
Le rapport avec le Diviser et Conquérir est donc que les algorithmes de D&D reposent sur la récursion. Et certaines versions d'entre eux ont ce "problème d'appels multiples de fonctions avec le même paramètre". Cherchez "matrix chain multiplication" et "longest common subsequence" pour de tels exemples où DP est nécessaire pour améliorer le T(n) de l'algo D&D.