Programmation dynamique
Mémoïsation
Lorsque nous implémentons un algorithme récursif, nous pouvons constater dans un grand nombre de cas qu'une même opération est souvent effectuée avec les mêmes valeurs en paramètre. A chaque fois ce calcul a un coût en temps processeur et en mémoire. Ce n'est pas conséquent lorsque nous traitons des données peu importantes, mais ce coût se révèle souvent être de type exponentiel, et nous nous retrouvons avec des programmes dont les performances ne sont plus acceptables (ou des programmes qui ne peuvent carrément plus s'exécuter) dès que la quantité de données à traiter devient plus importante.
Nous pouvons éviter de calculer inutilement une opération dont le calcul a déjà été effectué, en mémorisant le résultat lors du premier appel. A chaque appel, nous testons si le résultat est présent, mais ce test est généralement nettement mois coûteux que l'opération à calculer. Nous devrons donc modifier l'appel récursif pour invoquer une méthode "memo" comme le montre l'exemple ci-dessous.
Code Pascal (Fibonacci simple) (9 lignes)
function fibonacci(var n : integer) : integer; {Pre: n>=0} begin if n < 2 then fibonacci := n; else fibonacci := fibonacci(n-1) + fibonacci(n-2); end;
Dans cette implémentation, nous pouvons remarquer deux appels récursifs1, ce qui donne un ordre de grandeur de temps T(n) ≤ (2n)
Code Pascal (Fibonacci avec mémoïsation) (35 lignes)
program fibonacci-memo; cons undef = -1; {valeur qui n'est jamais employee et qui signifie une valeur non definie} cons maxIndice = 20; var memoArray : packed array[0..maxIndice] of integer; function fibonacci(var n : integer) : integer; {Pre: 0 <= n <= maxIndice} begin if n < 2 then fibonacci := n; else fibonacci := fibonacciMemo(n-1) + fibonacciMemo(n-2); end; function fibonacciMemo(var n : integer) : integer; {Pre: 0 <= n <= maxIndice} begin if memoArray[n] = undef then memoArray[n] := fibonacci(n);{il est necessaire de calculer la valeur} fibonacciMemo := memoArray[n];{la valeur est deja calculee} end; {main program} begin var i : integer; var j : integer; j:=20; for i:=0 to maxIndice do memoArray[i]:=undef; writeln('Fibonacci(', j, ') = ', fibonacci(j)); end.
Dans cette deuxième implémentation, nous pouvons remarquer que le point de départ est toujours notre fonction fibonacci(n), mais que chaque appel au calcul(anciennement un appel récursif) est remplacé par un appel à la nouvelle fonction de mémoïsation. La complexité en temps de ce deuxième exemple est de (n) * (Trecherche et/ou écriture; dans le tableau), donc de (n) ou au maximum identique à l'exemple précédent.
La fonction de mémoïsation vérifie si le calcul a déjà été effectué(si c'est le cas une valeur est présente à l'indice n dans le tableau) et retourne cette valeur si c'est le cas, ou effectue le calcul et stocke la valeur dans le tableau avant de la renvoyer comme résultat.
mémoïsation, mémoization, ou programmation dynamique descendante ?
La mémoïsation est aussi appelée programmation dynamique descendante, car nous vérifions la présence de la valeur avant de faire l'appel récursif. Nous retrouvons aussi souvent ce type d'algorithme sous le nom de mémoization.Récursivité ascendante
Comme pour la mémoïsation, la récursivité ascendante nous permet de construire une table des résultats intermédiaires, mais ici de manière à traiter le problème le plus simple en premier. Si nous calculons les résultats intermédiaires lors de la "remontée"2 dans l'arbre, la probabilité que la valeur se trouve déjà dans le tableau des résultats est beaucoup plus importante.
La complexité de ce type d'algorithme est généralement de (n)
La structure de notre programme est modifiée, car nous utiliserons une boucle au lieu d'appels récursifs. Nous économisons donc la pile induite par notre alorithme récursif (gain mémoire), et nous nous assurons un remplissage optimal des résultats.
Problème de la récursivité ascendante
Le bien fondé de l'approche dynamique est indiscutable lorsque tout résultat repose sur d'autres résultats antérieurs, mais ce n'est pas toujours le cas. Si ce n'est pas le cas, nous ne devrions mémoriser que les sous-résultats nécessaires, sinon nous risquons d'effectuer une série de calculs inutiles. Nous pouvons tenter de déterminer quels sont les sous-résultats nécessaires par un graphe : lorsque plusieurs flèches pointent vers la même valeur nous sommes en présence d'un "re-calcul".
Optimisation de la récursivité ascendante
Nous pouvons parfois réduire la place mémoire nécessaire, comme le montre le schéma aminé suivant qui ne mémorise que les 2 derniers résultats de sous-problèmes.
Deutsche Übersetzung
Sie haben gebeten, diese Seite auf Deutsch zu besuchen. Momentan ist nur die Oberfläche übersetzt, aber noch nicht der gesamte Inhalt.Wenn Sie mir bei Übersetzungen helfen wollen, ist Ihr Beitrag willkommen. Alles, was Sie tun müssen, ist, sich auf der Website zu registrieren und mir eine Nachricht zu schicken, in der Sie gebeten werden, Sie der Gruppe der Übersetzer hinzuzufügen, die Ihnen die Möglichkeit gibt, die gewünschten Seiten zu übersetzen. Ein Link am Ende jeder übersetzten Seite zeigt an, dass Sie der Übersetzer sind und einen Link zu Ihrem Profil haben.
Vielen Dank im Voraus.
Dokument erstellt 19/10/2009, zuletzt geändert 26/10/2018
Quelle des gedruckten Dokuments:https://www.gaudry.be/de/programmer-dynamique.html
Die Infobro ist eine persönliche Seite, deren Inhalt in meiner alleinigen Verantwortung liegt. Der Text ist unter der CreativeCommons-Lizenz (BY-NC-SA) verfügbar. Weitere Informationen auf die Nutzungsbedingungen und dem Autor.
Referenzen
- IHDCB331 - Méthodes de Programmation : PY Schobbens,
Cours de Méthodes de Programmation
(September 2009) - Programmation dynamique : Wikipedia (version 17/10/09)
- 20bits : Jesse Farmer,
Introduction to Dynamic Programming
(version 19/10/09) - Memoization & Java : Tom White,
Memoization in Java Using Dynamic Proxy Classes
(version 17/10/09) - Memoization & C++ : Paul McNamee,
Automated Memoization in C++
(version 17/10/09)
Diese Verweise und Links verweisen auf Dokumente, die während des Schreibens dieser Seite konsultiert wurden, oder die zusätzliche Informationen liefern können, aber die Autoren dieser Quellen können nicht für den Inhalt dieser Seite verantwortlich gemacht werden.
Der Autor Diese Website ist allein dafür verantwortlich, wie die verschiedenen Konzepte und Freiheiten, die mit den Nachschlagewerken gemacht werden, hier dargestellt werden. Denken Sie daran, dass Sie mehrere Quellinformationen austauschen müssen, um das Risiko von Fehlern zu reduzieren.