Vous êtes sur la page 1sur 9

2 EXEMPLE DE LA FONCTION FIBONACCI

La programmation dynamique met en jeu les valeurs associées aux solutions, qui permettent de sélec-
tionner les solutions optimales, et les solutions elles-mêmes qui sont de différente nature (et construites
une fois seulement qu’elles ont été identifiées comme optimales, car de valeur optimale).
Complément recherche : Il y a ainsi deux passes, une pour calculer les valeurs à utiliser pour le critère d’optimalité, et une pour
calculer la solution qui correspond à la valeur optimale calculée lors de la première passe.
quelques notes sur la programmation dynamique
Si les solutions recherchées sont précisément les valeurs mises en jeu pour le critère d’optimalité, alors
la dernière étape devient inutile (i.e. une seule passe suffit).
Pascale Le Gall
Nous verrons que face à problème qui se décompose en sous-problèmes, la programmation dynamique
est à privilégier si le problème fait apparaître :
16 octobre 2018
Sous-problèmes optimaux. Parmi les possibilités de décomposer le problème en sous-problèmes,
certaines décompositions sont à privilégier, car elles mettent en jeu des sous-problèmes optimaux
au regard du critère d’optimalité ;
1 Introduction Sous-problèmes avec recouvrement. Les décompositions font apparaître des sous-problèmes qui
partagent eux-mêmes des sous-problèmes, autrement dit, des sous-sous-problèmes du problème
La programmation dynamique et la programmation par mémoïation partagent la même idée simple : original, en commun. Il convient de ne les traiter (au sens résoudre) qu’une seule fois. De plus,
ne pas recalculer deux fois la même instance d’un (sous-)problème. pour que la complexité résultante soit raisonnable (polynomiale), il faut que l’espace des sous-
problèmes ne soit pas trop grand (polynomial).

1.1 A propos de la mémoïsation


La mémoïsation consiste à stocker tous les résultats intermédiaires dans une table (tableau, ou dic- 2 Exemple de la fonction Fibonacci
tionnaire) au fur et à mesure que des sous-problèmes sont considérés, notamment au travers des appels La définition mathématique de la fonction Fibonacci est définie sur les entiers naturels. Elle est bien
récursifs aux sous-problèmes, et à consulter la table avant de résoudre un sous-problème pour vérifier connue 1 :
si le sous-problème a déjà été rencontré. Si la table ne contient pas déjà la solution du sous-problème,
alors on résoud le sous-problème (selon une approche récursive en général).
F0 = 0
L’inconvénient générique de l’approche par mémoïsation est de sauvegarder par défaut toutes les F1 = 1
solutions des sous-problèmes rencontrés, sans chercher à savoir si les solutions sauvegardées seront de
nouveau utiles. ∀n ≥ 2, Fn = Fn−2 + Fn−1

La programmation dynamique ajoute une dimension, à savoir que la solution n’est pas directement 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
obtenue en consultant la table de mémoïsation, mais sera en recherchant parmi plusieurs familles de
De cette définition, on peut en déduire un algorithme récursif évident :
sous-problèmes.
Algorithme 1 : FIBONACCI (n)

1.2 A propos de la programmation dynamique 1 if n ≤ 1 then


2 return n
La programmation dynamique est une approche générale pour concevoir des algorithmes pour des 3 return FIBONACCI(n − 2) +FIBONACCI(n − 1)
problèmes d’optimisation.
Quel est la complexité de cet algorithme ? Notons T (n) la complexité associées au calcul de F IBON ACCI(n)
Un problème d’optimisation consiste à rechercher parmi un ensemble de solutions d’un problème, celles
qui optimisent un critère donné.
T (n) ≥ Fn = Fn−1 + Fn−2
La résolution d’un problème d’optimisation suit les étapes suivantes :
Puisque Fn ≥ Fn−1 ≥ Fn−2 ≥ Fn−3 ≥ ...
(i) caractérisation de la structure (en tant que structure de données) d’une solution optimale ;
n
(ii) définition récursive de la valeur de la solution optimale au regard du critère d’optimisation ; Fn ≥ 2Fn−2 ≥ 2(2Fn−4 ) ≥ 2(2(2Fn−6 )) ≥ ... ≥ 2 2
(iii) calcul ascendant (ou bottom-up) de la valeur de la solution optimale ; 1. elle peut différer par un décalage des cas d’initialisation, mais pas par le schéma récursif.
(iv) construction de la solution du problème

1 2 Pascale Le Gall
2 EXEMPLE DE LA FONCTION FIBONACCI 2 EXEMPLE DE LA FONCTION FIBONACCI


Soit T (n) ≥ ( 2)n ≈ (1.4)n et T (n) croît exponentiellement avec n.
L’utilisation d’un tableau A de valeurs pour mémoriser les calculs intermédiaires F IBON ACCI(i)

Algorithme 2 : FIBONACCI_MEMORY (n)


1 Create Array A[1 : n + 1] ;
2 A[1] ← 0 ;
3 A[2] ← 1 ;
4 for i ← 3 to n + 1 do
5 A[i] ← A[i − 1] + A[i − 2] ;
6 return A[A.length]

La complexité en temps est en Θ(n), mais elle est associée à une complexité en espace linéaire (taille
du tableau A).
La version par mémoïsation consiste à systématiquement consulter la table en aveugle avant de faire
toute calcul d’une nouvelle solution. Une version possible en Python est : Figure 1 – Graphe de dépendances entre les appels de la fonction Fibonacci
memo = {}
à plusieurs reprises (différents chemins de la racine au nœud en question), l’objectif étant de ne calculer
def f i b ( n ) :
if n in memo : qu’une fois chacun des nœuds atteignables.
return memo [ n ]
elif n == 0 :
memo[ 0 ] = 0
return 0
elif n == 1 : Si les solutions des sous-problèmes sont sauvegardées, il suffit de s’y référer à chaque nouvelle demande
memo[ 1 ] = 1 de résolution d’un sous-problème déjà rencontré. On échange ainsi du temps de calcul contre de l’espace
return 1 mémoire. La résolution des sous-problèmes peut se faire selon une approche descendante (top-down)
else : ou ascendante (bottom-up).
memo [ n]= f i b ( n−1)+ f i b ( n−2)
return memo [ n ] Peut-on envisager une meilleure complexité. Remarquons les égalités suivantes :

L’algorithme peut être amélioré, en n’utilisant que deux variables pour mémoriser les valeurs intermé- Fn+1 = 1 × Fn + 1 × Fn−1
diaires.
Fn = 1 × Fn + 0 × Fn−1
Algorithme 3 : FIBONACCI-ITER(n)
Montrons alors :
1 if n ≤ 1 then ! !n
Fn+1 Fn 1 1
2 return n =
Fn Fn−1 1 0
3 else
4 pprev ← 1; Par induction :
5 prev ← 1; — Cas n = 1 : trivial (F0 = 0, F1 = 1, F2 = 1).
6 for i ← 2 to n do
7 f ← prev + pprev; — Cas inductif : n ≥ 2
8 pprev ← prev; ! ! ! !n−1 !
prev←f; Fn+1 Fn Fn Fn−1 1 1 1 1 1 1
9 = . = .
Fn Fn−1 Fn−1 Fn−2 1 0 1 0 1 0
10 return f
! !n
Fn+1 Fn 1 1
La figure 1 illustre toutes les dépendances entre appels de la fonction Fibonacci : en partant de l’idée =
Fn Fn−1 1 0
que le nœud i représente le calcul de Fibonacci de i, tous les nœuds atteignables à partir du nœud
5 participent au calcul de Fibonnaci de 5, et à ce tître doivent être exécutés une fois au moins. Les
nœuds représentent les sous-problèmes qu’il s’agit de résoudre. Ces sous-problèmes pouvant apparaître

3 Pascale Le Gall 4 Pascale Le Gall


3 SOUS-TABLEAU DE SOMME MAXIMALE 3.2 Approche force brute optimisée 3 SOUS-TABLEAU DE SOMME MAXIMALE

!n
1 1 Complexité globale en Θ(n3 )
Le calcul direct de est en Θ(n).
1 0
Algorithme 4 : MAX-SUBARRAY-BRUTE-FORCE(A : array)
Il est possible d’adopter une approche "divide and conquer" pour calculer la fonction puissante an . Data : A an array of n items
( n n Résultat : The subarray such that A[j] + ... + A[k] is maximized, index j and index k
a 2 .a 2 si n est pair
an = n−1 n−1
1 n ← A.length
a 2 .a 2 .a si n est impair
2 max-so-far ← −∞ (ou bien 0)
3 for j ← 1 to n do
La complexité est alors de Θ(log n).
4 for k ← j to n do
5 sum ← 0
6 for i ← j to k do
3 Sous-tableau de somme maximale 7 sum ← sum + A[i]
Dans l’exemple du tableau A suivant : 8 if sum > max-so-far then
9 max-so-far ← sum
10 low ← j
[13, −3, −25, 20, −3, −16, −23, 18, 20, −7, 12, −5, −22, 15, −4, 7]
11 high ← k
A[8..11] est le sous-tableau de somme maximale (sum = 43).
12 return max-so-far, low, high
Par convention, le sous-tableau vide est de somme 0 (ce qui constitue de facto une borne inférieure de
la somme maximale d’un sous-tableau).
Etant donné un tableau A = [a1 , a2 , ...an ] d’entiers positifs ou négatifs, il s’agit de trouver les indices 3.2 Approche force brute optimisée
j et k avec j < k qui maximisent la somme :
En remarquant que si l’on connait sj,k , il est possible de calculer sj,k+1 en temps constant :
k
X
sj,k = aj + aj+1 + ... + ak = ai sj,k+1 = sj,k + A[k + 1]
i=j
il est possible d’améliorer l’algorithme "force brute" pour obtenir un algorithme en Θ(n2 ).
Différentes solutions algorithmiques sont possibles :
Algorithme 5 : MAX-SUBARRAY-BRUTE-FORCE-OPTIM(A : array)
— O(n3 ) : force brute.
Data : A an array of n items
— O(n2 ) : force brute avec optimisation (évidente). Résultat : The subarray such that A[j] + ... + A[k] is maximized, index j and index k
— O(n log(n)) : approche "divide and conquer". 1 n ← A.length
— O(n) : programmation dynamique. 2 max-so-far ← −∞
3 for j ← 1 to n do
4 sum ← 0
3.1 Approche force brute 5 for i ← j to n do
6 sum ← sum + A[i]
Il s’agit de calculer séparément la somme sj,k de tous les sous-tableaux A[j, k] pour les comparer. 7 if sum > max-so-far then
8 max-so-far ← sum
Etude de la complexité
9 low ← j
— Generation of tous les sous-tableaux Θ(n2 ) 10 high ← k
— Calcul de la somme des éléments pour chacun des sous-tableaux : Θ(n).
11 return m, low, high
— Trouver le maximun des sommes calculées Θ(n)

3.3 Version "divide and conquer"


Une approche "divide and conquer" demande d’identifier des sous-problèmes de taille significativement
plus petite, et de s’y ramener. Pour cela, on reformule le problème, pour pouvoir considérer des

5 Pascale Le Gall 6 Pascale Le Gall


3.3 Version "divide and conquer" 3 SOUS-TABLEAU DE SOMME MAXIMALE 3.4 Approche programmation dynamique : version 31 SOUS-TABLEAU DE SOMME MAXIMALE

Finalement, la version "divide and conquer" de MAX-SUBARRAY est donnée par :

Algorithme 7 : MAX-SUBARRAY(A : array, low, high)


1 if high == low then
2 return (low, high, A[low])
3 else
4 mid ← ⌊ low+high
2 ⌋
5 (left-low, left-high, left-sum) ← MAX-SUBARRAY (A, low, mid)
6 (right-low, right-high, right-sum) ← MAX-SUBARRAY (A, mid + 1, high)
7 (cross-low, cross-high, cross-sum) ← MAX-CROSSING-SUBARRAY (A, low, mid, high)
8 if left-sum ≥ right-sum and left-sum ≥ cross-sum then
9 return (left-low, left-high, left-sum)
sous-tableaux comme des tableaux de référence desquels on va chercher des sous-tableaux de somme
10 else if right-sum ≥ left-sum and right-sum ≥ cross-sum then
maximale.
11 return (right-low, right-high, right-sum)
Le problème générique s’exprime désormais en : trouver un sous-tableau de somme maximal à partir 12 else
d’un tableau A[low..high]. Ainsi l’étape de subdivision du problème MAXIMUM-SUBARRAY(A, low, high) 13 return (cross-low, cross-high, cross-sum)
se ramène aux deux-sous problèmes mid = ⌊ low+high2 ⌋ pour une certaine valeur intermédiaire mid.
Etapes clés Analyse de l’algorithme en supposant n impair (par facilité)
— Divide : Division du tableau en deux sous-tableaux de même taille mid = ⌊ low+high
2 ⌋. (
— Conquer : Appels récursifs avec les deux sous-tableaux identifiés : c1 if n = 1
T (n) =
MAXIMUM-SUBARRAY(A, low, mid) et MAXIMUM-SUBARRAY(A, mid + 1, high) 2T ( n2 ) + c2 n else
— Merge Il s’agit de trouver le sous-tableau du tableau initial (A, low, high) de somme maximale Autrement dit, même complexité que le tri par fusion (merge sort) soit Θ(n log n).
qui recouvre partiellement les deux sous-tableaux afin de trouver quelle est la meilleure solution
parmi les 3 candidates (premier sous-tableau du début, second sous-tableau ou sous-tableau
traversant). 3.4 Approche programmation dynamique : version 1
L’algorithme MAX-CROSSING-SUBARRAY est en charge de trouver le sous-tableau maximal
qui recouvre les deux sous-tableaux. On va considérer un tableau m où m[i] va sauvegarder la somme du sous-tableau de A qui termine à
Algorithme 6 : MAX-CROSSING-SUBARRAY(A : array, low, mid , high) l’indice i, et qui est de somme maximale parmi ces tableaux.
Le calcul de m[i] à partir de m[i − 1] dépend du signe de m[i − 1] :
1 left-sum ← −∞
2 sum ← 0 (
3 for i ← mid to low by −1 do m[i − 1] + A[i] si m[i − 1] > 0
m[i] =
4 sum ← sum + A[i] A[i] sinon
5 if sum > left-sum then
6 left-sum ← sum La valeur recherchée est alors la valeur maximale parmi les valeurs du tableau m.
7 max-left ← i
max = max( 0, maxi∈1..N ( m[i] ) )
8 right-sum ← −∞
9 sum ← 0
10 for j ← mid + 1 to high by 1 do
11 sum ← sum + A[j]
12 if sum > right-sum then
13 right-sum ← sum
14 max-right ← j

15 return (max-left, max-right, left-sum + right-sum)

La complexité de MAX-CROSSING-SUBARRAY est en Θ(n).

7 Pascale Le Gall 8 Pascale Le Gall


3.5 Approche programmation dynamique : version 32 SOUS-TABLEAU DE SOMME MAXIMALE 3.6 Calcul des indices de début et de fin des sous-tableaux
3 SOUS-TABLEAU
de somme maximale
DE SOMME MAXIMALE

Algorithme 8 : MAX-SUBARRAY-LINEAR(A : array) On obtient la relation :

1 m[1..n] : new array


2 max-so-far ← A[1] OP T f in(n) = max{OP T f in(n − 1) + A[n], 0}
3 m[1] ← A[1]
4 for i ← 2 to A.length do
OP T (n) = max{OP T (n − 1), OP T f in(n)}
5 if m[i − 1] > 0 then
6 m[i] ← m[i − 1] + A[i]
En d’autres termes, si la solution optimale ne contient pas A[n], alors OP T (n) = OP T (n − 1).
7 else Sinon, elle contient A[n], et la solution est la somme associée au sous-tableau optimal terminant en n,
8 m[i] ← A[i] OP T f in(n).
9 if m[i] > max-so-far then
Algorithme 9 : MAX-SUBARRAY-LINEAR-BIS(A : array)
10 max-so-far ← m[i]
1 opt ← 0
11 return max-so-far
2 opt’ ← 0
La complexité est en Θ(n) 3 for i ← 1 to A.length do
4 opt′ ← max(0, opt′ + A[i])
def max_subarray ( a ) : 5 opt ← max(opt, opt′ )
maxsofar = a [ 0 ]
m = [a[0]] 6 return opt
for i in range ( 1 , len ( a ) ) :
if m[ i −1] > 0 : La complexité est linéaire, en Θ(n).
m = m + [m[ i −1]+a [ i ] ]
else : Remarque : l’implémentation tient compte de ce que la convention qu’un tableau vide est de somme
m = m + [a[ i ]] nulle.
if m[ i ] > maxsofar :
maxsofar = m[ i ]
return maxsofar 3.6 Calcul des indices de début et de fin des sous-tableaux
def max_subarray_bis ( a ) : de somme maximale
maxsofar = a [ 0 ]
cur = a [ 0 ] Jusqu’à présent,on a calculé la valeur associée au critère d’optimalité des solutions (à savoir la somme
for i in range ( 1 , len ( a ) ) :
des éléments du sous-tableau), mais on n’a pas calculé la solution, c’est-à-dire les indices de début et
if c u r > 0 :
cur = cur + a [ i ] de fin des sous-tableaux solutions (i.e. dont la somme des éléments est maximale).
else : Pour cela, il faut, à partir du calcul de la valeur optimale, y plaquer le calcul de la solution du problème
cur = a [ i ]
if c u r > maxsofar :
maxsofar = c u r
return maxsofar

3.5 Approche programmation dynamique : version 2


Considérons OP T (n) la somme maximale parmi tous les sous-tableaux de A[1..n].
Par convention, la somme associée à un sous-tableau vide est 0. Donc, un sous-tableau ne représente
un candidat potentiel pour la recherche de la sous-somme maximale que si la somme associée est
positive.
Considérons OP T f in(n) la somme maximale de tous les sous-tableaux terminant en A[n]. Si A[n] est
dans le sous-tableau donnant lieu à la solution optimale, alors OP T f in(n) = OP T f in(n − 1) + A[n]
(avec éventuellement OP T f in(n − 1) = 0), sinon, OP T f in(n) = 0.

9 Pascale Le Gall 10 Pascale Le Gall


3.6 Calcul des indices de début et de fin des sous-tableaux
3 SOUS-TABLEAU
de somme maximale
DE SOMME MAXIMALE 4 PLUS LONGUE SOUS-SÉQUENCE COMMUNE À DEUX SÉQUENCES

recherché. if c u r > 0 :
cur = cur + a [ i ]
Algorithme 10 : DEBFIN-SUBARRAY-LINEAR(A : array) else :
deb_cur = i
1 m[1..n] : new array cur = a [ i ]
2 max-so-far ← A[1] if c u r > maxsofar :
3 m[1] ← A[1] maxsofar = c u r
4 deb-so-far ← 1 deb = deb_cur
5 fin-so-far ← 1 fin = i
6 cur-deb ← 1 return maxsofar , deb , f i n , a [ deb : ( f i n + 1 ) ] , s u m _ l i s t ( a [ deb : ( f i n + 1 ) ] )
7 for i ← 2 to A.length do
8 if m[i − 1] > 0 then
9 m[i] ← m[i − 1] + A[i]
10 else 4 Plus longue sous-séquence commune à deux sé-
cur-deb ← i
11
12 m[i] ← A[i]
quences
13 if m[i] > max-so-far then Etant donnée une séquence X = x1 , x2 , . . . , xm , Z = z1 , z2 , . . . , zk est une sous-séquence de X s’il
14 max-so-far ← m[i] existe une sous-suite croissante i1 , i2 , . . . ik vérifiant ∀j ∈ [1, k], zj = xij .
15 deb-so-far ← cur-deb Par exemple, A, C, A est une sous-séquence de B, A, A, C, B, A, B.
16 fin-so-far ← i
La proximité entre deux séquences X et Y peut être mesurée par la longueur de la plus longue sous-
17 return deb-so-far,fin-so-far séquence commune (PLSSC) à X et Y .

Les parties en noir sont exactement celles héritées de l’algorithme 8 tandis que les parties bleues sont Une approche force brute pourrait consister à tester toutes les sous-séquences possibles. Comme il y
intercalées pour remonter les informations relatives à la solution optimale, dans le cas présent un a un nombre exponentiel de sous-séquences à considérer, l’algorithme résultant est exponentiel.
couple d’entiers, représentant les indices de début et de fin du sous-tableau solution. Etant donnée une séquence X = x1 , x2 , . . . , xm de longueur m, Xi = x1 , x2 , . . . , xi dénote pour 0 ≤
i ≤ m la séquence préfixe de X de longueur i.
Deux implémentations possibles (la seconde étant plaquée sur le second calcul des valeurs optimales) :
Soit Z = z1 , z2 , . . . zk la PLSSC de X = x1 , x2 , . . . xm et de Y = y1 , y2 , . . . yn . On a alors :
def ind_subarray ( a ) :
maxsofar = a [ 0 ]
— Si xm = yn , alors zk = xm = yn , et Zk−1 est une PLSSC de Xm−1 et de Yn−1 .
deb = 0 — Si xm 6= yn , alors, si zk 6= xm , alors Z est une PLSSC de Xm−1 et de Y .
deb_cur = 0
— Si xm 6= yn , alors, si zk 6= yn , alors Z est une PLSSC de X et de Yn−1 .
fin = 0
m = [a[0]] Ces propriétés s’établissent à partir des observations :
for i in range ( 1 , len ( a ) ) : — Supposons xm = yn . Si zk 6= xm , alors il est possible d’ajouter xm à Z pour obtenir une sous-
if m[ i −1] > 0 :
séquence commune à X et Y (puisque xm termine aussi Y ). C’est en contradiction avec le fait
m= m + [m[ i −1]+a [ i ] ]
else : que Z est une PLSSC à X et à Y . Donc xk = xm et Zk−1 est une séquence de Xm−1 et de
deb_cur = i Yn−1 . Supposons qu’il existe une PLSSC de Xm−1 et de Yn−1 de longueur strictement supérieur
m= m + [ a [ i ] ] à k − 1. Dans ce cas, en ajoutant xm à W , alors on obtiendrait une sous-séquence de X et Y de
if m[ i ] > maxsofar : longueur strictement supérieure à k. Contradiction avec k longueur de Z PLSSC de X et de Y .
maxsofar = m[ i ] Donc Zk−1 est une PLSSC de Xm−1 et de Yn−1 .
deb = deb_cur
Remarque : si xm 6= yn , alors nécessairement zk 6= xm ou zk 6= yn .
fin = i
return maxsofar , deb , f i n , a [ deb : ( f i n + 1 ) ] , s u m _ l i s t ( a [ deb : ( f i n + 1 ) ] ) — Supposons xm 6= yn et zk 6= xm , alors Z est une sous-séquence de Xm−1 et de Y . Et Z est
nécessairement une PLSSC de Xm−1 et Y (sinon Z ne serait pas une PLSSC de X et de Y ).
def ind_subarray_bis ( a ) :
— Supposons xm 6= yn et zk 6= yn , alors Z est une sous-séquence de Xm et de Yn−1 (par symétrie
maxsofar = a [ 0 ]
deb = 0 avec le point précédent).
deb_cur = 0 Ainsi si les deux séquences X et Y terminent par le même élément, alors la recherche d’une PLSSC se
fin = 0 ramène à la recherche d’une PLSSC pour Xm−1 et Yn−1 , sinon la recherche d’une PLSSC se ramène
cur = a [ 0 ] à la recherche de deux PLSSC, pour Xm−1 et Y et pour X et Yn−1 .
for i in range ( 1 , len ( a ) ) :

11 Pascale Le Gall 12 Pascale Le Gall


4 PLUS LONGUE SOUS-SÉQUENCE COMMUNE À DEUX SÉQUENCES 4 PLUS LONGUE SOUS-SÉQUENCE COMMUNE À DEUX SÉQUENCES

Les deux sous-problèmes, la recherche d’une PLSSC pour Xm−1 et Y et pour X et Yn−1 possèdent de X, autrement dit, on laisse tomber le dernier élément de X.
des sous-problèmes communs, notamment la recherche d’une PLSSC de Xm−1 et Yn−1 .
Algorithme 12 : PATH_INDICATIONS(X,Y)
Notons l(i, j) la longueur d’une PLSSC à Xi et Yj . Data : X array of m items, Y array of n items
 Résultat : d an array of dimensions [1..m,1..n] of path indications
 0
 si i = 0 ou j = 0 1 m ← X.length
l(i, j) = l(i − 1, j − 1) + 1 si i > 0, j > 0 et Xi = Yj 2 n ← Y.length
 max(l(i, j − 1), l(i − 1, j))

si i > 0, j > 0 et Xi 6= Yj 3 for i ← 0 to m do
4 c[i, 0] ← 0
En partant de ces relations de récurrence, et en stockant les longueurs dans un tableau à deux dimen-
sions, on peut calculer la longueur d’une PLSSC de deux séquences X et Y 5 for j ← 0 to n do
6 c[0, j] ← 0
Algorithme 11 : LENGTH_PLSSC(X,Y) 7 for i ← 1 to m do
Data : X array of m items, Y array of n items 8 for j ← 1 to n do
Résultat : The length of a longuest sub sequence common to X and Y 9 if X[i] = Y [j] then
1 m ← X.length 10 c[i, j] ← c[i − 1, j − 1] + 1
2 n ← Y.length 11 d[i, j] ← ’z’
3 c : new array of dimensiosn[0..m, 0..n] 12 else if c[i − 1, j] ≥ c[i, j − 1] then
4 for i ← 0 to m do 13 c[i, j] ← c[i − 1, j]
5 c[i, 0] ← 0 14 d[i, j] ← ’x’
6 for j ← 0 to n do 15 else
7 c[0, j] ← 0 16 c[i, j] ← c[i, j − 1]
8 for i ← 1 to m do 17 d[i, j] ←, ’y’
9 for j ← 1 to n do
10 if X[i] = Y [j] then 18 return d
11 c[i, j] ← c[i − 1, j − 1] + 1
12 else if c[i − 1, j] ≥ c[i, j − 1] then Algorithme 13 : PLSSC(X,Y)
13 c[i, j] ← c[i − 1, j] Data : X array of m items, Y array of n items, d path indications for (X,Y )
Résultat : Z the longest subsequence of common elements
14 else
15 c[i, j] ← c[i, j − 1] 1 i ← X.length
2 ← Y.length
16 return c[m, n] 3 for i ← 0 to m do
4 c[i, 0] ← 0
Cet algorithme ci-dessus calcule bien la longueur de la plus longue sous-séquence commune à deux 5 for j ← 0 to n do
séquences X et Y . Mais on n’a pas accès à la sous-séquence commune proprement dite. 6 c[0, j] ← 0
Pour cela, il suffit de remarquer que la longueur augmente lorsque X[i] et Y [j] sont identiques. Lorsque 7 for i ← 1 to m do
c[i − 1, j] ≥ c[i, j − 1], alors le cas [i, j] se ramène au cas [i − 1, j], en laissant tomber la dernière lettre 8 for j ← 1 to n do
9 if X[i] = Y [j] then
10 c[i, j] ← c[i − 1, j − 1] + 1
11 d[i, j] ← ’z’
12 else if c[i − 1, j] ≥ c[i, j − 1] then
13 c[i, j] ← c[i − 1, j]
14 d[i, j] ← ’x’
15 else
16 c[i, j] ← c[i, j − 1]
17 d[i, j] ←, ’y’

18 return d

13 Pascale Le Gall 14 Pascale Le Gall


4 PLUS LONGUE SOUS-SÉQUENCE COMMUNE À DEUX SÉQUENCES 4 PLUS LONGUE SOUS-SÉQUENCE COMMUNE À DEUX SÉQUENCES

En partant de d[n, m] et en suivant les indications fournis par les éléments de d : else :
— si l’élément d[i, j] est ’z’, alors on garde X[i] et on passe à d[i − 1, j − 1] ; print ( x [ i − 1 ] . r j u s t ( 3 ) , end=" " )
for j in range ( 0 ,m+1):
— si l’élément d[i, j] est ’x’, alors on passe à d[i − 1, j] ; if type ( c [ ( i , j ) ] ) == int :
— si l’élément d[i, j] est ’y’, alors on passe à d[i, j − 1]. print ( repr ( c [ ( i , j ) ] ) . r j u s t ( 3 ) , end= " " )
else :
Le calcul de la plus longue sous-séquence commune à X et Y est donné par P LSSC(X, Y, X.length, Y.length, d) print ( c [ ( i , j ) ] . r j u s t ( 3 ) , end= " " )
avec d résultat de P AT H_IN DICAT ION S(X, Y ) print ( )

Algorithme 14 : PLSSC(X,Y,i,j,d) def l e n g t h _ p l s s c _ a f f ( x , y ) :


Data : X array of m items, Y array of n items, i ≤ X.length, j ≤ Y.length, d array of path long , tab , n ,m = l e n g t h _ p l s s c ( x , y )
indications a f f i c h e _ t a b ( x , y , tab , n ,m)
Résultat : a PLSSC for X and Y
def p a t h s _ p l s s c ( x , y ) :
1 if i = 0 or j = 0 then c = {}
2 return [] c [(0 ,0)] = 0
d = {}
3 else if d[i, j] = ’z’ then d[(0 ,0)] = " "
4 return add(X[i],PLSSC(X,Y,i-1,j-1,d)) for i in range ( 0 , len ( x ) ) :
5 else if d[i, j] = ’x’ then c [ ( i +1 ,0)]=0
6 return PLSSC(X,Y,i-1,j,d)) d [ ( i +1 ,0)] = " "
for j in range ( 0 , len ( y ) ) :
7 else c [ ( 0 , j +1)] = 0
8 return PLSSC(X,Y,i,j-1,d)) d [ ( 0 , j +1)] = " "
for i in range ( 0 , len ( x ) ) :
Pour deux séquences X et Y de longueur m et n, l’algorithme PLSSC doit être appelé avec les indices for j in range ( 0 , len ( y ) ) :
m et n, et le tableau des indications de chemin calculé à partir de X et Y . L’algorithme est en O(m+n). if x [ i ] == y [ j ] :
c [ ( i +1, j +1)] = c [ ( i , j ) ] +1
d [ ( i +1, j +1)] = "="
def l e n g t h _ p l s s c ( x , y ) : elif c [ ( i , j +1)] >= c [ ( i +1, j ) ] :
c = {} c [ ( i +1, j +1)] = c [ ( i , j +1)]
c [(0 ,0)] = 0 d [ ( i +1, j +1)] = " | "
for i in range ( 0 , len ( x ) ) : else :
c [ ( i +1 ,0)]=0 c [ ( i +1, j +1)] = c [ ( i +1, j ) ]
for j in range ( 0 , len ( y ) ) : d [ ( i +1, j +1)] = "−"
c [ ( 0 , j +1)] = 0 return d , len ( x ) , len ( y )
for i in range ( 0 , len ( x ) ) :
for j in range ( 0 , len ( y ) ) :
if x [ i ] == y [ j ] :
c [ ( i +1, j +1)] = c [ ( i , j ) ] +1
elif c [ ( i , j +1)] >= c [ ( i +1, j ) ] : def p l s s c ( x , y , d , i , j ) :
c [ ( i +1, j +1)] = c [ ( i , j +1)] if i == 0 or j == 0 :
else : return [ ]
c [ ( i +1, j +1)] = c [ ( i +1, j ) ] elif d [ i , j ] == "=" :
return c [ ( len ( x ) , len ( y ) ) ] , c , len ( x ) , len ( y ) return p l s s c ( x , y , d , i −1, j −1) + [ x [ i − 1 ] ]
elif d [ i , j ] == " | " :
return p l s s c ( x , y , d , i −1, j )
def a f f i c h e _ t a b ( x , y , c , n ,m) : else :
print ( " i j " . r j u s t ( 3 ) , end = " " ) return p l s s c ( x , y , d , i , j −1)
print ( " " . r j u s t ( 3 ) , end = " " )
for j in range ( 0 ,m) :
print ( y [ j ] . r j u s t ( 3 ) , end =" " ) def p l s s c _ r e s ( x , y ) :
print ( ) d , n ,m = p a t h s _ p l s s c ( x , y )
for i in range ( 0 , n +1): a f f i c h e _ t a b ( x , y , d , n ,m)
if i == 0 : s s c = p l s s c ( x , y , d , n ,m)
print ( " " . r j u s t ( 3 ) , end= " " ) return s s c

15 Pascale Le Gall 16 Pascale Le Gall


4 PLUS LONGUE SOUS-SÉQUENCE COMMUNE À DEUX SÉQUENCES 4 PLUS LONGUE SOUS-SÉQUENCE COMMUNE À DEUX SÉQUENCES

t | | | | | | = - - |
i | | | | | = | | | |
def p l s s c _ s t r ( x , y ) : e | | | | | | | | | =
return p l s s c _ r e s ( list ( x ) , list ( y ) ) l = | | | | | | | | |
[’o’, ’t’, ’e’]
Ce qui donne par exemple >>> plssc_str(’optiminisme’,’pessimisme’)
ij p e s s i m i s m e
>>> plssc_str("python","cobra")
ij c o b r a o | | | | | | | | | |
p = - - - - - - - - -
p | | | | | t | | | | | | | | | |
y | | | | | i | | | | = - = - - -
t | | | | | m | | | | | = - - = -
h | | | | | i | | | | = | = - - -
o | = - - - n | | | | | | | | | |
n | | | | | i | | | | = | = | | |
[’o’] s | | = = | | | = - -
>>> plssc_str("pomme ananas","poire banane") m | | | | | = | | = -
ij p o i r e b a n a n e e | = | | | | | | | =
[’p’, ’i’, ’m’, ’i’, ’s’, ’m’, ’e’]
p = - - - - - - - - - - - >>> plssc_str(’centrale’,’supelec’)
o | = - - - - - - - - - - ij s u p e l e c
m | | | | | | | | | | | |
m | | | | | | | | | | | | c | | | | | | =
e | | | | = - - - - - - = e | | | = - = |
| | | | | = - - - - - - n | | | | | | |
a | | | | | | | = - = - - t | | | | | | |
n | | | | | | | | = - = - r | | | | | | |
a | | | | | | | = | = - - a | | | | | | |
n | | | | | | | | = | = - l | | | | = - -
a | | | | | | | = | = | | e | | | = | = -
s | | | | | | | | | | | | [’e’, ’l’, ’e’]
[’p’, ’o’, ’e’, ’ ’, ’a’, ’n’, ’a’, ’n’]
>>>

avec "=" pour "z", "|" pour "x" et "-" pour "y".
Et pour finir avec d’autres exemples :

>>> plssc_str(’exponentiel’,’logarithme’)
ij l o g a r i t h m e

e | | | | | | | | | =
x | | | | | | | | | |
p | | | | | | | | | |
o | = - - - - - - - |
n | | | | | | | | | |
e | | | | | | | | | =
n | | | | | | | | | |

17 Pascale Le Gall 18 Pascale Le Gall

Vous aimerez peut-être aussi