La programation dynamique François Lemieux UQAC François Lemieux (UQAC) La programation dynamique 1 / 51 La programmation dynamique 1 Le compromis espace/temps 2 Principes de la programmation dynamique 3 Exemple 1: Calcul des nombres de Fibonacci 4 Exemple 2: Concaténation de nombres premiers 5 Exemple 3: Multiplication chaı̂née de matrices 6 Exemple 4: Plus court chemin dans un graphe (Floyd) 7 Exemple 5: Sous-chaı̂ne commune de longueur maximale 8 Exemple 6: Disposer des mots en paragraphe François Lemieux (UQAC) La programation dynamique 2 / 51 Le compromis espace-temps Une situation récurrente en informatique est que la diminution du temps d’exécution d’un algorithme se fait souvent au prix d’une augmentation de l’espace mémoire. Exemple: Une fonction f : N → N peut être remplacer par une table des valeurs. Similairement, diminuer l’utilisation de l’espace mémoire peut être fait si on accepte d’augmenter le temps d’exécution. Exemple. Lecture et écriture dans un fichier compressé. François Lemieux (UQAC) La programation dynamique 3 / 51 Principes de la programmation dynamique On évite de refaire certains calculs en les mémorisant dans une table. Cette méthode est souvent utilisée lorsque l’on ne peut pas utiliser un algorithme diviser-pour régner pour une des raisons suivantes: 1 Un algorithme diviser-pour-régner se révèle inefficace parce qu’il refait souvent les mêmes appels récursifs 2 Un algorithme diviser-pour-régner serait efficace si seulement on pouvait “deviner” comment diviser le problème Nous allons voir deux exemples qui illustrent ces situations. François Lemieux (UQAC) La programation dynamique 4 / 51 Exemple 1: Nombres de Fibonacci Algorithme diviser-pour-régner (approche descendante): fib1(n) si (n<2) retourner n sinon retourner fib1(n-1) + fib1(n-2) √ Le temps est Θ(φn ) où φ = 1+2 5 Comme il n’y a que n paramètres possibles, alors il y a une multitude d’appels identiques. François Lemieux (UQAC) La programation dynamique 5 / 51 fib(5) fib(4) fib(3) fib(2) fib(2) fib(1) fib(0) fib(1) fib(0) fib(3) fib(1) fib(1) fib(2) fib(0) François Lemieux (UQAC) La programation dynamique fib(1) 6 / 51 Meilleure solution On utilise une table T [1 · · · n] initialisé à 0 Après le premier appel à fib(k) on met le résultat dans T [k] Avant chaque appel à fib(k) on regarde dans T [k] pour voir si le résultat a déjà été calculé. François Lemieux (UQAC) La programation dynamique 7 / 51 Approche descendante (fonctions à mémoire) L’approche descendante est la façon récursive d’implémenter un algorithme de programmation dynamique. Une fonction récursive qui utilise une table pour éviter de répéter des calculs est appelée fonction à mémoire. fib2(n) si T[n]>0 alors retourner T[n] si n<2 alors T[n]=n sinon T[n] = fib2(n-1) + fib2(n-2) retourner T[n] Le temps est Θ(n) François Lemieux (UQAC) La programation dynamique 8 / 51 fib(5) fib(4) fib(3) fib(2) fib(2) fib(1) fib(0) fib(1) fib(0) fib(3) fib(1) fib(1) fib(2) fib(0) fib(1) Les sous-arbres en rouges correspondent à des calculs qui ont déjà été effectués. François Lemieux (UQAC) La programation dynamique 9 / 51 fib(5) fib(4) fib(3) fib(2) fib(2) fib(1) fib(0) fib(3) fib(1) Les noeuds internes correspondent à des appels récursifs distincts, ce qui garantie un temps linéaire. François Lemieux (UQAC) La programation dynamique 10 / 51 Approche ascendante Il s’agit d’une méthode itérative où on remplit la table de façon ascendante: On commence par les entrées correspondant aux sous-problèmes de petite taille. fib2(n) T[0]=0 T[1]=1 pour i=2 à n faire T[i] = T[i-1] + T[i-2] Le temps est Θ(n) Remarque: Dans ce cas ci, le tableau peut être remplacé par deux variables entières. François Lemieux (UQAC) La programation dynamique 11 / 51 Exemple 2: Concaténation de nombres premiers Supposons que nous disposions d’un algorithme Premier(w ) permettant de déterminer si une séquence de bits w représente un nombre premier: 1 si w est premier Premier(w ) = 0 sinon François Lemieux (UQAC) La programation dynamique 12 / 51 Description du problème Entrée: Une séquence de n bits w = b1 b2 · · · bn Sortie: Vrai si on peut écrire w sous la forme w = w1 w2 · · · wm (m > 0) tel que wi est l’encodage binaire d’un nombre premier, faux sinon. Exemple: La séquence w = 1110110 est la concaténation de 11 (3), 101 (5) et 10 (2). Remarquez que cette solution n’est pas unique puisqu’on peut aussi décomposer w en 11101 (29) et 10 (2). François Lemieux (UQAC) La programation dynamique 13 / 51 Approche diviser-pour-régner Sequence(w) Si (Premier(w)=1) alors retourner vrai Si (n=1) retourner faux Trouver une décomposition w=uv Si Sequence(u)=vrai et Sequence(v)=vrai alors retourner vrai Sinon retourner faux Si on pouvait trouver efficacement une telle décomposition alors l’algorithme Sequence serait aussi efficace. François Lemieux (UQAC) La programation dynamique 14 / 51 Remarque Une idée serait de choisir u comme le plus petit préfixe de w tel que Premier(u) = 1. Si un tel préfixe n’existe pas alors on retourne faux. Malheureusement, cette idée ne fonctionne pas. Exemple: Si w = 10110001 alors 10 (2) est le plus petit préfixe de w qui soit premier. Cependant, la seule décomposition possible de w est 101 (5) et 10001 (17). François Lemieux (UQAC) La programation dynamique 15 / 51 Essayer toutes les décompositions possibles Sequence(w) Si (Premier(w)=1) alors retourner vrai Si (n=1) retourner faux Pour i=1 à n-1 faire u = i premiers bits de w v = (n-i) derniers bits de w Si Sequence(u)=vrai et Sequence(v)=vrai alors retourner vrai Retourner faux T (n) = n−1 X [T (i) + T (n − i)] + Θ(1) i=1 François Lemieux (UQAC) La programation dynamique 16 / 51 Pn−1 T (n) = i=1 [T (i) + T (n − i)] + Θ(1) P = 2 n−1 i=1 T (i) + Θ(1) > 2T (n − 1) On a donc que T (n) > 2n T (0) et on conclu que T (n) ∈ Ω(2n ) François Lemieux (UQAC) La programation dynamique 17 / 51 Fonction à mémoire On utilise un tableau T dont chaque case peut être vide ou contenir vrai ou faux. Initialement toutes les cases de T sont vides. Les indices de ce tableau sont les segments de w (c’est-à-dire les séquences de la formes bi · · · bj pour i ≤ j) On met dans ce tableau le résultats des appels récursifs. François Lemieux (UQAC) La programation dynamique 18 / 51 Sequence(w) Si (T(w) est non vide) alors retourner T(w) T(w)=faux Si (Premier(w)=1) alors T(w)=vrai Si (n=1) alors T(w)=faux Pour i=1 à n-1 faire u = i premiers bits de w v = (n-i) derniers bits de w Si Sequence(u)=vrai et Sequence(v)=vrai alors T(w)=vrai Retourner T(w) François Lemieux (UQAC) La programation dynamique 19 / 51 abcde a ab bcde b cde c de cd d e c bc abc cde de bcd de abcd e e e d Développement partiel de l’arbre d’exécution pour w = abcde François Lemieux (UQAC) La programation dynamique 20 / 51 Analyse Il y a Θ(n2 ) segments distincts. L’arbre d’exécution contient donc Θ(n2 ) noeuds internes. Chaque noeud interne possède moins de 2n enfants. Le nombre de feuilles est donc O(n3 ) T (n) ∈ O(n3 ). François Lemieux (UQAC) La programation dynamique 21 / 51 Implémentation du tableau Entrée: w = b1 b2 · · · bn On utilise un tableau Tn×n initialisé de la façon suivante: vrai si Premier(bi · · · bj ) = 1 T [i, j] = faux sinon Si on dénote par S l’ensemble des séquences de bits qui sont des concaténations de nombres premiers alors, à la fin de l’algorithme, le tableau sera: vrai si bi · · · bj ∈ S T [i, j] = faux sinon Temps pour l’initialisation: Θ(tn2 ) où t est le temps d’exécution de l’algorithme Premier. François Lemieux (UQAC) La programation dynamique 22 / 51 Programmation dynamique Sequence(w) pour longueur=2 à n faire pour debut=1 à n-longueur+1 faire fin = debut+longueur-1 pour coupure=debut à fin-1 faire si T[debut,coupure]=vrai et T[coupure+1,fin]=vrai alors T[debut,fin]=vrai Retourner T[1,n] T (n) ∈ Θ(n3 ) François Lemieux (UQAC) La programation dynamique 23 / 51 Exemple Si w = 10101, alors T sera initialisé de la façon suivante: 1 2 3 4 5 1 F - 2 V F - 3 V F F - 4 F V V F - 5 F V V F F Les séquences de longueur 1 correspondent aux entrées de la diagonale principale, les séquences de longueur 2 correspondent aux entrées de la seconde diagonale, etc. François Lemieux (UQAC) La programation dynamique 24 / 51 Exemple (suite) À la fin de l’algorithme le tableau sera: 1 2 3 4 5 1 F - 2 V F - 3 V F F - 4 V V V F - 5 V V V F F Remarque: La valeur d’une entrée ne dépend que des entrées précédentes sur la même ligne et des entrées suivante sur la même colonne. Par exemple, on met vrai dans T [1, 4] car T [1, 2] et T [3, 4] sont tous les deux vrais. François Lemieux (UQAC) La programation dynamique 25 / 51 Exemple 3: Multiplication chaı̂née de matrices On veut calculer le produit matriciel: M = M1 M2 · · · Mn Fait: Multiplier une matrice p × q par une matrice q × r en utilisant la méthode standard nécessite pqr produits scalaires. Exemple: A : 13 × 5 B : 5 × 89 C : 89 × 3 D : 3 × 34 On veut calculer ABCD. François Lemieux (UQAC) La programation dynamique 26 / 51 Le nombre de produits scalaires est donné dans le tableau suivant: ((AB)C )D 10582 (AB)(CD) 54201 (A(BC ))D 2856 A((BC )D) 4055 A(B(CD)) 26418 Question: Comment trouver la meilleure parenthétisation? François Lemieux (UQAC) La programation dynamique 27 / 51 Essayer toutes les possibilités Soit T (n) le nombre de façons de parenthétiser M1 M2 · · · Mn M = (M1 · · · Mi ) (Mi+1 · · · Mn ) {z } | {z } | T (i) façons T (n − i) façons T (n) = n−1 P i=1 T (i)T (n − i) (nombre de Catalan) T (2) = 1, T (3) = 2, T (4) = 5, T (5) = 14, T (10) = 4862, T (15) = 2674440 Fait: T (n) ∈ Ω(4n /n2 ) François Lemieux (UQAC) La programation dynamique 28 / 51 Solution par programmation dynamique Soit Mi : matrice de dimension di−1 × di Soit mi,j : nombre minimal de produits scalaires nécessaires pour évaluer Mi · · · Mj Supposons que nous sachions que la meilleure solution implique de placer des parenthèses de la façon suivante: (Mi · · · Mk )(Mk+1 · · · Mj ) François Lemieux (UQAC) La programation dynamique 29 / 51 1 Mi · · · Mk est une matrice di−1 × dk et nécessite mi,k produits scalaires 2 Mk+1 · · · Mj est une matrice dk × dj et nécessite mk+1,j produits scalaires 3 Le nombre total de produits scalaires est mi,k + mk+1,j + di−1 dk dj On doit trouver la position k qui minimise mi,j : mi,j = ( François Lemieux (UQAC) min {mi,k + mk+1,j + di−1 dk dj } si i < j i ≤k <j 0 La programation dynamique si i = j 30 / 51 Exemple Par exemple, pour calculer X = m2,5 nous avons besoin de connaı̂tre A : (m2,2 et m3,5 ), B : (m2,3 et m4,5 ) et C : (m2,4 et m5,5 ) 1 2 3 4 5 A B C X 6 1 2 3 A 4 B 5 C 6 d=5 d=4 d=3 d=2 d=1 d=0 m2,5 = min{ m2,2 + m3,5 + d1 d2 d5 , m2,3 + m4,5 + d1 d3 d5 , m2,4 + m5,5 + d1 d4 d5 } François Lemieux (UQAC) La programation dynamique 31 / 51 Fait 1: Pour calculer une diagonale, on a seulement besoin de connaı̂tre les diagonales précédentes. Fait 2: La diagonale d contient les éléments mi,j tels que d = j − i Algorithme: 1 Tous les éléments de la diagonale i = 0 sont mis à 0 2 i =i +1 3 Calculer toutes les entrées de la diagonale i 4 Si i < n aller à 2. François Lemieux (UQAC) La programation dynamique 32 / 51 Exemple Considérons n = 4 matrices dont les dimensions sont données par: d0 = 13, d1 = 5, d2 = 89, d3 = 3, d4 = 34 Calcul de la diagonale d = 1 m1,2 = 13 × 5 × 89 = 5785 m2,3 = 5 × 89 × 3 = 1335 m3,4 = 89 × 3 × 34 = 9078 François Lemieux (UQAC) La programation dynamique 33 / 51 Calcul de la diagonale d = 2 m1,3 = min{m1,1 + m2,3 + 13 × 5 × 3, m1,2 + m3,3 + 13 × 89 × 3} = min{1530, 9256} = 1530 m2,4 = min{m2,2 + m3,4 + 5 × 89 × 34, m2,3 + m4,4 + 5 × 3 × 34} = min{24208, 1845} = 1845 François Lemieux (UQAC) La programation dynamique 34 / 51 Calcul de la diagonale d = 3 m1,4 = min{m1,1 + m2,4 + 13 × 5 × 34, m1,2 + m3,4 + 13 × 89 × 34, m1,2 + m4,4 + 13 × 3 × 34} = min{4055, 54201, 2856} = 2856 1 2 3 4 François Lemieux (UQAC) 1 0 2 5785 0 3 1530 1335 0 4 2856 1845 9078 0 La programation dynamique 35 / 51 Analyse Pour 0 ≤ d ≤ n − 1 il y a n − d éléments sur la diagonale d Pour chaque élément, on doit considérer d possibilités. Le temps d’exécution est: T (n) = n−1 P = n = = (n − d)d d=1 n−1 P d− d=1 n2 (n−1) 2 n3 −n 6 − n−1 P d2 d=1 n(n−1)(2n−1) 6 Donc T (n) ∈ θ(n3 ) François Lemieux (UQAC) La programation dynamique 36 / 51 Exemple 4: Plus court chemin dans un graphe Entrée: Un graphe dirigé G = hN, Ai où chaque arc possède une longueur non négative. Problème: Trouver la longueur du plus court chemin entre tous les noeuds. On suppose N = {1, 2, ..., n} On suppose aussi que G est donné sous forme d’une matrice L[1...n, 1...n]. François Lemieux (UQAC) La programation dynamique 37 / 51 Algorithme de Floyd Cet algorithme construit une matrice D qui donne la longueur du plus court chemin entre chaque pair de noeud. Idée: Initialise D à L Après l’itération k, D donne la longueur du plus court chemin lorsque l’on utilise que les noeuds dans {1, 2, ..., k} comme noeuds intermédiaires. Définition: Dk est la matrice D après l’itération k (D0 = L). François Lemieux (UQAC) La programation dynamique 38 / 51 Dk [i, j] = min {Dk−1 [i, j], Dk−1 [i, k] + Dk−1 [k, j]} M Dk-1[i,j] i j M Dk-1[i,k] M Dk-1[k,j] k François Lemieux (UQAC) La programation dynamique 39 / 51 fonction Floyd(L[1...n,1...n]) D=L pour k=1 à n faire pour i=1 à n faire pour j=1 à n faire D[i,j] = min{D[i,j], D[i,k]+D[k,j]} retourner D Temps dans Θ(n3 ) François Lemieux (UQAC) La programation dynamique 40 / 51 15 4 1 30 5 5 50 5 15 3 2 15 0 5 ∞ ∞ 50 0 15 5 D0 = L = 30 ∞ 0 15 15 ∞ 5 0 François Lemieux (UQAC) 0 50 D1 = 30 15 0 45 D3 = 30 15 François Lemieux (UQAC) La programation dynamique 5 0 35 20 41 / 51 ∞ ∞ 0 5 50 0 15 5 D2 = 30 35 0 15 15 20 5 0 5 20 10 0 20 0 15 5 D4 = 30 35 0 15 20 5 0 15 La programation dynamique 5 0 35 20 20 15 0 5 15 10 0 5 10 5 15 0 10 5 15 0 42 / 51 Sous-chaı̂ne commune de longueur maximale Entrée Deux chaı̂nes de caractères x = x1 , x2 · · · xm et y = y1 y2 · · · yn . Problème Trouver une chaı̂ne z = z1 z2 · · · zk de longueur maximale telle que z est une sous-chaı̂ne de x et y . Exemple: Si x = abcbdab et y = bdcaba alors bca est une sous-chaı̂ne commune tandis que bcba est une sous-chaı̂ne commune de longueur maximale. François Lemieux (UQAC) La programation dynamique 43 / 51 Définition: MaxCom(x, y ) est l’ensemble de toutes les sous-chaı̂nes communes de longueur maximale entre x et y . Observation: Soit x = x1 · · · xm et y = y1 · · · yn , deux chaı̂nes et soit z = z1 · · · zk ∈ MaxCom(x, y ). 1 Si xm = yn alors 1 2 2 3 zk = xm = yn et z1 · · · zk−1 ∈ MaxCom(x1 · · · xm−1 , y1 · · · yn−1 ) Si xm 6= ym et zk 6= xm alors z ∈ MaxCom(x1 · · · xm−1 , y ) Si xm 6= ym et zk 6= yn alors z ∈ MaxCom(x, y1 · · · yn−1 ) François Lemieux (UQAC) La programation dynamique 44 / 51 Définition: Soit Ci,j la longueur de la sous-chaı̂ne commune de longueur maximale entre x1 · · · xi et y1 · · · yj . On a: si i = 0 ou j = 0 0 C +1 si i, j > 0 et xi = yj Ci,j = i−1,j−1 max{Ci.j−1 , Ci−1,j } si i, j > 0 et xi 6= yj François Lemieux (UQAC) La programation dynamique 45 / 51 SousChaineMax(x1 · · · xm , y1 · · · yn ) pour i=1 à m faire Ci,0 = 0 pour j=1 à n faire C0,j = 0 pour i= 1 à m faire pour j=1 à n faire si (xi = yi ) alors Ci,j = Ci−1,j−1 + 1 sinon si (Ci−1,j ≥ Ci,j−1 ) alors Ci,j = Ci−1,j sinon Ci,j = Ci,j−1 François Lemieux (UQAC) La programation dynamique 46 / 51 Disposer des mots en paragraphe On désire séparer une séquence de mots m1 , . . . , mn en une série de lignes constituant un paragraphe. Notre objectif est d’éviter autant que possible la perte d’espace à la fin de chaque ligne à l’exclusion de la dernière. Pour simplifier, on suppose que chaque mot doit être écrit sur une seule ligne et que tous les caractères (incluant les caractères d’espacement) ont la même largeur. François Lemieux (UQAC) La programation dynamique 47 / 51 Disposer des mots en paragraphe L’entrée est composée de n entiers w1 , w2 , . . . wn représentant le nombre de caractères dans chaque mot ainsi que d’un entier L représentant la largeur du paragraphe. Pour simplifier les calculs, on suppose que les mots se terminent tous par un espace supplémentaire. Par exemple, si mi est le mot “ou” alors wi = 3. On suppose aussi que L inclu un espace supplémentaire (e.g. pour des lignes de 80 caractères on a L = 81). François Lemieux (UQAC) La programation dynamique 48 / 51 Disposer des mots en paragraphe La principale contrainte est donc que si les mots i à j sont placés sur la même ligne alors on doit avoir wi + wi+1 + · · · + wj ≤ L. Dans ce cas la perte d’espace est X = L − (wi + wi+1 + · · · + wj ) On défini la pénalité d’une ligne comme X 3 (on pénalise ainsi d’avantage une ligne ayant une grande perte d’espace que plusieurs lignes ayant une petite perte d’espace). On désire minimiser la pénalité du paragraphe, c’est-à-dire la somme des pénalités de chaque ligne. François Lemieux (UQAC) La programation dynamique 49 / 51 Disposer des mots en paragraphe Nous utiliserons deux tableaux de taille n: PEN: Pour chaque indice i, PEN[i] contient la pénalité minimale du paragraphe contenant uniquement les mots wi , . . . , wn . FIN: Pour chaque indice i, FIN[i] contient l’indice du dernier mot de la première ligne dans la solution optimal du problème restreint aux mots wi , . . . , wn . Remarque: Si FIN[i]=k alors PEN[i]=(L − (wi + · · · + wk ))3 + PEN(k+1) François Lemieux (UQAC) La programation dynamique 50 / 51 Disposer des mots en paragraphe Paragraphe(wi , . . . , wn , L) Si PEN[i] est non vide alors return PEN[i] Si (wi + · · · + wn ≤ L) alors PEN[i]=0 FIN[i]=n return 0 M=∞ kmin=i k=i tantque (wi + · · · + wk ≤ L) faire P= Paragraphe(wk+1 , . . . , wn , L) + (L − (wi + · · · + wk ))3 Si (P < M) alors M=P kmin=k k++ FIN[i]=kmin return PEN[i]=M François Lemieux (UQAC) La programation dynamique 51 / 51