Algorithmique Cristina Sirangelo, ENS-Cachan Préparation à l'option Informatique de l'agrégation de mathématiques
AlgorithmiqueCristina Sirangelo, ENS-Cachan
Préparation à l'option Informatique de l'agrégation de mathématiques
Plan
1. Analyse et complexité des algorithmes (S. Haddad)
2. Types abstraits et structures de données
3. Algorithmes de tri
4. Techniques classiques de conception d’algorithmes
5. Algorithmes de graphes
Diviser pour régner
Diviser pour régner (“Divide et impera”)
Stratégie “diviser pour régner” pour résoudre un problème:
‣ diviser le problème en sous-problèmes, qui sont des instances du même problème, mais de taille plus petite
‣ résoudre les sous-problèmes récursivement (ou les résoudre directement si de taille suffisamment petite)
‣ combiner les solutions des sous-problèmes pour obtenir la solution du problème d’origine
Exemples déjà vus:
‣ recherche dichotomique
‣ tri fusion
‣ tri rapide
Analyse du coût
Basée sur la résolution de récurrences T(n) : coût pour une instance de taille n
T(1) = Θ(1)
T(n) = T(n1) + ..+T(na) + f(n)
‣ n1...na: tailles des a sous-problèmes, ni < n
‣ f(n): coût de la création des sous-problèmes + combinaison de leurs solutions
Souvent: a sous-problèmes de la même taille n/b, pour une constante b; f(n) = Θ(nd)
Théorème. La récurrence (*) où n/b signifie n/b ou n/b et a > 0, b > 1 et d ≥ 0, a pour solution
T(1) = Θ(1)
T(n) = a T(n/b) + Θ(nd)(*)
Multiplication de matrices
Entrée: M et N, deux matrices n×n
Sortie: P = M N
‣ Approche naïve coût: Θ(n3)
‣ Première approche diviser pour régner
‣ 8 produits de matrices n/2 × n/2
‣ combinaison des résultats: Θ(n2)
T(n) = 8 T(n/2) +Θ(n2) ⇒ T(n) = Θ(nlog28) = Θ(n3)
‣ Deuxième approche diviser pour régner (Strassen) : réduit à 7 le nombre de multiplications de matrices n/2 × n/2
A B
C D
E F
G H
A E + B G A F + B H
C E + D G C F + D H=
M N M N
Ω(n2)
Algorithme de Strassen [1969]
‣ 7 produits de matrices n/2 × n/2
‣ création des sous-problèmes + combinaison des résultats: Θ(n2)
T(n) = 7 T(n/2) +Θ(n2) ⇒ T(n) = Θ(nlog27) ≈ O(n2.81)
Après Strassen: Coppersmith–Winograd [1986] O(n2.3755) A.Stothers [2010] O(n2.3736)V. Williams [2011] O(n2.3727)
A B
C D
E F
G H
P5+P4-P2+P6 P1+P2
P3+P4 P1+P5-P3-P7
M N M N
P1= A(F-H) P2 = (A+B)H P3=(C+D) E P4=D(G-E)
P5= (A+D)(E+D)P6=(B-D)(G+H)P7=(A-C)(E+F)
Multiplication d’entiers de grande taille
Entrée: deux entiers, x et y, de n bits
Sortie: x y (2n bits)
Approche naïve: O(n2)
Approche diviser pour régner (supposer n puissance de 2, pour simplifier)
Pour un entier z de n bits: zL (zR) : entier représenté par les n/2 bits de poids plus fort (faible)
‣ apparemment 4 sous problèmes (multiplications d'entiers de n/2 bits), mais 3 suffisent:
P1= xL yL P2=(xL + xR) (yR + yL) P3=xR yR
xL yR + xR yL= P2 - P1 - P3
‣ combinaison des solutions des sous-problèmes: sommes et “shift” Θ(n)
T(n) = 3 T(n/2) +Θ(n) ⇒ T(n) = Θ(nlog23) ≈ O(n1.59)
x = 2n/2 xL + xR
y = 2n/2 yL + yR
x y = (2n/2 xL + xR) (2n/2 yL + yR) = 2n xL yL + 2n/2 (xL yR + xR yL) + xR yR
Convolution et multiplication de polynômes
Entrée: deux polynômes A(x) et B(x) de degré au plus n, représentés par leurs coefficients:(a0, .., an), (b0,.., bn)
Sortie: le polynôme C(x) = A(x)B(x) représenté par ses coefficients (c0, .., c2n)
‣ (c0, .., c2n) est appelé la convolution de (a0, .., an), (b0,.., bn) (dénoté )
‣ Techniques pour la multiplication de polynômes : techniques pour le calcul de la convolution
‣ Approche naïve: O(n2)
‣ Approches diviser pour régner
‣ approche directe (similaire à la multiplication d’entiers) O(n1.59) (Exercice)
‣ FFT (Fast Fourier Transform) O(n log n)
‣ d’autres applications de la convolution: traitement du signal
Convolution et traitement du signal
‣ Signal numérisé: somme d’impulsions décalés dans le temps
signal: une quantité en fonction du temps signal numérisé (échantillonnage)
impulsion unité
a(t)
1
t
t
Convolution et traitement du signal
Système de traitement du signal
Système linéaire: réponse à la somme de deux signaux = sommes des réponsesréponse à l’amplification d’un signal = amplification de la réponse
Système invariant par rapport au temps: réponse à un signal décalé dans le temps = réponse décalée
‣ Soit b(t) la réponse du système à l’impulsion unité δ(t)
‣ Sortie = convolution de l’entré et de la réponse à l’impulsion unité
c(t)a(t)
t t
Multiplication de polynômes par FFT
Entrée: deux polynômes A(x) et B(x) de degré au plus d, représentés par leurs coefficients:(a0, .., an-1), (b0,.., bn-1) n-1=2d
Sortie: le polynôme C(x) = A(x)B(x) représenté par ses coefficients (c0, .., cn-1)
‣ Représentation des polynômes par paires point-valeur:
Théorème. Si A(x) est un polynôme de degré au plus d, pour tout ensemble de d+1 points distincts x0...xd, les valeurs A(x0), ..., A(xd) caractérisent A(x) de façon unique (i.e., A(x) est le seul polynôme de degré au plus d ayant ces valeurs sur les points x0...xd)
‣ Si A et B sont deux polynômes de degré au plus d représentés par paires point valeurs
A(x0) ... A(xn-1)
B(x0) ... B(xn-1)
avec n-1= 2d
Le produit est immédiat:
C(xk)= A(xk) B(xk), k=0,..n-1 est une représentation de C(x) par paires point-valeur
Multiplication de polynômes par FFT
Algorithme de multiplication de polynômes
Entrée: deux polynômes A(x) et B(x) de degré au plus d, représentés par leurs coefficients:(a0, .., an-1), (b0,.., bn-1) n-1=2d
Sortie: le polynôme C(x) = A(x)B(x) représenté par ses coefficients (c0, .., cn-1)
‣ Sélection : Choisir n points x0, ..., xn-1
‣ Évaluation : Calculer A(x0) ... A(xn-1) et B(x0) ... B(xn-1) (coefficients → point-valeur)
‣ Multiplication : Calculer C(xk)= A(xk) B(xk), k=0,..n-1
‣ Interpolation : Reconstruire (c0, .., cn-1) de C(x0) ... C(xn-1) (point-valeur → coefficients)
Objectif: Evaluation et Interpolation en O(n log n) - approche diviser pour régner
Multiplication de polynômes par FFT
Evaluation d’un polynôme A(x) de degré au plus n-1, en n points x0, ..., xn-1
Supposer n une puissance de 2
‣ A(x) divisé en puissances paires et impaires:
A(x) = A0(x2) + x A1(x2)
A0 et A1 ont degré au plus n/2 -1
‣ Si les points x0, .. xn-1 sont choisi comme des couples ± :
± x0,...., ± xn/2-1
A(xj) = A0(xj2) + xj A1(xj2)
A(-xj) = A0(xj2) - xj A1(xj2)
‣ Évaluation de A(x) (de degré au plus n-1) en n points ± x0,...., ± xn/2-1:
‣ Évaluation de A0(x) (de degré au plus n/2-1) en n/2 points: xj2, j=0,..., n/2-1
‣ Évaluation de A1(x) (de degré au plus n/2-1) en n/2 points: xj2, j=0,..., n/2-1
‣ Combinaison des résultats
‣ À chaque étape de la récursion les points doivent être des couples ±
Multiplication de polynômes par FFT
Évaluation d’un polynôme A(x) de degré au plus n-1, en n points x0, ..., xn-1
‣ x0, ..., xn-1 sont choisis comme les n racines n-ièmes complexes de l’unité: 1, ω, ω2, ..., ωn-1
Lemme
les n racine n-ièmes de l’unité sont des couples: ± x0,...., ± xn/2-1 et
sont les racines (n/2)-ièmes complexes de l’unité
Définition (DFT) Transformée de Fourier Discrète
La transformée de Fourier discrète des coefficients (a0, ..., an-1) est le vecteur
<A(1) A(ω), A(ω2)..., A(ωn-1)> où A est le polynôme de coefficients (a0, ..., an-1)
L’approche diviser pour régner avec ce choix de x0, ..., xn-1 à chaque étape calcule la DFT.
Cet algorithme d'évaluation est appelé Transformée de Fourier rapide (FFT)
Transformé de Fourier rapide (FFT)
FFT( a0, .., an-1)
// calcule la transformée de Fourier discrète <A(1) A(ω), A(ω2)..., A(ωn-1)> de (a0, ..., an-1)
s0 ← a0, a2, ..., an-2 // les n/2 coefficients de A0
s1 ← a1, a3, ..., an-1 // les n/2 coefficients de A1
FFT(s0) calcule A0(ω2 j ), j=0,.., n-1
FFT(s1) calcule A1(ω2 j ), j=0,.., n-1
for (j←0 to n-1) do calculer A(ωj) comme A0(ω2 j ) + ωj A1(ω2 j ) endfor
Analyse du coût:
‣ 2 sous-problèmes de taille n/2;
‣ production des sous-problèmes et combinaison des solutions: Θ(n)
T(n) = 2T(n/2) +Θ(n) ⇒ T(n) = Θ(n log n)
Multiplication de polynômes par FFT
Algorithme de multiplication de polynômes
Entrée: deux polynômes A(x) et B(x) de degré au plus d, représentés par leurs coefficients:(a0, .., an-1), (b0,.., bn-1) n-1=2d
Sortie: le polynôme C(x) = A(x)B(x) représenté par ses coefficients (c0, .., cn-1)
‣ Sélection : Choisir n points x0, ..., xn-1 - les racine n-ièmes de l’unité
‣ Évaluation : Calculer A(x0) ... A(xn-1) et B(x0) ... B(xn-1) - FFT
‣ Multiplication : Calculer C(xk)= A(xk) B(xk), k=0,..n-1 - calcule la DFT de (c0, .., cn-1)
‣ Interpolation : Reconstruire (c0, .., cn-1) de C(x0) ... C(xn-1) - DFT inverse
Multiplication de polynômes par FFT
Interpolation: DFT inverse
Soit y = DFT(a0, .., an-1) et Y le polynôme de coefficients y
y T = Vn·(a0, .., an-1)T
Vn: matrice de Vandermonde, inversible
On en déduit
i.e. les coefficients d’origine s’obtiennent par évaluation du polynôme Y en 1 ω-1 ω-2..., ω-(n-1)
La DFT inverse peut être calculée par l’algorithme FFT( y) modifié comme suit:
- remplacer ω par ω-1
- multiplier chaque élément du résultat final par 1/n
Interpolation: Θ (n log n)
Multiplication de polynômes par FFT
Algorithme de multiplication de polynômes
Entrée: deux polynômes A(x) et B(x) de degré au plus d, représentés par leur coefficients:(a0, .., an-1), (b0,.., bn-1) n-1=2d
Sortie: le polynôme C(x) = A(x)B(x) représenté par ses coefficients (c0, .., cn-1)
‣ Sélection : Choisir n points x0, ..., xn-1 - les racine n-ièmes de l’unité Θ(n)
‣ Évaluation : Calculer A(x0) ... A(xn-1) et B(x0) ... B(xn-1) - FFT Θ(n log n)
‣ Multiplication : Calculer C(xk)= A(xk) B(xk), k=0,..n-1 - calcule la DFT de (c0, .., cn-1) - Θ(n)
‣ Interpolation : Reconstruire (c0, .., cn-1) de C(x0) ... C(xn-1) - DFT inverse (FFT) - Θ(n log n)
Θ(n log n)
Programmation dynamique
Programmation dynamique
‣ Paradigme basé sur la résolution de sous-problèmes pour résoudre le problème principal
‣ Surtout utilisé pour des problèmes d’optimisation: trouver une solution qui optimise (maximise/minimise) un certain critère d'évaluation.
‣ Conditions:
‣ sous-structure optimale: une solution optimale peut être obtenue à partir des solutions optimales de certains sous-problèmes
‣ il existe un ordre sur les sous-problèmes compatible avec la sous-structure optimale
Programmation dynamique
Programmation dynamique vs diviser pour régner
programmation dynamique adaptée quand les sous-problèmes à résoudre ont des sous-problèmes superposés :
‣ approche directe diviser pour régner: un sous-problème est résolu à chaque fois qu’il intervient
‣ programmation dynamique: chaque sous-problème est résolu une seule fois:
‣ approche bottom-up (itératif) : résoudre d’abord les sous-problèmes de taille plus petite, ensuite les sous-problèmes de taille plus grande, en utilisant les sous-problèmes déjà résolus
‣ approche top-down (récursif) avec memoïsation: résolution récursive de ses sous-problèmes; les solutions déjà calculées sont mémorisées et utilisées ensuite si les sous-problèmes interviennent à nouveau
Un premier exemple: plus courts chemins dans un graphe acyclique
‣ Graphe orienté pondéré acyclique: G=(V, E, w), E ⊆ V×V , w: E → R (poids des arcs), G n’a pas de cycles
‣ Chemin de s à t :
‣ suite p = v1 v2 ... vn telle que s = v1, t = vn et (vi , vi+1) ∈ E , pour tout i=1,..,n-1
‣ poids du chemin
‣ Plus court chemin entre s et t : chemin de s à t de plus petit poids
‣ distance dist(s, t): le poids du plus court chemin entre s et t
Problème: trouver le plus court chemin entre deux sommets s et t dans un graphe acyclique donné.
Une solution: un chemin de s à t, Critère d’optimisation: le poids du chemin
Hypothèse: le graphe en entrée est linéarisé (ses sommets sont triés selon un tri topologique)
s t
2
4
6
1 1
3
2 4
Plus courts chemins dans un graphe acyclique
Trouver une sous-structure optimale
‣ Partition de l’espace des solutions: Si s ≠ t, chaque chemin de s à t doit se terminer par un arc (u, t) où u est un prédécesseur de t
‣ Solution optimale dans chaque partition: le plus court chemin qui se termine par un arc (u,t) fixé est le plus court chemin p de s à u (sous-problème) suivi de l’arc (u, t)
Preuve par “substitution”
‣ Solution optimale: la meilleure des solutions optimales de chaque partition
Si on est intéressé seulement par le calcul de la distance:
p
s t
s tu
Plus courts chemins dans un graphe acyclique
‣ Sous-problèmes: les plus courts chemins du sommet s vers les autres sommets du graphe
‣ Ordre de résolution des sous-problèmes (compatible avec la sous-structure optimale) : le tri topologique
‣ les plus courts chemins vers les prédécesseurs d’un sommet v doivent être disponibles pour calculer le plus court chemin vers v
‣ Algorithme de programmation dynamique (bottom-up) pour le calcul de la distance de s à t
Distance ( Graphe (V,E), Sommet s, Sommet t)
initialiser toutes les valeurs d’un tableau dist[1..|V|] à ∞ // les distances depuis s
dist[s] ← 0
Pour tout v ∈ V\{s} dans l’ordre topologique
pour tout (u, v) ∈ E dist[v] ← min{ dist[v], dist[u] + w(u,v) }
retourner dist[t]
Coût: O(|V|+|E|)
Plus courts chemins dans un graphe acyclique
Une solution récursive directe (diviser pour régner)
Distance ( Graphe (V,E), Sommet s, Sommet t)
si (s=t) retourner 0
d ← ∞pour tout (u, t) ∈ E
d ← min{ d, Distance((V,E), s, u) + w(u,t) }retourner d
Sous-problèmes superposés:
la distance (s, v) est calculée plusieurs fois si
v est prédécesseur de plusieurs sommets
Lemme: le nombre d’appels récursifs est borné inférieurement par le nombre de chemins de s à t
⇒ Coût de l’algorithme diviser pour régner: exponentiel
(1,5)
(1,4) (1,3)
(1,3) (1,2) (1,1)
(1,1)(1,2) (1,1)
(1,1)
1 2 3 4 5
Plus courts chemins dans un graphe acyclique
Algorithme de programmation dynamique récursif (top-down) avec memoïsation
Distance ( Graphe (V,E), Sommet s, Sommet t, Tableau dist)
// la partie alloué de dist contient les distances déjà calculées de s aux sommets du graphe
si dist[t] est initialisé retourner dist[t] // solution déjà calculée
// sinon la solution est calculée et mémorisée dans dist :
si (s=t) dist[t] ← 0
sinon
d ← ∞pour tout (u, t) ∈ E
d ← min{ d, Distance((V,E), s, u, dist) + w(u,t) }dist[t] ← d
retourner dist[t]
Coût O(|V|+|E|): création du tableau dist + un appel récursif pour chaque arc
Top-down vs bottom-up
Coût asymptotique en générale comparable
Approche bottom-up
‣ résout tous les sous-problèmes (ex. le distances de s à tous les sommets du graphe)
Approche top-down
‣ résout seulement les sous-problèmes engendrés par le problème principal (ex. les distances de s aux sommets qui ont un chemin vers t)
‣ overhead dû aux appels récursifs
Construction de la solution optimale
Par modification de l’algorithme de programmation dynamique qui calcule la valeur optimale :
‣ Pour chaque sous-problème mémoriser le choix optimal :
‣ Reconstruire la solution optimale :
Plus-court-chemin ( Graphe (V,E), Sommet s, Sommet t)
initialiser les éléments d’un tableau dist[1..|V|] à ∞ // les distances depuis s
dist[s] ← 0
initialiser les éléments d’un tableau pred[1..|V|] à NIL // les prédécesseurs dans le plus court chemin
Pour tout v ∈ V\{s} dans l’ordre topologique
dist[v]←∞pour tout (u, v) ∈ E
si ( dist[u] + w(u,v) < dist[v] ) dist[v] ← dist[u] + w(u,v); pred[v]← u
retourner dist[t], pred
Imprimer-chemin(Tableau pred, Sommet t) // imprime le chemin jusqu’au sommet t
si pred[t] ≠ NIL Imprimer-chemin (pred, pred[t])
imprimer t
Plus courts chemins dans un graphe acyclique
‣ Problème typique pour la programmation dynamique
Graphe des sous-problèmes:
‣ Sommets: les sous-problèmes
‣ Arcs: les relation entre sous-problèmes
‣ Acyclicité: ordre de résolution des sous-problèmes
‣ Base pour les algorithmes classiques de plus courts chemins
Plus long chemin dans un graphe acyclique
Entrée: Un graphe acyclique (non-pondéré) G=(V,E), les sommets triés selon un tri topologique
Problème: trouver le [la taille du] plus long chemin dans G
‣ Sous-structure optimale:
‣ chaque chemin qui se termine en v, se termine par un arc (u,v) ∈ E
‣ le plus long chemin qui se termine par (u,v) ∈ E = le plus long chemin qui se termine en u suivi de l'arête (u,v)
‣ Sous-problèmes (pour le calcul de la taille) :
L(v) : taille du plus long chemin qui se termine en v, pour tout v ∈ V
‣ Relation entre les sous-problèmes: L(v)= max { 1+ L(u) | (u,v) ∈ E } (convention: max ∅ =0)
‣ Ordre de résolution des sous-problèmes : le tri topologique des sommets
‣ Algorithme bottom-up:
‣ Coût: O( |V|+|E| )
Plus-long-chemin ( Graphe (V,E) )Tableau L[1..|V|]Pour tout v ∈ V dans l’ordre topologique
L[v] ← max { 1+ L[u] | (u,v) ∈ E }retourner max L
Plus long chemin dans un graphe acyclique
‣ Construction de la solution:
‣ dans l’algorithme précèdent ajouter et mettre à jour un tableau des prédécesseurs
‣ reconstruire le chemin en arrière
‣ Solution diviser-pour régner (même analyse que dans le cas des plus courts chemins)
- Arbre des appels récursifs pour le calcul de L(t) = “unraveling” en arrière du graphe
‣ Un sous-problème L(u) pour chaque chemin de u à t (sous-problèmes superposés)
- Coût: exponentiel dans le nombre de sommets
‣ Applications: Calcul de la plus longue sous-séquence croissante
Entrée : une séquence a1...an de nombres
Problème : trouver la plus longue sous-séquence aj1 ,...., ajk , avec 1 ≤ j1 < j2 < ...< jk ≤ n,
telle que aj1< aj2 <...< ajk
Il s’agit du plus long chemin dans le graphe G=(V,E)
- V= {a1...an},
- (ai, aj) ∈ E ssi i < j et ai <aj (i.e. aj peut suivre ai dans une sous-séquence croissante)
Plus longue sous-séquence commune (PLSC)
Entrée : Deux séquences A=a1... an, B=b1... bm
Problème: Trouver la (longueur de la) plus longue séquence C = c1...ck, telle que C est une sous-séquence de A et de B
( C est une sous-séquence de a1 ... an si C = aj1...ajk avec 1 ≤ j1 < j2 ...< jk ≤ n)
‣ Application: comparaison de deux brins d’ADN
‣ Un brin d’ADN: une séquence de molécules (les bases) dans l’ensemble {A, C, G, T }
‣ la longueur maximale d’une sous-séquence commune de deux brins d’ADN est une mesure de similarité des organismes possédant ces ADN
‣ Notation Si A=a1... an Aj dénote la séquence a1... aj pour tout j=1, ..., n
Plus longue sous-séquence commune
‣ Sous-structure optimale:
Lemme. Soit A=a1... an , B=b1... bm
Si an = bm ⇒ PLSC(A, B) = PLSC(An-1, Bm-1) an
Si an ≠ bm ⇒ PLSC(A, B) est soit PLSC(An-1, B) soit PLSC(A, Bm-1)
Preuve. Soit C= ai1...aik = bj1...bjk une PLSC de A et B
Si an = bm
‣ On démontre aik (=bjk) =an. Si aik≠an et donc bjk≠ bm ⇒ ik < n et jk < m ⇒
C an = C bm est une sous-séquence commune de A et B plus longue que C ⇒ contradiction.
‣ On démontre Ck-1 = PLSC(An-1, Bm-1). Ck-1 est une sous-séquence de An-1 et Bm-1. Supposer qu’il existe une sous-séquence D de An-1 et Bm-1 plus longue que Ck-1 ⇒
D an est une sous-séquence de A et B plus longue que C=Ck-1 an ⇒ contradiction.
Si an ≠ bm
‣ Si aik = an ⇒ bjk ≠ bm ⇒ jk < m ⇒ C est une sous-séquence de A et Bm-1 ⇒ C = PLSC(A, Bm-1)
‣ Si aik ≠ an ⇒ ik < n ⇒ C est une sous-séquence de An-1 et B ⇒ C = PLSC(An-1, B)
Plus longue sous-séquence commune
‣ Sous-problèmes (pour le calcule de la longueur de la PLSC): |PLSC(Ai, Bj)|, i=0..n, j=0..m
‣ Relation entre les sous-problèmes: Si i=0 ou j=0 ⇒ |PLSC(Ai, Bj)| = 0, Sinon:
Si ai = bj ⇒ |PLSC(Ai, Bj)| = |PLSC(Ai-1, Bj-1)| +1
Si ai ≠ bj ⇒ |PLSC(Ai, Bj)| = max { |PLSC(Ai-1, Bj)|, |PLSC(Ai, Bj-1)| }
Remarque: sous-problèmes déterminés par une une propriété de l’instance du problème
‣ Ordre de résolution des sous-problèmes c(i,j) = |PLSC(Ai, Bj)|: par lignes
‣ Algorithme bottom-up pour la longueur de la PLSC:
Longueur-PLSC ( Séquence a1... an , Séquence b1... bm )
Tableau c[0..n, 0..m]
for (i ←0 to n) dofor (j← 0 to m) do
if (i=0 or j=0) c[i, j]← 0 elseif ( ai = bj ) c[i, j]← c[i-1, j-1]+ 1else c[i, j]← max{ c[i-1, j], c[i, j-1] } endif
endforendforreturn c[n, m] Coût: Θ(n m)
Plus longue sous-séquence commune
‣ Calcul de la PLSC
‣ pour chaque sous-problème mémoriser le sous-problème choisi pour le calcul de c(i,j)
PLSC ( Séquence a1... an , Séquence b1... bm )
Tableau c[0..n, 0..m]
Tableau d[0..n, 0..m]
for (i ←0 to n) do
for( j← 0 to m) do
if (i=0 or j=0) c[i, j]← 0; d[i, j] ←NIL
elseif ( ai = bj ) c[i, j]← c[i-1, j-1]+ 1; d[i, j] ←(i-1, j-1)
elseif (c[i-1, j] > c[ i, j-1] ) c[i, j]← c[i-1, j]; d[i, j] ←(i-1, j)
else c[i, j]← c[i, j-1]; d[i, j] ←(i, j -1)
endif
endfor
endforreturn b, c[n, m]
Plus longue sous-séquence commune
‣ Reconstruction de la solution: Imprimer-PLSC ( a1... an , d, n, m )
‣ Optimisation de l’espace utilisé:
‣ Le tableau d peut être éliminé: les tests: ai = bj et c[i-1, j] > c[ i, j-1] déterminent d[i, j]
‣ Pour le calcul de la longueur, le tableau c peut être réduit à 2 lignes: la courante et la précédente
Imprimer-PLSC ( Séquence A , Tableau d, i, j )
//imprime la PLSC de Ai et Bj à partir du tableau d
if ( d[i, j] = (i-1, j-1) ) Imprimer-PLSC( A, d, i-1, j-1); Imprimer ai
elseif ( d[i, j] = (i-1, j) ) Imprimer-PLSC( A , d, i-1, j)
elseif ( d[i, j] = (i, j -1) ) Imprimer-PLSC(A, d, i, j -1)
endif
Coût: O(n+m)
Plus longue sous-séquence commune
‣ Solution récursive exponentielle. Implémentation directe de la récurrence:
longueur-PLSC(An, Bm)
if( n=0 or m=0 ) return 0
elseif (an = bm) return longueur-PLSC(An-1, Bm-1) +1
else return max { longueur-PLSC(An-1, Bm), longueur- PLSC(An, Bm-1) }
Coût : cas An et Bm disjoints: T(n,m) = 1+ T(n-1, m)+T(n, m-1) , T(0, m)=T(n, 0)=1
T(n, m) ≥ 2min{n,m} par substitution
Sous-problèmes superposés:(n,m)
(n-1,m) (n,m-1)
(n-2, m) (n-1,m-1) (n-1,m-1) (n,m-2)
......
... ...
Exercice : Distance d'édition (Edit distance)
‣ Mesure de similarité entre deux chaînes de caractères. Utilisé par exemple comme mesure de similarité d’ADN
Définition Un alignement de deux chaînes de caractères ws et wt sur l’alphabet ∑ est un
mot sur l’alphabet (∑ ∪ { - })2 \ (-,-) tel que
s1, ..sn privé des symboles “-” coïncide avec ws , et t1, ..tn privé des symboles “-” coïncide avec wt
Exemple. Un alignement de “algorithme” et “alternatif”
A L G O - R - - I T - H M E
A L T - E R N A T I F - - -
‣ Un alignement peut être vu comme un ensemble d'opérations d’insertion
suppression et remplacement qui transforment la première chaîne dans la deuxième
Coût d’un alignement : nombre de symboles avec si ≠ ti (nombre de modifications)
Définition La distance d'édition de ws et wt est le coût minimale d’un alignement entre ws et wt (le nombre minimal de modifications pour transformer ws en wt)
Exercice : Distance d'édition
Exercice. Proposer un algorithme de programmation dynamique pour calculer la distance d'édition de deux chaînes de caractères en temps polynomial. La comparer avec une solution récursive directe.
Multiplication d’une suite de matrices
Entrée: n matrices à multiplier
A1 ...An
où Ai a dimension pi-1 × pi for i=1, ..,n
Problème: trouver l’ordre de multiplication des matrices deux à deux (le parenthésage) qui minimise le coût de la multiplication
Coût de la multiplication de deux matrices p×q et q×r : p×q×r multiplications scalaires
Exemple
Coût de (A1 A2) A3 : 3·100·5 + 3·5·50 = 2250 multiplications scalaires
Coût de A1(A2 A3) : 100·5·50 + 3·100·50 = 40000 multiplications scalaires
A1
3 × 100 A2
100 × 5 A3
5 × 50
Multiplication d’une suite de matrices
‣ Sous-structure optimale
- Partition de l’espace des solutions:
‣ Si j > i chaque parenthésage de Ai...Aj est obtenu par :
un parenthésage de Ai.. Ak et un parenthésage de Ak+1..Aj, pour un certain i≤ k < j
‣ Si j = i le seul parenthésage est la matrice Ai
- Parenthésage optimal de Ai...Aj avec k fixé :
‣ Parenthésage optimale de Ai... Ak suivi d’un parenthésage optimal de Ak+1...Aj preuve par substitution
- Parenthésage optimal de Ai..Aj:
m(i, j) : Coût du parenthésage optimal de Ai...Aj m(i, i) =0
m(i, j) = min { m(i, k) + m(k+1, j) + pi-1 · pk · pj | i ≤ k < j }
pi-1 · pk · pj : coût de la multiplication de Ai...Ak (pi-1 × pk) et Ak+1...Aj (pk × pj)
Multiplication d’une suite de matrices
‣ Sous-problèmes (pour le calcul du coût optimal) : m(i, j)
‣ Relation entre les sous-problèmes : m(i, j) = min { m(i, k) + m(k+1, j) + pi-1·pk·pj | i ≤ k < j }
‣ Ordre de résolution des sous-problèmes: par taille
‣ Algorithme bottom-up
‣ Coût: O(n3)( boucles pour s, i et k )
Coût-Suite-Matrices ( Dimensions <p1, ..., pn>)
Tableau m[1..n, 1..n]for( i← 1 to n ) do m[i, i] ← 0 endfor
for( s← 1 to n-1 ) // s: taille des sous-problèmes for ( i← 1 to n-s )
j ← i + s//calcul de m[i, j]m[i, j] ← ∞for ( k ← i to j-1 )
q ← m[i, k] + m[k+ 1, j] + pi-1·pk·pj
if ( q < m[i, j] ) m[i, j] ← q endifendfor
endforendforreturn m[1,n]
Multiplication d’une suite de matrices
Suite-Matrices ( Dimensions <p1, ..., pn>)
Tableau m[1..n, 1..n]; Tableau t[1..n, 1..n];for( i← 1 to n ) do m[i, i] ← 0 endfor
for( s← 1 to n-1 ) // s: taille des sous-problèmes for ( i← 1 to n-s )
j ← i + s// calcul de m[i, j]m[i, j] ← ∞for ( k ← i to j-1 )
q ← m[i, k] + m[k+ 1, j] + pi-1·pk·pj
if ( q < m[i, j] ) m[i, j] ← q; t[i, j] ←k endif
endforendfor
endforreturn m[1, n], t
Imprimer-Paren( Tableau t, i, j)// imprime le parenthésage optimal de // Ai..Ajif (i=j) Imprimer ik ← t[i, j];Imprimer “(“Imprimer-Paren(t, i, k)Imprimer-Paren(t, k+1, j)Imprimer “)”
‣ Reconstruction de la solution: Imprimer-Paren(t, 1, n)
‣ Calcul du parenthésage optimal
Multiplication d’une suite de matrices
‣ Solution récursive directe exponentielle :
m ( Dimensions <p1, ..., pn>, Entier i, Entier j)
if (i=j) return 0 endifm ←∞for (k ← i to j-1 )
m ← min{ m, m(<p1, ..., pn>, i, k) + m(<p1, ..., pn>, k+ 1, j) + pi-1·pk·pj }return m
Coût
T(i, i) =1 pour tout i=1..n
‣ Solution récursive avec memoïsation: passer et mettre à jour un paramètre m[1..n, 1..n] contenant les valeurs m(i, j) déjà calculés O(n3)
pour i < j
⇒ T(i, j) ≥ 2j-i par substitution
⇒ T(1,n) =Ω(2n)
Multiplication d’une suite de matrices
‣ Sous-problèmes superposés:
Remarque: la solution récursive directe reste plus efficace qu’examiner tous les parenthésages possibles:
cela est dû à l'indépendance des sous-problèmes (à la base de la sous-structure optimale)
m(1,4)
m(1,1) m(2,4) m(1,2) m(3,4) m(1,3) m(4,4)...
m(2,2) m(3,4) m(2,3) m(4,4) m(1,1) m(2,3) m(1,2) m(3,3)
...
... ... ...
...
Problème du sac à dos (Knapsack)
Entrée: n objets avec poids w1, ..wn et valeurs v1, .., vn ; un poids maximal W (tous entiers positifs)
Problème: trouver la “combinaison” d’objets à valeur totale maximale dont le poids total n'excède pas W. i.e. trouver des quantités x1,...xn telles que:
x1 w1 + ...+ xn wn ≤ W et x1 v1 + ...+ xn vn est maximal
Plusieurs variantes:
‣ avec répétition (chaque objet peut être sélectionné plusieurs fois); x1, ...,xn entiers positifs
‣ sans répétition (aussi appelé 0/1-knapsack, chaque objet peut être sélectionné une seule fois) x1..xn ∈ {0, 1}
‣ fractionnaire (chaque objet peut être sélectionné en partie) x1...xn ∈ [0, 1], rationnels
‣ La variante fractionnaire: solution polynomiale avec approche gloutonne
‣ Les deux autres variantes: NP-durs, algorithmes de programmation dynamique pseudo-polynomiaux (O(n W))
Problème du sac à dos
Applications:
Plusieurs problèmes de sélection de taches contrainte par une ressource :
w1 = v1 ... wn = vn : tâches (ex. temps de CPU demandé par chaque processus)
W: contrainte sur la ressource (ex. temps de CPU)
‣ Problème du sac à dos: sélectionner les tâches à exécuter de façon a maximiser l’utilisation de la ressource
( le cas particulier où vi = wi : problème du Subset-Sum )
Problème du sac à dos avec répétition
Solution avec capacité W: Des quantités entières positives x1...xn pour les objets 1..n tel que x1 w1 + ...+ xn wn ≤ WSolution optimale: Une solution qui maximise x1 v1 + ...+ xn vn
‣ Sous-structure optimale
- Chaque solution non vide contient une unité de l’objet i, pour un certain i tq wi ≤W
- Solution optimale contenant une unité de i : unité de i + solution optimale avec capacité W-wi
(preuve par substitution, utilise la répétition)
- Solution optimale pour W: la meilleure des solutions optimales contenant i, pour tout i tq wi ≤W
‣ Sous-problèmes: valeurs optimales avec capacité w≤W K(w) = max { K(w-wi) + vi | wi ≤ w}
Problème du sac à dos avec répétition
‣ Algorithme bottom-up
‣ Remarque: réduction au plus long chemin dans un graphe acyclique
‣ Construction de la solution, memoïsation: standard
‣ Memoïsation: Calcule seulement les sous-problèmes de la forme K( W - ∑ wi )
Sac-a-dos( W, w1...wn , v1...vn )
Tableau K[0...W]; K[0] ← 0for (w ←1 to W ) do //calcul de K[w]
K[w] ← 0 ; for (i ←1 to n) do
if (wi ≤ w) K[w] ← max { K[w], K[w - wi] + vi } endif endfor
endfor return K[W]
Coût : Θ(n W)
0 1 w w+wj W...vj
......
Problème du sac à dos sans répétition
Solution avec capacité W et objets {1,..,n}: un sous-ensemble de {1,..,n} ( variables xi ∈{0, 1} ) tel que x1 w1 + ...+ xn wn ≤ WSolution optimale: Une solution qui maximise x1 v1 + ...+ xn vn
‣ Sous-structure optimale
- Dans chaque solution pour W et {1,.., n}: xn est 0 ou 1 ( xn=1 seulement si wn ≤ W)
- Solution optimale avec xn = 0 : solution optimale pour W et {1..n-1}
- Solution optimale avec xn =1 (si wn ≤ W) : objet n + solution optimale pour W-wn et {1..n-1} (preuve par substitution)
- Solution optimale pour {1,.., n} et W: la meilleure des deux
‣ Sous-problèmes: valeurs optimales avec capacité w≤W et ensemble d’objets {1,..,j }
K(w, j) = max { K(w, j-1), K(w-wj, j-1) + vj } j≥1, w≥0où le deuxième cas est présent seulement si wj ≤ w
K(0, j)=K(j, 0) =0
‣ Ordre de résolution des sous-problèmes : par lignes (ou par colonnes)
Problème du sac à dos sans répétition
‣ Algorithme bottom-up
‣ Optimisation: si les sous-problèmes sont résolus par colonnes, garder seulement la dernière colonne calculée. Complexité en espace O(W)
‣ Construction de la solution, memoïsation: standard
Sac-a-dos-01( W, w1...wn , v1...vn )
Tableau K[0...W, 0..n]; Initialiser tous les K[0, j] et K[w, 0] à 0for (w ←1 to W ) do
for (j ← 1 to n) doif (wj ≤ w) K[w, j] ← max { K[w, j-1], K[w-wj, j-1] + vj }
else K[w, j] ← K[w, j-1]endif
endforendfor return K[W, n] Coût : Θ(n W)
Ordonnancement d'intervalles pondérés (Exercice)
Entrée: Un ensemble de n intervalles (de temps), chacun avec une valeur associée, vi, i=1..n
Problème: Sélectionner un sous-ensemble S⊆{1,.., n} d’intervalles disjoints avec valeur totale maximale
‣ Applications: ordonnancement de tâches pondérées sur une ressource mono-tâche
Exercice: Proposer un algorithme de programmation dynamique (bottom-up et top-down) qui résout le problème de la sélection d’intervalles pondérés. Analyser sont temps d'exécution.
v1= 2
v2=5
v3=2
Algorithmes de programmation dynamique sur les graphes
‣ Plus courts chemins dans des graphes pondérés arbitraires
‣ avec un nombre maximal d'arêtes
‣ entre toutes les paires de nœuds (Floyd-Warshall)
‣ Problème du voyageur de commerce
Algorithmes gloutons
Algorithmes gloutons
‣ Approche gloutonne adaptée si le problème d’optimisation à une sous-structure optimale de forme particulière (sous-structure optimale avec choix glouton)
‣ Rappel: sous-structure optimale
‣ les différentes choix possibles sur une partie de la solution partitionnent l’espace des solutions
‣ dans chaque partition la solution optimale est donnée par le choix correspondant et la solution optimale d’un sous-problème
‣ ⇒ la solution optimale est la meilleure parmi les solutions optimales de chaque partition
‣ Sous-structure optimale avec choix glouton
‣ Il existe un choix sur une partie de la solution (le choix glouton) tel que:
solution optimale : choix glouton + solution optimale d’un sous-problème
‣ ⇒ trouver la solution optimale: réitérer le choix glouton
‣ choix glouton : typiquement celui qui maximise une certaine notion de bénéfice “immédiat”
‣ Sous-structure optimale avec choix glouton : cas particulier de la sous-structure optimale où une partition est toujours meilleure que les autres
Rappel: ordonnancement d’intervalles pondérés
Entrée: Un ensemble T de n intervalles, chacun avec une valeur associée, vi , i=1..n
Problème: Sélectionner un ensemble S⊆T d’intervalles disjoints avec valeur maximale
Une sous-structure optimale : (pas la plus adaptée à une solution de programmation dynamique)
‣ Soit Ti l'ensemble d’intervalles qui commencent après la fin de l’intervalle i
‣ choix possibles : le premier intervalle d’une solution peut être 1 ou 2 ou...ou n
‣ solution optimale ayant premier intervalle i : {i} ∪ Opt(Ti)
‣ solution optimale Opt(T) : la solution {j} ∪ Opt(Tj) ayant valeur maximale, i.e. où j est t.q.
vj+ v(Opt(Tj)) = max { vi+ v(Opt(Ti)), i = 1..n }
Cela devient une sous-structure optimale avec choix glouton dans le cas particulier où les intervalles sont non-pondérés
Ordonnancement d’intervalles (non-pondérés)
Entrée: Un ensemble T de n intervalles
Problème: Sélectionner un sous-ensemble S⊆T d’intervalles disjoints de cardinalité maximale
‣ Cas particulier du problème pondéré avec vi=1, i = 1..n
‣ Sous-structure optimale:
‣ Soit Ti l'ensemble d’intervalles qui commencent après la fin de l’intervalle i
‣ choix possibles : le premier intervalle d’une solution peut être 1 ou 2 ou...ou n
‣ solution optimale ayant premier intervalle i : {i} ∪ Opt(Ti)
‣ solution optimale Opt(T): la solution de cardinalité maximale parmi {i} ∪ Opt(Ti) , i = 1..n
‣ Si i se termine plus tôt que j ⇒ Ti ⊇Tj ⇒ |Opt(Ti)| ≥ |Opt(Tj)|
⇒ Sous-structure optimale avec choix glouton:
Opt(T) = {j} ∪ Opt(Tj) où j est l’intervalle qui se termine plus tôt dans T
Remarque: dans le cas pondéré :
Ti ⊇Tj ⇒ v( Opt(Ti) ) ≥ v( Opt(Tj) ) vi + v( Opt(Ti) ) ≥ vj+ v( Opt(Tj) )⇒
Ordonnancement d’intervalles (non-pondérés)
‣ Algorithme glouton:
trier les intervalles de T par temps de fin croissant, soit 1...n cet ordre
S← ∅; //solution courante
f ← 0; //temps de fin du dernier intervalle ajouté à S for ( i ← 1 to n ) do
if ( début(i) ≥ f )
S ← S ∪ {i} // i est l’intervalle qui se termine le plus tôt après f
f ← fin(i)
endfor
return S
‣ Coût: Θ(n) si les intervalles sont déjà triés par temps de fin, sinon Θ(n log n)
Ordonnancement de tous les intervalles (Exercice)
Entrée: Un ensemble T de n intervalles
Problème: partitionner T en T1..Tr de telle sorte que les intervalles dans chaque Ti soient disjoints et r soit minimale
Application: affecter un ensemble de tâches à plusieurs ressources (mono-tâche), en minimisant le nombre de ressources utilisées
Ex. tâches/intervalles = cours avec un créneau horaire, ressources = salles de cours
Exercice.
a) Relier le problème de l'ordonnancement de tous les intervalles à un problème de coloration de graphe.
b) Proposer un algorithme glouton pour le problème de l'ordonnancement de tous les intervalles et analyser son coût. Discuter la relation entre ce coût et la réduction a)
7 8
6
3
4 5
1 2
7 8
6 3
4 5
1 2
Ordonnancement de tâches avec dates limite
Entrée: Un ensemble de n tâches. Chaque tâche i a une durée di et une date limite ti avant laquelle elle doit être exécutée. Un temps initial s.
Problème: affecter un intervalle [si, fi] de durée di à chaque tâche i, de telle sorte que si ≥ s pour tout i, les intervalles des différentes tâches soient disjoints et le délai de l’ordonnancement soit minimal.
délai d’une tâche: ℓ(i) =
délai de l’ordonnancement S : ℓ(S) = max { ℓ(i), i = 1..n }
‣ Sous structure optimale avec choix glouton:
Lemme Une solution optimale pour les tâches 1..n triées par date limite croissante, à partir de l’instant s est donnée par:
‣ la tâche 1 sur l’intervalle [s, s+ d1]
‣ une solution optimale pour les tâches 2..n à partir du temps s+ d1 (si n >1)
‣ Algorithme glouton
trier les tâches par date limite croissante ; f ←s
for (i = 1 to n) do affecter à la tâche i l’intervalle [f, f+di] ; f← f+di endfor
fi - ti si fi > ti,
0 si fi ≤ ti
Ordonnancement de tâches avec dates limite
Preuve du lemme
Soit 1..n les tâches triées par date limite croissante
On démontre qu’il existe une solution optimale S donnée par:
‣ la tâche 1 sur l’intervalle [s, s+ d1] (le choix glouton)
‣ une solution S’ pour les tâches 2..n à partir du temps s + d1 (une solution du sous-problème)
‣ S’ est optimale : par substitution (ℓ(S) = max { ℓ(1), ℓ(S’) } )
Paradigme classique de preuve: - partir d’une solution optimale T
- la “transformer” en une solution avec les propriété de S, en préservant l’optimalité
Ordonnancement de tâches avec dates limite
Soit T un ordonnancement à délai minimal
‣ recompacter les intervalles de T pour que il n’y ai pas de temps où la ressource est inactive
‣ tant qu’il y a deux intervalles contigüs j et k tels que j précède k en T mais tj > tk
‣ échanger j et k dans T
S est l'ordonnancement résultant (il satisfait les conditions demandées)
‣ Recompacter n’empire pas le délai de TLe temps de fin de chaque tâche reste inchangé ou diminue ⇒
le délai de chaque tâche se réduit ⇒ ℓ(T’) ≤ℓ(T)
fkfj
fkfj
tj tk
tj tk
T
T’
Ordonnancement de tâches avec dates limite
‣ Chaque échange n’empire pas le délai de T
‣ fk diminue ⇒ le délai de la tâche k se réduit ou reste inchangé ⇒ ℓ’(k) ≤ ℓ(k) ≤ℓ(T)
‣ fj augmente: le délai de la tâche j peut augmenter, mais on démontre ℓ’(j) ≤ℓ(k) ≤ℓ(T):
‣ si fj’ ≤ tj ⇒ ℓ’(j) = 0 ≤ ℓ(k)
‣ si fj’ > tj ⇒ ℓ’(j) = fj’ - tj et fk = fj’ > tj ≥ tk ⇒ ℓ(k) = fk -tk ⇒
ℓ’(j) = fj’ - tj = fk -tj ≤ fk - tk = ℓ(k)
‣ les temps de fin (et donc les délais) de toutes les autres tâches restent inchangés ‣ ⇒ ℓ(T’) ≤ℓ(T)
‣ Après toutes les transformations l’ordonnancement est optimal
ℓ(k)
ℓ’(j)
tk ≤ tj
fkfj
fj'fk'
tjtk
tjtk
T
T’
Problème du sac à dos fractionnaire
Entrée: n objets avec poids w1, ..wn et valeurs v1, .., vn ; un poids maximal W (tous entiers positifs)
Problème: trouver x1...xn ∈ [0, 1], rationnels tels que: x1 w1 + ...+ xn wn ≤ W et x1 v1 + ...+ xn vn est maximale
‣ Sous-structure optimale avec choix glouton
Supposer les objets 1..n triés par valeur par unité de poids (vi /wi) décroissante
Lemme. Une solution optimale avec capacité W>0, poids w1, ..wn et valeurs v1, .., vn est donnée par:
1) x1 : une fraction maximale de l’objet 1 (l’objet à vi /wi maximal) :
x1 = 1 si w1 ≤ W, x1 = W/w1 sinon
2) x2, .., xn : une solution (optimale) avec capacité W - x1w1, poids w2, ..wn et valeurs v2, .., vn
Si W=0 ⇒ xi = 0 est la seule solution (optimale)
Problème du sac à dos fractionnaire
‣ Algorithme glouton:
trier les objets par vi/wi décroissant, soit 1..n cet ordre
R ←W //capacité résiduelle
for (i ← 1 to n) do
// prendre la fraction maximale de l’objet i avec capacité résiduelle R
if ( wi ≤ R) xi←1; R ←R - wi
else xi ← R/wi; R ←0
endfor
‣ Coût O(n log n)
‣ des solutions linéaires existent
Problème du sac à dos fractionnaire
‣ Preuve du Lemme:
Il suffit de démontrer que il existe une solution S optimale avec x1= fraction maximale de l’objet 1 (à valeur relative maximale). (Conclure par substitution)
Soit T= y1 .. yn une solution optimale;
Soit x1 = 1 si w1 ≤ W et x1 = W/w1 sinon (la fraction maximale de l’objet 1)
Construction de S:
Intuition:
‣ remplacer y1 par x1 en réduisant, si nécessaire les fractions des autres objets.
‣ Dans S le même poids est occupé avec un meilleur rapport valeur/unité de poids
‣ ⇒ valeur(S) ≥ valeur(T) ⇒ S est optimale
‣ L’approche gloutonne ne marche par pour la version 0/1 du problème de sac à dos :
w1 = 10 v1 =60 w2 = 20 v2 = 100 W=20
solution optimale: x1 = 0 x2 = 1
Codage de Huffman
‣ Utilisé pour représenter du texte (séquence de caractères) ou des données de façon compacte
‣ Idée: représenter chaque caractère avec un nombre de bits variables
‣ mot plus court pour les caractères qui apparaissent plus fréquemment dans le texte
‣ encodage du texte : concaténation des mots représentants chaque caractère
‣ En entrée : la table de fréquences des caractères :pour chaque caractère c son nombre freq(c) d’occurrences dans le texte
‣ Objectif : produire un encodage préfixe : aucun mot du codage n’est un préfixe d’un autre mot du codage
‣ permet le décodage : un caractère dans le texte se termine lorsque le préfixe lu coïncide avec un mot du code (parsing unique du texte)
‣ Exemple de codage préfixe
‣ Taille de l’encodage du texte S:
a b c d e f
0 101 100 111 1101 1100
γ texte encodé : 1100111101
décodage 1100111101 : f d b
freq(c) · |γ(c)|
Codages préfixes et arbres binaires
‣ Chaque codage préfixe γ peut être représenté comme un arbre binaire Tγ dont les feuilles correspondent aux caractères :
‣ Si γ est vide, Tγ est l’arbre vide
‣ Si γ={ (a,ε) } , Tγ est l’arbre composé par une seule feuille étiquetée a
‣ Sinon soit γi , i=0,1 l’ensembles des codes (a, w) tels que (a,iw) est dans γ :
Tγ est composé par un racine, un sous-arbre gauche Tγ0 et un sous-arbre droit Tγ1
‣ Chaque arbre binaire dont les feuilles sont étiquetées par des caractères distincts représente un codage préfixe :
‣ Le mot qui encode un caractère est donné par le chemin de la racine à la feuille correspondante (gauche: 0, droite: 1 )
a b c d e f
0 101 100 111 1101 1100
b dc
ef
a
0 1
0 1
0 1 0 1
0 1
Codage de Huffmann
‣ Entrée : un ensemble de caractères C avec fréquences associées
‣ Problème : Trouver le codage préfixe γ qui minimise la taille de l’encodage
↕Trouver l’arbre binaire T dont les feuilles sont étiquetées par les caractères de C qui
minimise le coût B(T) = freq(v) · p(v) où p(v) est la profondeur de la feuille v
‣ Lemme. Il existe un codage préfixe optimal dont l’arbre binaire est complet (chaque nœud interne a deux fils)
‣ Un arbre optimal peut être calculé par un algorithme glouton : codage de Huffman
freq(c) · |γ(c)|
Codage de Huffman : algorithme glouton
‣ Sous-structure optimale avec choix glouton
Lemme. Soit les caractères c1...cn triés par fréquence croissante.Un arbre (codage préfixe) optimal T pour les caractères ci, i = 1..n est obtenu par la composition de :
‣ un nœud z avec deux fils c1 et c2,
‣ un arbre optimal T’ pour les caractères z, c3,..., cn avec freq(z) =freq(c1) + freq(c2)
‣ Algorithme glouton sur l’ensemble de caractères C
Q ← C
for ( i ← 1 to |C|-1 ) do // construction des |C|-1 nœuds internes
extraire de Q les deux caractères c1 et c2 à fréquence minimale
créer un nouveau nœud z ; z.gauche ← c1; z. droit ← c2
z.freq ← c1.freq + c2.freq;
Q ← Q ∪ {z}
endfor
‣ Coût : O(n log n) si Q est implementé comme un tas binaire
c2: f2c1: f1
z: f1 + f2
T’
Codage de Huffman : sous-structure optimale
Preuve du lemme. On démontre : Il existe un arbre optimal qui contient un nœud z avec deux fils feuilles c1 et c2.
Conclure par substitution ( B(T) = B(T’) + freq(c1) + freq (c2) )
- Soit T* un arbre optimal. Sans perte de généralité T* est complet.
- Soit z1 et z2 deux feuilles frères de profondeur maximale dans T*
- Pour i = 1,2, si ci ∈ {z1, z2} soit zi = ci
- Échanger z1 avec c1 et z2 avec c2
- Soit T l’arbre après l'échange et p() la profondeur des nœuds dans T*
- B(T) = B(T*) - p(z1)·freq(z1) - p(z2)·freq(z2) - p(c1)·freq(c1) - p(c2)·freq(c2) + p(z1)·freq(c1) + p(z2)·freq(c2) + p(c1)·freq(z1) + p(c2)·freq(z2)
B(T) - B(T*) = ( freq (z1) - freq(c1) ) ( p(c1) -p(z1) ) + ( freq (z2) - freq(c2) ) ( p(c2) -p(z2) ) ≤ 0 ⇒ T optimale
( freq (zi) ≥ freq(ci) et p(ci) ≤ p(zi), for i=1,2 ) ☐
Matroïdes
‣ Une théorie des algorithmes gloutons: un problème type avec sous-structure optimale basée sur un choix glouton
‣ Plusieurs problèmes peuvent être formulés comme un problème d’optimisation sur un matroïde : algorithme glouton générique
Définition. Un matroïde est un couple ( S, I ) où S est un ensemble fini et I est une famille non vide de sous-ensembles de S telle que:
1) (I héréditaire) A ∈ I et B⊆A ⇒ B ∈ I
2) (Propriété d'échange) A, B ∈ I et |A| <|B| ⇒ il existe y ∈ B\A , A∪{y} ∈ I
Les éléments de I sont appelés ensemble indépendants.
- extension de A∈ I : élément x ∈ S\A tel que A∪{x} ∈ I
- A∈ I maximal : A n’a pas d’extension
Matroïdes pondérés
‣ Matroïde pondéré : un matroïde M=( S, I ) avec une fonction de poids w telle que w(x) > 0 pour tout x∈S
‣ Poids d’un sous-ensemble A :
‣ Sous-ensemble optimal de M : élément A ∈ I de poids maximal
‣ Remarque: chaque sous-ensemble optimal de M est maximal ( w positive )
‣ Contraction de M par l'élément x M(x) =( S’, I’ )
- S’= S \ {x}- I’ ={ B ⊆ S \ {x} | B ∪ {x} ∈ I } - M(x) est un matroïde
‣ Sous-structure optimale avec choix glouton
Lemme. Pour un matroïde M=( S, I ), soit les éléments de S triés par poids décroissant. Alors il existe un sous-ensemble optimal de la forme A={x} ∪ A’ , où
- x est la première extension de ∅ en M, dans l’ordre de S
- A’ est un sous-ensemble optimal de M(x)
si x existe. Sinon ∅ est le seul sous-ensemble optimal.
Matroïdes pondérés : sous-structure optimale
Preuve du lemme.
‣ Si x n’existe pas, ∅ est le seul sous-ensemble indépendant ( par le fait que I est héréditaire ).
‣ Sinon, soit B un sous-ensemble optimal de M. Si x ∈ B poser A=B, sinon construire A comme suit:
A ← {x} tant que |A|<|B|
choisir un élément y ∈ B\A tel que A∪{y} ∈ I (propriété d'échange)A ← A∪{y}
‣ Quand |A| =|B| : A= B \{z} ∪ {x} ⇒ w(A) = w(B) - w(z) +w(x) ≥ w(B)
( w(x) ≥ w(z) parce que : z ∈ B ⇒ (I héréditaire) {z} ∈ I ⇒ z est une extension de ∅ )
⇒ A optimal et A= {x} ∪ A’
‣ A’ ⊆ S\{x} et A’ ∪{x} ∈ I ⇒ A’ est un sous-ensemble indépendant de M(x) ⇒ A’ est
optimal par substitution ( w(A) = w(x) + w(A’) ) ☐
Matroïdes pondérés : sous-structure optimale
On dénote M( x0, ..., xn ) la suite de contractions ( ( M(x0) )(x2) ....)(xn)
Par récurrence sur n on peut démontrer :
M( x0, ..., xn ) =( S’, I’ )
- S’= S \ {x0, ..., xn}- I’ ={ B ⊆ S \ {x0, ..., xn} | B ∪ {x0, ..., xn} ∈ I }
‣ Corollaire. Pour un matroïde M=( S, I ) soit les éléments de S triés par poids décroissant. Alors il existe un sous-ensemble optimal de la forme
A={x0 x1 ... xk} , où
- x0 est la première extension de ∅ en M, dans l’ordre de S
- xj est la première extension de ∅ en M( x0, x1, ..., xj-1 ), dans l’ordre de S, pour j≥ 1
- k est l’indice maximal tel que xk existe (A=∅ si x0 n’existe pas)
‣ Lemme. Pour tout x0, x1, ..., xj-1 ∈ S, la première extension de ∅ en M( x0, x1, ..., xj-1 ) coïncide avec la première extension de { x0, x1, ..., xj-1 } en M (dans l’ordre de S).
Preuve: par la caractérisation de M( x1, ..., xj-1 ) ci dessus.
Matroïdes pondérés : algorithme glouton
‣ Algorithme glouton sur le matroïde M=(S, I) (première version)
trier S par poids décroissant; A←∅
répéter
trouver le premier élément x ∈ S\A t.q. A ∪{x} ∈ I // la première extension de A
si x existe A←A∪ {x}
jusqu’à ce que x n’existe pas
‣ Lemme. Au début de chaque itération de l’algorithme ci-dessus, la première extension x de A suit tous les éléments de A dans S
‣ Preuve. Première itération: trivialement vrai (A est vide)
- Supposer vrai au début de l'itération i
- À la fin de cette itération Ai+1 =Ai ∪ {xi} où xi est la première extension de Ai et xi suit Ai dans S
- Au début de l'itération i+1: la première extension xi+1 de Ai+1 est une extension de Ai (I héréditaire) ⇒ xi+1 suit xi qui suit Ai ⇒ xi+1 suit Ai+1 ☐
Matroïdes pondérés : algorithme glouton
Algorithme glouton sur le matroïde M=(S, I)
trier S par poids décroissant; soit S = s1..sn après le tri
A←∅
for ( i= 1 to n ) do
if ( A ∪{si} ∈ I ) A← A∪ {si}
endfor
return A
‣ Coût O( n log n + n f(n) ) où f(n) est le coût de vérifier que un ensemble est indépendant
Exercice. Arbre couvrant minimal.
Soit G=(V, E) un graphe connexe non-orienté muni d’une fonction w de poids des arêtes. Un arbre couvrant de G est un sous-graphe A=(V, E’) avec E’ ⊆ E, tel que A est un arbre (i.e. un graphe non-orienté connexe et acyclique).
Le poids d’un ensemble d'arêtes D est la somme .
Un arbre couvrant minimal est un arbre couvrant de poids minimum.
‣ Montrer que le problème de trouver un arbre couvrant minimal d’un graphe peut s’exprimer comme un problème de sous-ensemble optimal dans un matroïde. En déduire un algorithme glouton et analyser sont coût.
Exercice. Ordonnancement de tâches de durée unitaire avec dates limite et pénalités.
Soit {a1,...,an} un ensemble de tâches de durée 1, chaque tâche ai ayant une date limite 1≤ti ≤n et une pénalité wi. Un ordonnancement est une permutation de {a1,...,an}; il correspond à un ordre d'exécution des tâches sur des intervalles unitaires contigüs, à partir de l’instant 0.
Dans un ordonnancement une tâche ai est en retard si elle finit après ti. Sinon la tâche est dite en avance.
La pénalité totale d’un ordonnancement T est la somme des pénalités des tâches en retard dans T.
Problème: trouver un ordonnancement qui minimise la pénalité totale.
‣ Montrer que le problème de l'ordonnancement de tâches de durée unitaires avec dates limites et pénalités peux s’exprimer comme un problème de sous-ensemble optimal dans un matroïde. En déduire un algorithme glouton.
Algorithmes gloutons sur les graphes
‣ Algorithmes d’arbre couvrant minimum (algorithmes de Prim et Kruskal)
‣ Algorithme de Dijkstra (plus courts chemins)
Bibliographie
‣ S. Dasgupta, C. Papadimitriou, U. Vazirani Algorithms McGraw Hill 2006disponible en ligne http://www.cs.berkeley.edu/~vazirani/algorithms/
‣ J. Kleinberg, E. Tardos Algorithm Design Addison-Wesley 2005
‣ T.H. Cormen, C. E. Leiserson, R. L. Rivest and C. Stein.Introduction to Algorithms. 3e édition, The MIT Press 2009