• Aucun résultat trouvé

Programmation dynamique ▷

N/A
N/A
Protected

Academic year: 2022

Partager "Programmation dynamique ▷"

Copied!
10
0
0

Texte intégral

(1)

Programmation dynamique

Histoire de l’informatique

Structures de données

Bases de données

Architectures matérielles et systèmes d’exploitation

Langages et programmation

Algorithmique

Ce document a pour objectif d’accompagner le professeur dans sa préparation de cours. Il n’est pas une proposition de cours et présente quelques notions qui dépassent le niveau attendu des élèves. En outre, les différents exemples traités ici n’ont pas vocation à être tous utilisés en classe, le professeur reste libre d’en choisir de tout autres s’il le souhaite.

1. Le problème du rendu de monnaie

1.1. Retour sur l’algorithme glouton

On a déjà vu en classe de première qu’un algorithme glouton pouvait donner une réponse au problème.

Voirhttps://cache.media.eduscol.education.fr/file/NSI/76/4/RA_Lycee_G_NSI_algo-gloutons_

1170764.pdf

Fixons les notations. On suppose donné un système monétaire où les valeurs faciales des pièces (ou des billets) sont rangées en ordre décroissant. Par exemple, le système Euro pourra être décrit par la listeeuros = [50, 20, 10, 5, 2, 1]. Pour payer une somme de 48 unités on pourrait bien sûr payer 48 pièces de 1, ou encore 3 pièces de 10, 3 pièces de 5, 1 pièce de 2 et 1 pièce de 1.

On cherche à payer la somme indiquée, en supposant qu’on a autant de pièces de chaque valeur que de besoin, en utilisant un nombre minimal de pièces.

L’algorithme glouton consiste à payer d’abord avec la plus grosse pièce possible : ici, il s’agit de 20, puisque 50 > 48. Ayant donné 20, il reste 28 à payer, et on poursuit avec la même méthode. Finalement, on va payer 48 sous la forme48=20+20+5+2+1. On a eu besoin de 5 pièces.

Considérons un autre système monétaire (en fait c’est l’ancien système impérial britannique) représenté par la liste suivante de valeurs faciales :imperial = [30, 24, 12, 6, 3, 1]. Pour payer 48 l’algorithme glouton va répondre :48=30+12+6. À la différence du système euro, pour lequel on peut démontrer que l’algorithme glouton donne toujours la réponse optimale, on constate que ce n’est pas le cas avec le système impérial, puisque on aurait pu se contenter de 2 pièces :48=24+24.

Rappelons comment peut se programmer l’algorithme glouton.

euros = [50, 20, 10, 5, 2, 1]

imperial = [30, 24, 12, 6, 3, 1]

def glouton(valeursFaciales, somme):

i = 0 # index de la pièce qu'on va essayer

p = len(valeursFaciales) # nombre de valeurs de pièces disponibles

(2)

monnaie = [] # liste des pièces rendues while i<p and somme>0:

if valeursFaciales[i]>somme:

i += 1 else:

monnaie.append(valeursFaciales[i]) somme -= valeursFaciales[i]

if somme==0:

return monnaie else:

return None

L’appelglouton(euros, 48)renvoie la liste[20, 20, 5, 2, 1]; l’appelglouton(imperial, 48)renvoie la liste [30, 12, 6].

1.2. Recherche de la réponse optimale

La réponse optimale est celle qui utilise le nombre minimal de pièces. On a vu que l’algorithme glouton échoue à la trouver en général (l’exemple de rendre 48 avec le sysètme impérial suffit à le prouver).

Une approche récursive permet de résoudre le problème : soitxune valeur faciale de l’une des pièces du système monétaire. Pour rendre une sommesde façon optimale, si l’on veut utiliser au moins une fois la piècex, il suffit de rendrexet la sommes−xde façon optimale. Il n’y a plus qu’à choisir, parmi tous les choix possibles dex, celui qui permet d’utiliser le minimum de pièces.

Autrement dit, si j’appellef(s)le nombre minimal de pièces qu’il faut utiliser pour payer la sommex, on a simplementf(0) =0et

f(s) =min

x⩽s(1+f(s−x)),

le minimum étant calculé sur toutes les valeursxd’une pièce.

Cette remarque permet d’écrire le programme récursif suivant : from math import inf

def dynRecursif(valeursFaciales, somme):

if somme < 0:

return inf elif somme == 0:

return 0 mini = inf

for x in valeursFaciales:

if somme >= x:

mini = min(mini, 1 + dynRecursif(valeursFaciales, somme - x)) return mini

On a importé du modulemathla valeur spécialeinfqui représente l’infini : pour tout entiera, l’expressiona < infvautTrue. Hélas, l’exécution de l’appeldynRecursif(euros, 48)estextrêmementlente. les mêmes calculs étant effectués de façon répétée. Une analyse du problème montrerait que la complexité est exponentielle.

Une idée classique consiste à mémoriser les résultats des appels pour être sûr qu’on n’aura pas besoin de les calculer plusieurs fois.

Ici, le calcul def(s)utilise le calcul def(s−x)pour chaque valeur dex. Autrement dit,f(s)n’utilise au plus que les valeurs de

f(s−1),f(s−2), …,f(3),f(2),f(1).

On va donc créer un tableau conservant ces données, et calculer de façon systématique pour des valeurs croissantes de l’index.

def dynMemoise(valeursFaciales, somme):

f = [0] * (somme + 1)

for s in range(1, somme + 1):

f[s] = inf

for x in valeursFaciales:

(3)

if s >= x:

f[s] = min(f[s], 1 + f[s - x]) return f[somme]

Le calcul def(48)va peut-être utiliser plusieurs fois la valeur def(8), mais celle-ci n’aura cette fois été calculée qu’une seule fois, et aussitôt rangée en mémoire. Cela ne fonctionne que parce que pour calculerf(48), par exemple, on a recours seulement aux valeurs def(s)pours<48.

Cette technique est habituellement appeléemémoïsation, fort laide tentative de traduction de l’anglaismemoizationmais qui est passée dans l’usage.

Cette fois l’appeldynMemoise(euros, 48)répond immédiatement 5 (l’algorithme glouton avait bien trouvé la réponse optimale) etdynMemoise(imperial, 48)renvoie 2, ce qui correspond à la solution optimale48=24+24.

L’algorithme est de complexité tout à fait raisonnable : les deux boucles emboîtées correspondent à un temps d’exécution de l’ordre desomme * len(valeursFaciales).

En revanche, il a un coût en espace (ou en mémoire) : il faut réserver la place nécessaire pour ranger toutes les valeurs def(n).

1.3. Reconstitution des détails de la réponse optimale

Si on remplace la dernière ligne de la fonctiondynMemoiseparreturn f, l’appeldynMemoise(imperial, 17) renvoie le tableau[0, 1, 2, 1, 2, 3, 1, 2, 3, 2, 3, 4, 1, 2, 3, 2, 3, 4].

Il y a une réponse optimale avec 4 pièces, comment la reconstituer ?

On af(17) =4, on cherche une piècextelle quef(17−x) =3, on a le choix entrex=1,x=3etx=12. Choisissonsx=12. On af(17) = 1+f(5). On cherche maintenant une piècextelle quef(5−x) =2, on peut choisirx = 3(x = 1aurait également convenu). On af(17) =1+f(5) =1+1+f(2)et enfinf(2) =1+f(1). Une solution optimale est donc de payer

17=1+1+3+12, avec 4 pièces.

On peut modifier la fonction précédente pour reconstituer une décomposition optimale : il suffit de détailler le calcul du minimum et en profiter pour mémoriser les valeurs notéesx,xetc. ci-dessus.

def dynMemoiseReconstitue(valeursFaciales, somme):

f = [0] * (somme + 1) g = [0] * (somme + 1)

for s in range(1, somme + 1):

f[s] = inf

for x in valeursFaciales:

if s >= x:

if 1 + f[s - x] < f[s]:

f[s] = 1 + f[s - x] # mise à jour du minimum g[s] = s - x # on retient d'où l'on vient monnaie = []

s = somme while s>0 :

monnaie.append(s - g[s]) s = g[s]

return monnaie

2. Qu'est-ce que la programmation dynamique ?

On peut dire que la résolution algorithmique d’un problème relève de la programmation dynamique si

le problème peut être résolu à partir de sous-problèmes similaires mais plus petits ;

l’ensemble de ces sous-problèmes est discret, c’est-à-dire qu’on peut les indexer et ranger les résultats dans un tableau ;

la solution optimale au problème posé s’obtient à partir des solutions optimales des sous-problèmes ;

les sous-problèmes ne sont pas indépendants et un traitement récursif fait apparaître les mêmes sous-problèmes un grand nombre de fois.

(4)

En général, c’est trouver une équation récursive, comme on l’a fait en, 1.2 pour le problème du rendu de monnaie, qui constitue l’étape essentielle de la résolution du problème.

Les algorithmes gloutons, en revanche, ne fournissent pas toujours une solution optimale, il décompose le problème initial à l’aide d’un seul sous-problème.

Les algorithmes nombreux sont souvent moins coûteux en exécution, mais sans pouvoir garantir une solution optimale.

Dans les deux cas, une propriété importante à vérifier est que l’optimalité d’une solution au problème soit garantie par l’optimalité des solutions aux sous-problèmes.

3. Un autre exemple : le problème du sac à dos

3.1. Description du problème, une équation récursive

On dispose denobjets de poids entiers strictement positifsp0,p1, …,pn−1, et auxquels on attache une valeur, qui est également un entier strictement positif. On notev0,v1, …,vn−1les valeurs de ces objets.

On dispose d’un sac qu’on ne doit pas surcharger : le poids total des objets qu’il contient ne doit pas dépasser un certain poidsP. On cherche à maximiser le total des valeurs des objets du sac.

Autrement dit, on cherche des nombresxivalant 0 ou 1 tels queni=1xipi⩽Ptout en maximisantni=1xivi.

Par exemple, on peut considérer des objets de poids respectifs 1, 2, 5, 6 et 7 et de valeurs 1, 6, 12, 18, 22, 28, et un sac dont le poids ne peut dépasser 11.

On peut choisir les objets de poids 1, 2 et 7, obtenant une valeur égale à1+6+28=35. On peut aussi choisir les objets de poids 5 et 6, obtenant une valeur égale à12+18=40. Mais comment s’assurer qu’on ne peut pas mieux faire ?

Notons, pour0⩽i⩽n−1et0⩽p⩽P,V(i, p)la valeur maximale des objets qu’on peut ranger parmi ceux de numéros 0 ài

sans dépasser le poidsp. La valeur maximale que nous cherchons est alorsV(n−1, P). Bien sûr, pour touti,V(i,0) =0etV(0, p) =

⎧⎪

⎪⎪

0 sip<p0; v0 sip⩾p0.

La clé de la résolution tient à l’équation récursive :

V(i, p) =

⎧⎪

⎪⎪

V(i−1, p) sip<pi; max(V(i−1, p), vi+V(i−1, p−pi)) sip⩾pi.

On a distingué le cas où on renonce à utiliser l’objet numéroi: la valeur maximale est alorsV(i, p). Si on utilise l’objet numéroi, on doit limiter àp−pile poids consacré aux objets de numéros 0 ài−1et ajouter sa valeurvià la valeur optimale du sous-problème.

3.2. Programmation

On en déduit facilement le programme suivant, où on initialise le tableauVà zéro.

def sacADos1(poids, valeurs, poidsMaximum):

assert(len(poids) == len(valeurs)) n = len(poids)

V = [[0]*(poidsMaximum + 1) for i in range(n)]

for p in range(poids[0], poidsMaximum + 1):

V[0][p] = valeurs[0]

for i in range(1, n):

for p in range(poids[i]):

V[i][p] = V[i - 1][p]

for p in range(poids[i], poidsMaximum + 1):

V[i][p] = max(V[i - 1][p], valeurs[i] + V[i - 1][p - poids[i]]) return V[n - 1][poidsMaximum]

Avecpoids = [1, 2, 5, 6, 7]etvaleurs = [1, 6, 18, 22, 28], l’appelsacADos1(poids, valeurs,

11) renvoie 40.

(5)

L’imbrication des deux boucles permet d’assurer un temps d’exécution de l’ordre den×P. Le coût en mémoire est également de l’ordre den×P, puisqu’il faut réserver la place du tableauV.

En fait, on pourrait consommer moins de mémoire. En effet on s’aperçoit que le calcul de la ligneV[i]du tableau utilise seulement les valeurs de la ligne précédentV[i - 1]et que la mise à jour deV[i][p]n’utilise que les valeurs deV[i - 1][k]pour kp.

Ainsi peut-on réécrire le programme de la façon suivante, en n’utilisant qu’une ligne de mémoire pour le tableauV.

1 def sacADos2(poids, valeurs, poidsMaximum):

2 assert(len(poids) == len(valeurs))

3 n = len(poids)

4 L = [0] * poids[0] + [valeurs[0]] * (poidsMaximum + 1 - poids[0])

5 for i in range(1, n):

6 for p in range(poids[i], poidsMaximum + 1):

7 L[p] = max(L[p], valeurs[i] + L[p - poids[i]])

8 return L[poidsMaximum]

En ligne 4, on initialise la ligne correspondant à l’objet 0 :V[0][p]est nul sip<p0, et égal àv0sip⩾p0.

4. Alignement de séquences : la plus longue sous-chaîne com- mune

4.1. Présentation du problème

Le séquençage du génome a conduit à une collaboration fructueuse entre généticiens et informaticiens et au développement de nouveaux algorithmes.

En effet, on peut coder le génome avec les quatre lettresACTGqui sont les initiales de quatre bases nucléiques : adénine, cytosine, thymine et guanine.

Du point de vue algorithmique, on considère donc des mots (très très longs) sur cet alphabet.

Un problème utile aux généticiens est celui de l’alignement de séquences, qui se décline en de nombreux sous-problèmes, dont plusieurs peuvent être abordés à l’aide de la programmation dynamique.

Nous nous intéressons ici à la recherche de la plus longue sous-chaîne commune. Étant donné deux textesxetysur l’alphabet

{A, C, T, G}, on cherche le textezle plus longueur possible qui soit à la fois extrait dexet dey. Dire quezest extrait dexsignifie que l’on peut obtenirzen effaçant des lettres dex. En particulier, les lettres qui figurent danszfigurent, dans le même ordre dansx. Prenons l’exemple dex=AT AT ACAGGT CAety=GACT ACACGACT, pour lesquels une plus longue sous-chaîne commune estz=AT ACACA.

A T A T A C A G G T C A

A T A C A C A

G A C T A C A C G A C T

Notons bien qu’il n’y a pas unicité !AT AACAT est également une plus longue sous-chaîne commune, de même longueur 7, comme le schéma suivant le montre :

A T A T A C A G G T C A

A T A A C A T

G A C T A C A C G A C T

Dans la suite, nous noterons plscc(x, y)une plus longue sous-chaîne commuen dexety, etλ(x, y)sa longueur.1 Autrement dit : plscc(AT AT ACAGGT CA, GACT ACACGACT) =AT AACAT

etλ(AT AT ACAGGT CA, GACT ACACGACT) =7.

1. comme il n’y a pas unicité, il s’agit d’un abus de langage. Il serait plus correct de noter plscc(x, y)pour l’ensemble des plus longues sous-chaînes communes dexetyet donc, sizest l’une d’elle,zplscc(x, y).

(6)

4.2. Résolution du problème

Considérons deux motsxety, dont on cherche la longueurλ(x, y)des plus longues sous-chaînes communes.

On peut distinguer deux cas :

Premier cas Si les deux mots ont la même lettre finale, on écritx=uAety=vAAest la lettre finale en commun et oùuetv

sont les préfixes des deux mots. Alors bien sûr, une plus longue sous-chaîne commune dexetypeut se construire en cherchant une plus longue sous-chaîne commune deuetvet d’y ajouter au bout la lettreA. Autrement dit :λ(x, y) =λ(u, v) +1. Deuxième cas Si les deux mots n’ont pas la même lettre finale, par exemple six=uAety=vT, on ne peut pas aligner leAet

leT. Mais on pourrait trouver unAdansvou unTdansu, donc on doit chercher la plus longue sous-chaîne commune entre plscc(uA, v)et plscc(u, vT). Autrement dit :λ(x, y) =max(λ(x, v), λ(u, y)).

4.3. Programmation

Calcul de la longueur d’une plus longue sous-chaîne commune

On applique la discussion menée ci-dessus.

Pour cela on va construire une matricelongueurtelle quelongueur[i][j]représente la longueur d’une plus longue sous- chaîne commune des préfixesx[:i]ety[:j]dexety.

Il faut faire attention à l’initialisation de cette matrice, c’est-à-dire aux valeurs à donner àlongueur[i][0](ou bien, de la même façon, àlongueur[0][j]). En effet, on ne peut pas, quandi(ouj) vaut 0, utiliser l’indicei−1(ouj−1), ce qui oblige à gérerà la mainces cas particuliers.

C’est ce qui est fait dans le programme suivant, en lignes 5–16. C’est finalement le cas général, traité en lignes 17–24, qui se révèle le plus simple.

1 def lplscc(x, y):

2 n = len(x)

3 p = len(y)

4 longueur = [[0]*p for i in range(n)]

5 if x[0]==y[0]:

6 longueur[0][0] = 1

7 for i in range(1, n):

8 if x[i]==y[0]:

9 longueur[i][0] = 1

10 else:

11 longueur[i][0] = longueur[i - 1][0]

12 for j in range(1, p):

13 if x[0]==y[j]:

14 longueur[0][j] = 1

15 else:

16 longueur[0][j] = longueur[0][j - 1]

17 for i in range(1, n):

18 for j in range(1, p):

19 if x[i] == y[j]:

20 longueur[i][j] = longueur[i - 1][j - 1] + 1

21 elif longueur[i - 1][j]>longueur[i][j - 1]:

22 longueur[i][j] = longueur[i - 1][j]

23 else:

24 longueur[i][j] = longueur[i][j - 1]

25 return longueur[n - 1][p - 1]

(7)

Calcul d’une plus longue sous-chaîne commune

On s’inspire du programme précédent pour calculer une plus longue sous-chaîne commune.

Il suffit de créer une nouvelle matrice, appelée iciscc, telle quescc[i][j]contienne une plus longue sous-chaîne commune de x[:i]ety[:j]. Il s’agit donc d’une matrice de chaînes de caractères.

Cela signifie qu’on associe à chaque affectation delongueurune affectation descc.

Quand on recopie une valeur précédente danslongueur, on fait tout simplement de même pour la matricescc.

En revanche, à l’affectationlongueur[i][j] = longueur[i - 1][j - 1] + 1, on associe l’instruction suivante : scc[i][j] = scc[i - 1][j - 1] + x[i], c’est-à-dire qu’on prolonge la sous-chaîne commune avec le caractère en commun qu’on vient de repérer.

def plscc(x, y):

n = len(x) p = len(y)

longueur = [[0]*p for i in range(n)]

scc = [[""]*p for i in range(n)]

if x[0]==y[0]:

longueur[0][0] = 1 scc[0][0] = x[0]

for i in range(1, n):

if x[i]==y[0]:

longueur[i][0] = 1 scc[i][0] = y[0]

else:

longueur[i][0] = longueur[i - 1][0]

scc[i][0] = scc[i - 1][0]

for j in range(1, p):

if x[0]==y[j]:

longueur[0][j] = 1 scc[0][0] = x[0]

else:

longueur[0][j] = longueur[0][j - 1]

scc[0][j] = scc[0][j - 1]

for i in range(1, n):

for j in range(1, p):

if x[i] == y[j]:

longueur[i][j] = longueur[i - 1][j - 1] + 1 scc[i][j] = scc[i - 1][j - 1] + x[i]

elif longueur[i - 1][j]>longueur[i][j - 1]:

longueur[i][j] = longueur[i - 1][j]

scc[i][j] = scc[i - 1][j]

else:

longueur[i][j] = longueur[i][j - 1]

scc[i][j] = scc[i][j - 1]

return scc[n - 1][p - 1]

4.4. Une amélioration

Le coût d’exécution des programmes précédents est de l’ordre den×pdans la mesure où on a deux boucles emboîtées à l’intérieur desquelles se déroulent un nombre fini d’instructions.

Cependant, leur coût en mémoire est important, puisqu’il faut réserver de la place pour une (ou deux) matrice(s) de taillen×p.

(8)

En examinant le code précédent, on observe que le calcul de l’élément d’indices(i, j)n’utilise que les valeurs d’indices(i−1, j−1),

(i−1, j)ou(i, j−1).

Comme le montre la figure ci-dessous, il suffit donc de conserver en mémoire deux “diagonales”2verte et bleue pour en déduire la suivante, colorée en jaune.

Si on est attentif, on s’aperçoit même que l’on peut calculer les nouvelles valeurs (en jaune) en écrasant les plus anciennes en vert.

En effet, une case verte n’est utilisée qu’une seule fois, pour le calcul d’une seule case jaune.

Cela permet de passer d’un coût mémoire de l’ordre den×pà un coût de l’ordre de2 max(n, p). Cependant, cela rend techniquement beaucoup plus difficile la programmation de l’algorithme, le travail sur les indices est bien plus compliqué.

Dans une première lecture, on peut s’arrêter ici : le programme ci-dessous est vraiment peu lisible, très technique, et largement au-dessus de ce qui peut être attendu des lycéens.

Sin=/ p, on ajoute en nombre suffisant des espaces au motxou au motyde telle sorte qu’ils soient finalement de la même longueur. Cela ne modifiera pas les sous-chaînes communes, puisque l’espace ne figurait au départ ni dansxni dansy. Cela permet d’assurer quen=p, ce qui simplifie un petit peu le travail.

La figure ci-dessous montre comment on indexe les diagonales, dek=0àk=2n−2: la case jaune modifiée doit avoir le même index que la case verte qui est utilisée par le calcul.

0 1

2 3

4 5

6

0 1

2 3

4 5

0 1

2

4 5

1 2

3 4 2

3

1 2

3 4

2 3 1

2 3

4 5

2 3

4 3 1

2 3

4 5 2

3 4

3 k=0 k=1 k=2 k=3 k=4 k=5 k=6 k=7 k=8 k=9 k=10 k=11 k=12

On propose le programme suivant pour le calcul de la longueur de la plus longue sous-chaîne commune. Il va de soi que sa difficulté empêche son utilisation directe en classe. Il démontre simplement la possibilité de ne recourir qu’à deux tableaux de taillen, sans utiliser de matricen×p.

Quelques explications :

kdésigne le numéro de la diagonale en cours de traitement ;

rdésigne l’index dans les tableauxlongueurVetlongueurBde l’élément qui serait dans la première colonne de la matrice ;

2. dans la suite on continuera à les appeler des diagonales, ce qui est bien sûr un abus de langage

(9)

retr2sont les index dans la diagonale bleue des éléments qui sont utiles au calcul de l’élément d’indexrdans la diagonale jaune (tantôt au-dessus, tantôt à sa gauche dans la matrice) ;

ietjdésignent les index des caractères observés dansxety. def lplscc2(x, y):

n = len(x) p = len(y) if n < p:

x = x + (" " * (p - n)) n = p

elif n > p:

y = y + (" " * (n - p)) p = n

assert(n == p) longueurV = [0]*n longueurB = [0]*n

# initialisation diagonale k=0 k, r = 0, (n - 1)//2

if x[0] == y[0]:

longueurV[r] = 1

# initialisation diagonale k=1 k, r = 1, (n - 2)//2

if x[0] == y[1]:

longueurB[r + 1] = 1 if x[1] == y[0]:

longueurB[r] = 1

#--- phase 1 ---

for k in range(2, n): # les diagonales qui rallongent

for r in range((n - k - 1)//2, (n - k - 1)//2 + k + 1):

j = r - (n - k - 1)//2 i = k - j

r2 = r + 1 if (n + k)%2 == 0 else r - 1 if x[i] == y[j]:

longueurV[r] = longueurV[r] + 1 else:

longueurV[r] = max(longueurB[r], longueurB[r2]) longueurV, longueurB = longueurB, longueurV

#--- phase 2 ---

for k in range(n, 2*n - 1): # les diagonales qui rétrécissent for r in range((k - n + 1)//2, (k - n + 1)//2 + 2*n - k - 1):

i = n - 1 - r + (k - n + 1)//2 j = k - i

r2 = r + 1 if (n + k)%2 == 0 else r - 1 if x[i] == y[j]:

longueurV[r] = longueurV[r] + 1 else:

longueurV[r] = max(longueurB[r], longueurB[r2]) longueurV, longueurB = longueurB, longueurV

return longueurB[(n - 1)//2]

(10)

5. Références

Le lecteur qui voudrait en savoir davantage peut consulter les ouvrages suivants, d’un niveau général plus élevé bien sûr que celui attendu en NSI, mais qui peuvent contribuer utilement au professeur.

Benjamin Werner et François Pottier ont mis en ligne leur cours de l’école Polytechnique, dont le chapitre 8 est consacré à la program- mation dynamiquehttps://www.enseignement.polytechnique.fr/informatique/INF431/X11-2012-2013/

inf431-poly.pdf

Pour les lecteurs anglophones, on peut aussi conseiller le chapitre 6 du livreAlgorithm Design, de Jon Kleinberg et Éva Tardos qui enseignent à la Cornell University, ouvrage qui est également disponible en lignehttp://www.cs.sjtu.edu.cn/~jiangli/

teaching/CS222/files/materials/Algorithm%20Design.pdf

Références

Documents relatifs

Dans la suite du problème on suppose que x et y représentent des quantités économiques et que f est une fonction économique définie sur

Lorsque des r´ esultats du cours seront utilis´ es, ils devront clairement ˆ etre. ´

[r]

[r]

Remarque : Ces résultats sont notamment utilisés en mécanique pour le travail exercé par une force lors d’un déplacement.. On dit que deux vecteurs

Dans chacun des cas suivants, calculer cos( AB , AC ) et déterminer la mesure principale en radians de l’angle ( AB , AC ).. On rappelle que les triangles formés par O et

[r]

Les bases locales (à chaque sous domaine) étant orthonormées, on en déduit que les blocs diagonaux sont eux même diagonaux (égaux d'ailleurs à l'identité pour la matrice de masse