La programmation dynamique vous permet de penser à la conception d'algorithmes. C'est souvent très utile.
La mémorisation et les méthodes ascendantes vous donnent une règle / méthode pour transformer les relations de récurrence en code. La mémorisation est une idée relativement simple, mais les meilleures idées le sont souvent!
La programmation dynamique vous donne une façon structurée de penser au temps d'exécution de votre algorithme. Le temps d'exécution est essentiellement déterminé par deux nombres: le nombre de sous-problèmes que vous devez résoudre et le temps qu'il faut pour résoudre chaque sous-problème. Cela fournit un moyen simple et pratique de réfléchir au problème de conception de l'algorithme. Lorsque vous avez une relation de récurrence candidate, vous pouvez l'examiner et obtenir très rapidement une idée de la durée d'exécution (par exemple, vous pouvez souvent dire très rapidement combien de sous-problèmes il y aura, ce qui est une limite inférieure de la temps d'exécution; s'il y a de façon exponentielle de nombreux sous-problèmes que vous devez résoudre, la récurrence ne sera probablement pas une bonne approche). Cela vous aide également à exclure les décompositions de sous-problèmes potentiels. Par exemple, si nous avons une chaîne , définissant un sous-problème par un préfixe S [ 1 .. i ] ou un suffixe S [ j . . n ] ou sous-chaîne S [ i . . j ] pourrait être raisonnable (le nombre de sous-problèmes est polynomial en n ), mais définir un sous-problème par une sous-séquence de S n'est probablement pas une bonne approche (le nombre de sous-problèmes est exponentiel en n ). Cela vous permet d'élaguer "l'espace de recherche" des récidives possibles.S[1..n]S[1..i]S[j..n]S[ i . . j ]nSn
La programmation dynamique vous offre une approche structurée pour rechercher des relations de récurrence candidates. Empiriquement, cette approche est souvent efficace. En particulier, il existe des modèles heuristiques / communs que vous pouvez reconnaître pour des manières courantes de définir des sous-problèmes, selon le type d'entrée. Par exemple:
Si l'entrée est un entier positif , une façon possible de définir un sous-problème consiste à remplacer n par un entier plus petit n ′ (st 0 ≤ n ′ ≤ n ).nnn′0 ≤ n′≤ n
Si l'entrée est une chaîne , certaines façons possibles de définir un sous-problème incluent: remplacer S [ 1 .. n ] par un préfixe S [ 1 .. i ] ; remplacerS[ 1 .. n ]S[ 1 .. n ]S[ 1 .. i ] par un suffixe S [ j . . n ] ; remplacer S [ 1 .. n ] par une sous-chaîne S [ i . . j ]S[ 1 .. n ]S[ j . . n ]S[ 1 .. n ]S[ i . . j ]. (Ici, le sous-problème est déterminé par le choix de .)je , j
Si l'entrée est une liste , faites la même chose que pour une chaîne.
Si l'entrée est un arbre , une façon possible de définir un sous-problème consiste à remplacer T par n'importe quel sous-arbre de T (c'est-à-dire, choisir un nœud x et remplacer T par le sous-arbre enraciné en x ; le sous-problème est déterminé par le choix de x ).TTTXTXX
Si l'entrée est une paire , examinez récursivement le type de x et le type de y pour identifier une façon de choisir un sous-problème pour chacun. En d'autres termes, une façon possible de définir un sous-problème est de remplacer ( x , y ) par ( x( x , y)Xy( x , y) où x ′ est un sous-problème pour x et y ′ est un sous-problème pour y . (Vous pouvez également considérer les sous-problèmes du formulaire ( x , y( x′, y′)X′Xy′y Ou ( x ′ , y ) .)( x , y′)( x′, y)
Etc. Cela vous donne une heuristique très utile: juste en regardant la signature de type de la méthode, vous pouvez trouver une liste de façons possibles de définir des sous-problèmes. En d'autres termes, juste en regardant l'énoncé du problème - en ne regardant que les types d'entrées - vous pouvez trouver une poignée de façons possibles de définir un sous-problème.
C'est souvent très utile. Il ne vous dit pas quelle est la relation de récurrence, mais lorsque vous avez un choix particulier pour définir le sous-problème, il n'est souvent pas trop difficile de trouver une relation de récurrence correspondante. Ainsi, cela transforme souvent la conception d'un algorithme de programmation dynamique en une expérience structurée. Vous écrivez sur du papier brouillon une liste de façons possibles de définir les sous-problèmes (en utilisant l'heuristique ci-dessus). Ensuite, pour chaque candidat, vous essayez d'écrire une relation de récurrence et d'évaluer son temps d'exécution en comptant le nombre de sous-problèmes et le temps passé par sous-problème. Après avoir essayé chaque candidat, vous gardez le meilleur que vous ayez pu trouver. Fournir une certaine structure au processus de conception de l'algorithme est une aide majeure, car sinon la conception de l'algorithme peut être intimidante (il '