Prepa Help
Prepa Help › Cours › Informatique, 2e année

Programmation dynamique : cours, mémoïsation, sac à dos et exercices corrigés de prépa

Sous-structure optimale et chevauchement des sous-problèmes, équation de récurrence, mémoïsation et méthode ascendante, reconstruction d'une solution, sac à dos, rendu de monnaie, partition équilibrée, ordonnancement de tâches pondérées, plus longue sous-suite commune, distance d'édition, complexités en temps et en mémoire, comparaison avec les algorithmes gloutons, Floyd-Warshall (classes françaises).

Les nombres sont ceux du chapitre complet ; chaque filière voit la partie qui correspond à son programme.

La programmation dynamique résout un problème d'optimisation en le découpant en sous-problèmes du même type, chacun résolu une seule fois, dont les réponses sont gardées dans un tableau ou un dictionnaire. Elle demande deux propriétés : la sous-structure optimale, qui la rend exacte, et le chevauchement des sous-problèmes, qui la rend rapide. Le chapitre est au programme d'informatique commune de deuxième année en MP et PSI, au Maroc et en France, et en PC et PT en France. Il enseigne à définir le sous-problème par une phrase exacte, à écrire la récurrence avec ses cas de base et ses cas interdits, à programmer la version descendante par mémoïsation et la version ascendante par un tableau, à reconstruire une solution optimale et à justifier la complexité en temps et en mémoire. Les exemples sont le sac à dos binaire, où le glouton ne donne que 117 quand l'optimum vaut 132, le rendu de monnaie, la partition équilibrée, la plus longue sous-suite commune, la distance d'édition, le chemin de coût minimal dans une grille et les tâches pondérées ; les classes françaises y ajoutent l'algorithme de Floyd-Warshall, et le programme marocain demande le meilleur et le pire cas. Les exercices et les problèmes reprennent des questions des sujets du CNC, de Mines-Ponts, de X-ENS et de CCINP.

Ce que tu sauras faire

Beaucoup de problèmes d'optimisation se découpent en problèmes plus petits du même type. Quand ces petits problèmes reviennent sans cesse, les résoudre une seule fois et garder leurs réponses change tout : un calcul exponentiel devient un calcul de quelques milliers d'opérations. C'est l'idée de la programmation dynamique. Elle donne toujours l'optimum, au prix d'un tableau à remplir.

L'essentiel : Le sac à dos binaire, modèle de tout le chapitre Extrait gratuit

Un extrait de la fiche « L'essentiel » du chapitre. Les démonstrations sont dans le cours détaillé.

T[i][r]T[i][r] : meilleure valeur avec les ii premiers objets et une charge rr. L'objet i−1i - 1, de valeur vv et de poids pp, est laissé ou pris :

T[i][r]=max⁡(T[i−1][r], v+T[i−1][r−p]) si p≤r,T[i][r]=T[i−1][r] sinon,T[i][r] = \max\big(T[i-1][r],\ v + T[i-1][r-p]\big) \text{ si } p \le r, \qquad T[i][r] = T[i-1][r] \text{ sinon},

avec T[0][r]=0T[0][r] = 0. Réponse : T[n][c]T[n][c].

def sac_memo(objets, i, r, memo):
    if (i, r) in memo:              # test en tête
        return memo[(i, r)]
    if i == 0:
        s = 0
    else:
        v, p = objets[i - 1]
        s = sac_memo(objets, i - 1, r, memo)
        if p <= r:                  # cas interdit exclu
            t = v + sac_memo(objets, i - 1, r - p, memo)
            if t > s:
                s = t
    memo[(i, r)] = s                # rangé avant le return
    return s

def sac_table(objets, c):
    n = len(objets)
    T = [[0] * (c + 1) for i in range(n + 1)]
    for i in range(1, n + 1):
        v, p = objets[i - 1]
        for r in range(c + 1):
            T[i][r] = T[i - 1][r]
            if p <= r and v + T[i - 1][r - p] > T[i][r]:
                T[i][r] = v + T[i - 1][r - p]
    return T

def sac_choix(objets, c, T):
    n = len(objets)
    pris = [False] * n
    r = c
    i = n
    while i > 0:                    # remontée
        if T[i][r] != T[i - 1][r]:  # objet i - 1 pris
            pris[i - 1] = True
            r = r - objets[i - 1][1]
        i = i - 1
    choix = []
    for j in range(n):
        if pris[j]:
            choix.append(j)
    return choix

La remontée coûte O(n)O(n) : on marque les objets pris, puis on les relit dans l'ordre. Un ajout en tête, pris = [i - 1] + pris, recopierait la liste à chaque fois.

Appel : sac_memo(objets, len(objets), c, {}), avec un dictionnaire neuf. Sur les six lots du camion et 15 quintaux : 132, lots 1, 2 et 5 ; le glouton du rapport ne donne que 117.

Une méthode du chapitre Extrait gratuit

Un exercice corrigé Extrait gratuit

Dans le chapitre complet

Le cours détaillé, toute la fiche « L'essentiel », 19 méthodes, 21 pièges corrigés, la carte mentale, 36 exercices corrigés, 4 problèmes de concours, les fiches à mémoriser et l'assistant qui répond à tes questions. Ce chapitre est offert : il s’ouvre en entier avec un compte d’essai gratuit.

Ouvrir l'essai gratuitVoir les offres

Plan du cours

Les méthodes du chapitre

Chaque méthode est une fiche : quand l'utiliser, le rappel de cours, les étapes, la rédaction attendue sur la copie, un exemple traité, les erreurs à éviter.

  1. Reconnaître un problème de programmation dynamique
  2. Définir le sous-problème par une phrase exacte
  3. Établir la récurrence, les cas de base et les cas interdits
  4. Lire une récurrence ou un programme donnés par un sujet
  5. Écrire la version récursive naïve et compter ses appels
  6. Mémoïser avec un dictionnaire
  7. Écrire la version ascendante
  8. Choisir l'ordre de remplissage à partir des dépendances
  9. Reconstruire une solution optimale
  10. Justifier la complexité en temps et en mémoire
  11. Prouver la correction et la terminaison
  12. Comparer glouton et programmation dynamique sur un même problème
  13. Résoudre le sac à dos binaire et la partition équilibrée
  14. Rendre la monnaie et découper une barre
  15. Calculer une plus longue sous-suite commune
  16. Calculer une distance d'édition et une suite d'opérations
  17. Trouver un chemin de coût minimal dans une grille ou une matrice
  18. Résoudre un problème de tâches ou d'intervalles
  19. Calculer toutes les distances d'un graphe par Floyd-Warshall

Les exercices corrigés

Chaque exercice a ses indications, sa correction rédigée et ce qu'il faut en retenir.

Application directe (15)

Entraînement (16)

Approfondissement (5)

Les problèmes de concours

Des extraits de sujets réels, corrigés en entier, avec la durée conseillée et les méthodes mobilisées.

Questions fréquentes

Quelle est la différence entre un algorithme glouton et la programmation dynamique ?

Le glouton fait un seul choix à chaque étape, fixé par un critère, sans jamais le remettre en cause : il n'est exact que si son choix figure toujours dans une solution optimale. La programmation dynamique compare tous les choix et garde le meilleur : elle est exacte dès que la sous-structure optimale est vraie. Pour charger un camion de 15 quintaux, le glouton par rapport valeur sur poids donne 117 alors que l'optimum vaut 132 ; pour rendre 6 avec les pièces 1, 3 et 4, le glouton rend 4+1+14 + 1 + 1, trois pièces, alors que 3+33 + 3 en fait deux. Les deux méthodes reposent sur la sous-structure optimale ; le glouton exige en plus que son choix soit toujours le bon.

Mémoïsation ou version ascendante : laquelle choisir ?

Les deux donnent le même résultat et ont la même complexité dans le pire cas. La version descendante garde la fonction récursive : elle range chaque réponse dans un dictionnaire, passé en argument et créé vide à chaque appel principal, et le consulte avant de calculer ; elle ne calcule que les sous-problèmes rencontrés, mais Python limite la profondeur des appels à 1000 par défaut. La version ascendante remplit un tableau des plus petits sous-problèmes aux plus grands, dans un ordre qui respecte les dépendances : elle calcule toutes les cases, sans récursion ni limite de pile, et la reconstruction s'y lit directement.

Quelle est la complexité du sac à dos en programmation dynamique ?

Avec nn objets et une charge cc, il y a (n+1)(c+1)(n + 1)(c + 1) sous-problèmes, chacun calculé en temps constant à partir de deux cases : O(nc)O(nc) en temps et en mémoire, pour les deux versions. Comme une ligne ne dépend que de la précédente, deux lignes suffisent pour la valeur optimale, en O(c)O(c) de mémoire, mais la reconstruction est perdue. Ce coût dépend de la valeur de cc, pas de sa longueur d'écriture : la même charge comptée en kilogrammes au lieu de quintaux passe de 15 à 1500 colonnes. Au programme marocain, le meilleur cas de la version mémoïsée, tous les objets trop lourds, fait n+1n + 1 appels, en Θ(n)\Theta(n).

Comment reconstruire une solution optimale à partir du tableau ?

Le tableau donne la valeur optimale, pas les objets. On remonte depuis la case T[n][c] : si T[i][r] diffère de T[i - 1][r], l'objet i−1i - 1 a été pris, on le marque et l'on continue avec la charge r−pr - p ; sinon, on continue avec la même charge. La remontée coûte O(n)O(n) et ne change pas le coût total. En cas d'égalité entre les deux choix, la règle de remontée détermine la solution trouvée : un sujet qui donne un exemple impose souvent la sienne. On peut aussi ranger le choix fait pour chaque case dans un second tableau.

Comment calculer une plus longue sous-suite commune ou une distance d'édition ?

Les deux portent sur les préfixes et regardent les derniers éléments. Pour la plus longue sous-suite commune, T[i][j] vaut T[i - 1][j - 1] + 1 si les derniers éléments sont égaux, sinon le plus grand de T[i - 1][j] et T[i][j - 1], avec des zéros sur les bords. Pour la distance d'édition, D[i][j]D[i][j] est le minimum de D[i−1][j−1]+δD[i - 1][j - 1] + \delta, D[i−1][j]+1D[i - 1][j] + 1 et D[i][j−1]+1D[i][j - 1] + 1, où δ\delta vaut 0 si les deux dernières lettres sont égales et 1 sinon, avec D[i][0]=iD[i][0] = i et D[0][j]=jD[0][j] = j. Les deux tableaux coûtent O(nm)O(nm) en temps et en mémoire, la remontée O(n+m)O(n + m). De MAJHOUL à MEDJOUL, la distance vaut 3.

Floyd-Warshall est-il au programme de MP et PSI ?

Dans ce chapitre, il est traité pour les classes françaises : MP, PSI, PC et PT. Au Maroc, il vient avec les graphes. L'algorithme donne les distances entre toutes les paires de sommets : dk(i,j)d_k(i, j) est la longueur d'un plus court chemin de ii à jj dont les sommets intermédiaires sont parmi 0,…,k−10, \ldots, k - 1, et dk+1(i,j)=min⁡(dk(i,j),dk(i,k)+dk(k,j))d_{k+1}(i, j) = \min(d_k(i, j), d_k(i, k) + d_k(k, j)), avec d0d_0 égale à la matrice des poids. Une seule matrice suffit ; le coût est O(n3)O(n^3) en temps et O(n2)O(n^2) en mémoire. Il reste juste avec des poids négatifs, sans cycle de poids négatif ; un tel cycle rend négative une case de la diagonale.

Comment y accéder

Ce chapitre fait partie des chapitres offerts : il s'ouvre en entier avec un compte d'essai gratuit pour les filières dont il est l'un des premiers chapitres. Créer un compte d'essai ou se connecter. Pour l'accès complet et les tarifs, vois les offres.

Toute ta prépa au même endroit

Pour chaque chapitre : le cours, l'essentiel, les méthodes, les pièges, les exercices corrigés, les problèmes de concours, et un assistant qui répond à tes questions. Programmes du Maroc et de France. Avec la plateforme vidéo YOUNESS SCHOOL offerte, dès 600 DH (60 $) par mois, sans engagement.

Prépas scientifiques : voir l'offrePrépas ECS et ECT : voir l'offreTu hésites encore ? Essaie gratuitement Prepa Help.

← Dictionnaires et principes du hachage