i Université de Rennes I ENSSAT Lannion EII 2 et LSI 2 ALGORITHMIQUE AVANCÉE Travaux Dirigés et Travaux Pratiques Laurent Miclet et Patrick Bosc 8 mars 2007 ii The Feynman Problem-Solving Algorithm : 1. write down the problem ; 2. think very hard ; 3. write down the answer. Murray Gell-mann Première version Mars 2004 Special credits to : Nelly Barbot Sabri Bayoudh Marc Guyomard Florent Nicard Table des matières 0 Démonstrations, récurrences. 0.1 Rappels sur la démonstration. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 0.1.1 Démonstration par l’absurde. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 0.1.2 Démonstration par récurrence . . . . . . . . . . . . . . . . . . . . . . . . . . . . 0.1.3 Démonstration par récurrence partielle . . . . . . . . . . . . . . . . . . . . . . . 0.1.4 Démonstration par récurrence totale . . . . . . . . . . . . . . . . . . . . . . . . 0.1.5 Démonstration par récurrence descendante . . . . . . . . . . . . . . . . . . . . 0.1.6 Démonstration par récurrence à plusieurs indices . . . . . . . . . . . . . . . . . 0.2 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 1 2 3 3 4 6 9 1 Complexité d’un algorithme 1.1 Rappels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1.1 Ordres de grandeur de complexité : rappel des définitions. . . . . . . . . . . . . 1.1.2 Ordres de grandeur de complexité : remarques. . . . . . . . . . . . . . . . . . . 1.1.3 Temps de calcul pratique. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 13 13 13 13 14 2 Invariants, itération. 2.1 Rappels sur la logique de l’itération. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Exercices. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 19 19 3 Diviser et Diminuer pour Régner 3.1 Rappels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.1 Formule de récurrence 1 (Diviser n en en b parties) : . . . . . . . . . . . . . . 3.1.2 Formule de récurrence 2 (Diminuer n à n − 1) : . . . . . . . . . . . . . . . . . 3.2 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 21 21 22 23 4 Essais successifs 4.1 Rappels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1.2 Algorithmes génériques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 37 37 38 39 5 Programmation par Séparation et Evaluation Progressive 5.1 Rappels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.1.1 Principe : comparaison avec Essais Successifs . . . . . . . . . . . . . . . . . . . 5.1.2 Algorithme générique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.1.3 Un point de vue plus général. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 51 51 51 53 53 iii iv TABLE DES MATIÈRES 6 Programmation dynamique. 6.1 Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2.1 Séquences et automates. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2.2 Optimisation combinatoire. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2.3 Dénombrements. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2.4 Graphes : plus courts chemins et autres. . . . . . . . . . . . . . . . . . . . . . . 55 55 56 56 62 67 68 7 Algorithmes gloutons 71 8 Mélanges 8.1 Un problème d’optimisation particulier : le "sac à dos" . . . . . . . . . . . . . . . . . . 8.1.1 Trois problèmes légèrement différents. . . . . . . . . . . . . . . . . . . . . . . . 8.1.2 Trois solutions extrêmement différentes . . . . . . . . . . . . . . . . . . . . . . 8.2 Exercices. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.3 Travaux Pratiques. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 75 75 77 78 79 99 Exercices en cours de rédaction. 87 99 Exercice en test. 103 99 Exercice en test. 107 TABLE DES MATIÈRES v Notations, conventions. Notations. Nombres. N est l’ensemble des nombres entiers naturels : {0, 1, . . .}. Q est l’ensemble des nombres rationnels et Q+ celui des rationnels strictement positifs. R est l’ensemble des nombres réels et R+ celui des réels strictement positifs. C est l’ensemble des nombres complexes. La valeur entière du nombre réel a est notée ⌊a⌋ ; la notation ⌈a⌉ désigne le premier entier strictement supérieur à a. Vecteurs, matrices et tableaux. Alphabets, séquences. – On appelle alphabet Σ un ensemble fini de lettres dont le cardinal est notée |X|. – Une séquence, ou chaîne, ou mot ) sur Σ est une suite finie de lettres de Σ. La longueur |u| d’une séquence u est son nombre de lettres. Par exemple pour l’alphabet Σ = {a, b, c} et les séquences u = acbb et v = cab, on a |u| = 4 et |v| = 3. – On appelle préfixe d’une séquence une séquence par laquelle elle commence et suffixe d’une séquence une séquence par laquelle elle finit. Par exemple, le préfixe de longueur 2 de u est ac, le suffixe de longueur 3 de v est cab. – On note u[i] la lettre numéro i de u. Par exemple : u[3] = b. Pour 1 ≤ i ≤ j ≤ |u|, on note u[i : j] = ui , ui+1 , . . . , uj un facteur 1 de u. Par exemple, u[3 : 4] = bb. – On note ǫ la séquence de longueur nulle. – La concaténation des séquences u et v, qui consiste à créer une séquence de longueur |u| + |v|, ayant u pour préfixe et v pour suffixe, se note sans (si nécessaire avec) signe d’opération : uv (ou u.v). La concaténation est associative, a ǫ pour élément neutre, mais n’est pas commutative : (uv)w = u(vw) = uvw, ǫu = uǫ = u, mais en général uv 6= vu. – w est une sous-séquence 2 de u si on peut trouver toutes les lettres de w dans u, dans le même ordre. Plus formellement : w est une sous-séquence de u s’il existe une suite d’entiers (i1 , . . . , i|w| ), avec 1 ≤ i1 ≤ . . . ≤ i|w| ≤ i|u| et w = ui1 . . . ui|w| . Un facteur est un type particulier de sous-séquence : par exemple, ab est une sous-séquence de acbb, mais n’en est pas un facteur. Logos. Les exercices sont étiquetés selon leur niveau de difficulté par un seul difficile) ou trois ✍✍✍(très difficile, ou niveau recherche). ✍(facile), ou deux ✍✍(plus Algorithmes. Les algorithmes de ce document sont écrits dans un pseudo-code banal dont voici un exemple : 1: Appel : Algo(T , 1, n, m, Decision) ; imprimer(decision) 1 En anglais : substring. 2 En anglais : subsequence vi TABLE DES MATIÈRES précondition : n ≥ 1 2: procedure Algo(T , i, j, m : in ; Decision : out) 3: début 4: si i = j et m = T (i) alors 5: Decision ← (m = T (i)) /% L %/’affectation se note ← 6: sinon 7: si m ≤ T (⌊ i+j 2 ⌋) alors 8: Algo(T , i, ⌊ i+j 2 ⌋, m, Decision) 9: sinon 10: Algo(T , ⌊ i+j 2 ⌋ + 1, j, m, Decision) 11: fin si 12: fin si 13: fin La procédure récursive appelée Algo recherche par dichotomie si la valeur m est dans le tableau trié T . La réponse est donnée par la variable booléenne Decision. Les faits que T est un tableau d’entiers triés par ordre croissant, que m est un entier et que Decision est un booléen ne sont pas précisés dans ce pseudo-code, mais dans l’énoncé de l’exercice qui le précède. Ceci pour alléger et pour ne présenter que l’essentiel. On remarque que les variables d’appel de la procedure sont qualifiées ici comme in ou out, ou si besoin, in, out. La signification en est que les variables in sont nécessaires et ne sont pas modifiées au cours de la procédure, les variables out sont calculées par la procédure. Cette convention peut être allégée si cette information est inutile, en notant simplement : procedure Algo(T , i, j, m, Decision) Dans le cas d’une fonction, le résultat, s’il vaut r, est transmis sous la forme : resultat(r). Chapitre 0 Démonstrations, récurrences. 0.1 Rappels sur la démonstration. 0.1.1 Démonstration par l’absurde. Le raisonnement par l’absurde ou démonstration par l’absurde repose sur le principe logique du tiers exclu, qui affirme qu’une assertion qui ne peut pas être fausse est forcément vraie. Chercher à démontrer une proposition P par l’absurde se fait comme suit : – supposer non P (c’est à dire supposer que P est fausse), – constater une contradiction entre une implication de cette supposition et la négation de P. Dans ce cas, P ne peut pas être fausse et P est donc vraie. Si on ne constate pas de contradiction, on ne peut rien dire sur P. Par exemple, considérons la proposition P « il n’y a pas de plus petit nombre rationnel strictement plus grand que 0 ». Dans un raisonnement par l’absurde, nous commençons par supposer la négation de P, ce qui s’énonce : « il existe un plus petit nombre rationnel strictement positif », Appelons-le r. Nous allons maintenant déduire une contradiction. Soit s = r/2. Par construction, s est un nombre rationnel1 , strictement plus grand que 0 et strictement plus petit que r. Mais cela est contradictoire avec la négation de P, qui affirme que r était le plus petit nombre rationnel. Ainsi nous pouvons conclure que la proposition d’origine P est nécessairement vraie : il n’existe donc pas de plus petit nombre rationnel strictement plus grand que 0. Un exemple : la démonstration de l’irrationnalité de la racine carrée de 2. Nous voulons démontrer : il n’existe pas de rationnel positif dont le carré est 2, autrement dit : la racine carrée de 2 est irrationnelle. – Supposons qu’il existe un élément x = p/q de Q+ (ensemble des rationnels positifs) tel que x2 = 2, avec p et q premiers entre eux (c’est à dire que p/q est une fraction irréductible). On a : (p/q)2 = 2 donc p2 = 2q2 – Comme p2 = 2q2 on peut dire que p2 est un multiple de 2 ou encore que 2 divise p2 . Par conséquentmanote 2 divise aussi p. – Comme 2 divise p , il existe donc un entier naturel p ′ tel que p = 2p ′ et donc p2 = 4p ′2 c’est à dire 2q2 = 4p ′2 – 2q2 = 4p ′2 implique q2 = 2p ′2 , donc q2 est un multiple de 2, ou encore 2 divise q2 , donc 2 divise q. 1 Si vous pensez que cette affirmation mérite une démonstration, donnez-la. 1 2 CHAPITRE 0. DÉMONSTRATIONS, RÉCURRENCES. ... n0 n−1 n Fig. 1 – Géométrie de la démonstration par récurrence. La propriété doit être vraie sur la zone grise (initialisation à n0 ). La récurrence est alors : si le fait qu’elle soit vraie dans la zone hachurée (pour n − 1) implique qu’elle est vraie dans la zone doublement hachurée (pour n) , alors elle est vraie pour tout entier supérieur ou égal à n0 . – 2 divise à la fois p et q, ce qui est contradictoire avec l’hypothèse que p et q sont premiers entre eux. En conclusion, il n’existe pas de rationnel positif dont le carré est 2. Comme il n’existe pas non plus de rationnel négatif dont le carré est 2 (sinon l’opposé de ce nombre serait un rationnel positif dont le carré serait 2), la racine carrée de 2 est irrationnelle2 . 0.1.2 Démonstration par récurrence Cette démonstration (aussi appelée par « récurrence faible »ou « récurrence simple ») s’argumente ainsi : si une propriété est vraie pour un certain n0 et si le fait qu’elle soit vraie pour un certain n (supérieur à n0 ) implique qu’elle est vraie pour n+1, alors elle est vraie pour tout les entiers supérieurs ou égaux à n0 . Soit une propriété ou une formule P(n), dépendant de l’entier n ∈ N. Si on peut démontrer les deux propriétés suivantes : Initialisation. P(n0 ) est vraie pour un certain n0 ∈ N Récurrence. ∀n ≥ n0 , P(n) vraie ⇒ P(n + 1) vraie Alors on peut conclure : Conclusion. P(n) est vraie pour tout entier n supérieur ou égal à n0 . On appelle hypothèse de récurrence le fait de supposer vraie P(n), pour (essayer de) démontrer P(n + 1). L’initialisation s’appelle aussi la base et l’étape de récurrence est aussi appelée induction. Exemple Démontrons par récurrence la formule : n X i= i=1 n(n + 1) 2 Initialisation. Cette formule est vraie pour n0 = 1. En effet, on a : 1 X i=1 i=1 et la formule, essayée pour n0 = 1, donne le bon résultat : 1 X 1(1 + 1) i=1 2 2 Cette démonstration est due à Euclide, environ 250 av. J.-C. =1 0.1. RAPPELS SUR LA DÉMONSTRATION. 3 Récurrence. Supposons la formule vraie pour un certain n et essayons de montrer qu’elle est vraie pour n + 1. Autrement dit, nous essayons de prouver que l’hypothèse de récurrence n X i= i=1 n(n + 1) 2 implique n+1 X i= i=1 (n + 1)(n + 2) 2 C’est en effet exact, puisque : n+1 X i=1 n X i) + (n + 1) i = ( i=1 = = = n(n + 1) + (n + 1) 2 n2 + n + 2n + 2 2 (n + 1)(n + 2) 2 par l’hypothèse de récurrence Conclusion. La formule est vraie pour n = 1. De plus, si elle est vraie pour n, est vraie pour n + 1. Par conséquent, cette démonstration par récurrence a prouvé qu’elle est vraie pour tout entier strictement positif. 0.1.3 Démonstration par récurrence partielle Soit une propriété P(n), dépendant de l’entier n ∈ N. Si on peut démontrer les deux propriétés suivantes : Initialisation. P(1) est vraie Récurrence. ∀n ≥ 1 avec n = 2k , k ∈ N, k ≥ 1 P(n) vraie ⇒ P(2n) vraie Alors on peut conclure : Conclusion. P(n) est vraie pour tout n puissance entière de 2. Cette variante peut aussi servir à démontrer une propriété sur les nombres pairs, ou tout sous-ensemble infini de N constructible par induction. Quand on l’a appliquée, on peut éventuellement utiliser la démonstration par récurrence descendante (voir le paragraphe 0.1.5). 0.1.4 Démonstration par récurrence totale La démonstration par récurrence totale (ou forte, ou généralisée) paraît d’abord plus difficile à appliquer que la récurrence classique. Elle est cependant commode et (à juste titre) très employée. Elle affirme que si une propriété est vraie pour un certain n0 , et que si le fait qu’elle soit vraie pour tous les entiers de n0 à un certain n implique qu’elle est vraie pour n + 1, alors elle est vraie pour tout entier supérieur ou égal à n0 . 4 CHAPITRE 0. DÉMONSTRATIONS, RÉCURRENCES. ... n0 n0 + 1 n−1 n Fig. 2 – Illustration géométrique de la démonstration par récurrence totale. La propriété doit être vraie sur la zone grise (initialisation pour n0 ). La récurrence est alors : si le fait qu’elle soit vraie dans la zone hachurée (de n0 + 1 à n − 1) implique qu’elle est vraie dans la zone doublement hachurée (pour n) , alors elle est vraie pour tout entier supérieur ou égal à n0 . Soit une propriété P(n), dépendant de l’entier n ∈ N. Si on peut démontrer les deux propriétés suivantes : Initialisation. P(n0 ) est vraie (pour n0 ∈ N) Récurrence. ∀n ≥ n0 , (∀k, n0 ≤ k ≤ n, P(k) vraie ) ⇒ P(n + 1) vraie Alors on peut conclure : Conclusion. P(n) est vraie pour tout n ≥ n0 . Exemple Démontrons par récurrence totale la propriété : Tout nombre entier supérieur ou égal à 2 est le produit d’un3 ou de plusieurs nombres premiers. Initialisation. Cette propriété est vraie pour n = 2, puisque 2 est lui-même premier. Récurrence. Supposons que tout entier strictement inférieur à n soit le produit d’un ou de plusieurs nombres premiers. Il y a deux cas exclusifs : – Soit n est premier, et n lui-même est alors le produit d’un nombre premier. – Soit n admet un diviseur d, différent de 1 et de n. On a donc n = dp, où d et p sont deux entiers strictement inférieurs à n et supérieurs à 1. Chacun d’eux est donc le produit d’un ou de plusieurs nombres premiers, et n est donc lui-même le produit de plusieurs nombres premiers. Conclusion. n est donc le produit d’un ou de plusieurs nombres premiers. 0.1.5 Démonstration par récurrence descendante Soit une propriété ou une formule P(n), dépendant de l’entier n ∈ N. Si on peut démontrer les deux propriétés suivantes : Initialisation. P(n) est vraie pour un sous-ensemble infini de N Récurrence. P(n) vraie implique P(n − 1) vraie Alors on peut conclure : Conclusion. P est vraie pour tout les entiers naturels. 3 Le « produit d’un nombre »est défini comme le nombre lui-même. 0.1. RAPPELS SUR LA DÉMONSTRATION. 5 Exemple Cet exemple4 utilise à la fois la variante de la démonstration classique par récurrence (voir le paragraphe 0.1.3) et la démonstration par récurrence descendante. Il s’agit de démontrer la fameuse inégalité entre la moyenne arithmétique et la moyenne géométrique de n nombres réels positifs : 1 (x1 x2 . . . xn ) n ≤ x1 + x2 + . . . + xn n (0.1) Dans un premier temps, on montre que cette propriété est vraie sur tous les entiers n qui sont une puissance de 2. En effet, la démonstration est triviale pour n = 1 et elle est facile pour n = 2 (il suffit 2 2 ) découle du fait qu’un carré est positif ou nul). de constater que l’inégalité x1 x2 ≤ ( x1 +x 2 La variante de la démonstration classique par récurrence s’énonce alors : supposons la propriété 1 (x1 x2 . . . xn ) n ≤ x1 + x2 + . . . + xn n (0.2) vraie pour n = 2k . Cette propriété est-elle alors vraie pour 2n = 2k+1 ? C’est bien le cas : il suffit de réécrire l’équation 0.1 sous la forme 12 1 1 1 (x1 x2 . . . x2n ) 2n = (x1 x2 . . . xn ) n (xn+1 xn+2 . . . x2n ) n 1 et d’utiliser le fait que la propriété est vraie pour n = 2 en posant y1 = (x1 x2 . . . xn ) n et y2 = 1 (xn+1 xn+2 . . . x2n ) n On en déduit : 1 y1 + y2 (y1 y2 ) 2 ≤ 2 ce qui démontre qu’elle est vraie pour 2n = 2k+1 . On sait maintenant que la propriété est vraie pour tous les entiers du type n = 2k , avec k entier. Il est temps d’utiliser le raisonnement par récurrence descendante. Où en sommes nous ? La propriété à démontrer est vraie pour un sous-ensemble infini de N. Il nous reste à essayer de montrer que si elle est vraie pour n’importe quel entier n, alors elle est vraie pour n − 1. Pour ce faire, il est commode de définir un certain nombre z par : z= x1 + x2 + . . . + xn−1 n−1 Comme la propriété est supposée vraie pour tout ensemble de n nombres , elle est en particulier vraie pour les nombres (x1 , x2 , . . . , xn−1 , z). Autrement dit : 1 (x1 x2 . . . xn−1 z) n ≤ x1 + x2 + . . . + xn−1 + z n Cette équation, grâce à la définition de z se ramène à : 1 (x1 x2 . . . xn−1 z) n ≤ (n − 1)z + z =z n On en déduit : (x1 x2 . . . xn−1 z) ≤ zn et pour finir : 1 (x1 x2 . . . xn−1 ) n−1 ≤ z = x1 + x2 + . . . + xn−1 n−1 ce qui est exactement la même chose que la propriété 0.1, mais pour n − 1 au lieu de n. Par récurrence descendante, la propriété 0.1 est donc vraie pour tout entier n. 4 Sa paternité a été attribuée à Cauchy ([MAN89]). 6 CHAPITRE 0. DÉMONSTRATIONS, RÉCURRENCES. .. . .. . m+1 m ... 1 1 n n+1 Fig. 3 – Illustration géométrique d’une démonstration par récurrence totale à deux indices. La propriété doit être vraie sur les zones infinies grises (initialisation). La récurrence est alors : si le fait qu’elle soit vraie dans la zone finie hachurée implique qu’elle est vraie dans la zone finie doublement hachurée, alors elle est vraie partout. 0.1.6 Démonstration par récurrence à plusieurs indices Quand une propriété dépend de deux (ou plus de deux) entiers et non plus d’un seul, la démonstration par récurrence prend littéralement une nouvelle dimension ; nous présentons certains cas à deux indices qui seront utiles par la suite. Donnons pour commencer un schéma possible de démonstration par récurrence totale à deux indices. Soit une propriété ou une formule P(n, m), dépendant de deux entiers n et m ∈ N. On cherche à la démontrer par récurrence. Un schéma possible est le suivant : Si on peut démontrer les deux propriétés suivantes : Initialisation. – P(i, 1) est vraie pour tout i – P(1, j) est vraie pour tout j Récurrence. P(i, j) est vraie pour tous les couples (i, j) tels que 1 ≤ i ≤ n et 1 ≤ j ≤ m implique – P(n + 1, j) est vraie pour 1 ≤ j ≤ m – P(i, m + 1) est vraie pour 1 ≤ i ≤ n – P(n + 1, m + 1) est vraie Alors on peut conclure : Conclusion. P(n, m) est vraie pour tout les couples d’entiers naturels. Ce schéma de démonstration peut s’illustrer géometriquement par la figure 3. D’autres schémas sont évidemment possibles. L’affaire consiste à s’assurer que la propriété que l’on veut démontrer est vraie pour chaque couple d’entiers en « remplissant » le plan sans faire d’impasse sur aucun de ses points. Graphiquement, le schéma de la figure 4 est aussi correct5 : 5 Dans la dimension horizontale, il s’agit d’une récurrence totale et dans la dimension verticale d’une récurrence simple. 0.1. RAPPELS SUR LA DÉMONSTRATION. 7 .. . m+1 ... m ... 1 ... 1 n Fig. 4 – Une autre démonstration par récurrence à deux indices. La propriété doit être vraie sur la zone infinie grise (initialisation). La récurrence est alors : si le fait qu’elle soit vraie dans la zone infinie hachurée implique qu’elle est vraie dans la zone infinie doublement hachurée, alors elle est vraie partout. Une formulation générale. Les deux schémas précédents sont des cas particuliers d’une récurrence à deux indices plus générale, qui nécessite une notion supplémentaire : l’introduction d’un ordre partiel dans le plan entier. Définissons une relation d’ordre strict ≺ sur l’ensemble N × N des couples d’entiers par : Si a < c et b ≤ d ou si a ≤ c et b < d alors (a, b) ≺ (c, d). Cette relation est illustrée sur la figure 5. La démonstration générale par récurrence totale à deux indices s’énonce alors ainsi (son illustration est dur la figure 6) : Soit une propriété ou une formule P(n, m), dépendant de deux entiers n et m ∈ N. On cherche à la démontrer par récurrence totale. Un schéma général, est le suivant : Si on peut démontrer les deux propriétés suivantes : Initialisation. – P(i, 1) est vraie pour tout i – P(1, j) est vraie pour tout j Récurrence. P(i, j) est vraie pour tous les couples (i, j) tels que (i, j) ≺ (n, m) implique – P(n, m) est vraie Alors on peut conclure : Conclusion. P(n, m) est vraie pour tout les couples d’entiers naturels. Une variante possible est de ne pas faire une récurrence totale, mais une récurrence simple à deux indices, illustrée à la figure 7 Exemple Démontrons par récurrence6 totale double la propriété suivante : Pour tous les n et m entiers : √ n+m nm ≤ 2 6 Il existe une démonstration directe très simple. Cet exemple, un peu artificiel, a cependant son petit mérite pédagogique. 8 CHAPITRE 0. DÉMONSTRATIONS, RÉCURRENCES. (2, 4) (5, 4) (7, 3) (1, 1) Fig. 5 – Une relation d’ordre dans le plan. Les couples d’entiers (1, 1) et (2, 4) sont « inférieurs » au couple (5, 4). Le couple (7, 3) est « supérieur » au couple (1, 1) mais n’est en relation d’ordre ni avec .. le couple (2, 4) ni avec le couple . (5, 4). (n, m) ... Fig. 6 – Une illustration géométrique de la démonstration par récurrence double totale. La propriété doit être vraie sur les zones infinies grises (initialisation). La récurrence est alors : si le fait qu’elle soit vraie dans la zone finie hachurée (l’ensemble des couples d’entiers inférieurs au couple d’entiers .. (n, m)) implique qu’elle est vraie pour le couple d’entiers (n, m), alors elle est vraie partout. . (n, m) ... Fig. 7 – Une illustration géométrique de la démonstration par récurrence simple à deux indices. 0.2. EXERCICES 9 Nous utilisons le premier schéma de démonstration illustré à la figure 3. L’initialisation consiste d’abord √ , ce que l’on constate en élevant les deux membres au à démontrer que pour tout i on a : i ≤ i+1 2 carré. On fait ensuite de même pour l’autre partie de l’initialisation. Supposons maintenant cette propriété vraie pour 1 ≤ i ≤ n et 1 ≤ j ≤ m. Il faut démontrer que cette hypothèse de récurrence implique que la propriété est vraie sur la zone doublement hachurée. Montrons-la d’abord pour le couple de valeurs m + 1 et n + 1. Il faut donc prouver que : Donc il faut prouver que : p (n + 1)(m + 1) ≤ (n + 1)(m + 1) ≤ (n + 1) + (m + 1) 2 ((n + m) + 2)2 ((n + 1) + (m + 1))2 = 4 4 Donc que : nm + (n + m + 1) ≤ (n + m)2 + 4(n + m) + 4 (n + m)2 = + (n + m + 1) 4 4 Donc que : nm ≤ (n + m)2 4 ce qui est vrai par hypothèse de récurrence. p Il faut maintenant faire la preuve que l’hypothèse de récurrence entraîne que (n + 1)(j) ≤ p (i+1)+(m) (n+1)+(j) pour 1 ≤ j ≤ m et que (i + 1)(m) ≤ pour 1 ≤ i ≤ n ; ces résultats sont 2 2 obtenus par un calcul analogue au précédent. Par conséquent, la propriété est vraie pour tous les couples d’entiers. Commentaires 0.2 Exercices Exercice 0.1 (♥ Démonstration par récurrence : 1.). Démontrer par récurrence simple que : ∀n ∈ N, n! ≥ 2n−1 La solution est page ?? Exercice 0.2 (♥ Démonstration par récurrence : 2.). Question 1. Démontrer par récurrence simple puis par récurrence totale, que toute somme de six centimes ou plus peut être obtenue avec des pièces de deux et de sept centimes. Question 2. Démontrer par récurrence simple puis par récurrence totale que toute somme de douze centimes ou plus peut être obtenue avec des pièces de trois et de sept centimes. Pourquoi seulement au-dessus de douze ? La solution est page ?? Exercice 0.3 (♥ Fausse démonstration par récurrence : 1.). On se propose de démontrer par récurrence la propriété P suivante : Tout couple d’entiers naturels est constitué de deux entiers égaux. La récurrence se fait sur le maximum des deux nombres a et b que nous allons noter max(a, b). Initialisation. Si max(a, b) = 0, alors il est clair que a = b = 0. 10 CHAPITRE 0. DÉMONSTRATIONS, RÉCURRENCES. Récurrence. Supposons la propriété P vraie quand le maximum de a et b est p. L’hypothèse de récurrence est donc : si max(a, b) = p alors a = b = p. Montrons que P est vraie quand le maximum de a et b est (p + 1). Prenons un couple (a, b) tel que max(a, b) = p + 1. Le maximum de (a − 1) et de (b − 1) est donc p. Par hypothèse de récurrence, nous avons : a − 1 = b − 1 = p. D’où : a − 1 + 1 = b − 1 + 1 = p + 1 et finalement a = b = p + 1. Où est l’erreur ? La solution est page ?? Exercice 0.4 (♥ Fausse démonstration par récurrence : 2.). On se propose de démontrer par récurrence la propriété P suivante : n points quelconques du plan sont toujours alignés. La récurrence se fait simplement sur le nombre n. Initialisation. Pour n = 2 P est vraie, puisque 2 points sont toujours alignés. Récurrence. Supposons la propriété (P) vraie pour p points : p points sont alignés. Montrons qu’alors p + 1 points sont alignés. Prenons p + 1 points nommés A1 , A2 , A3 , ... Ap+1 . Par hypothèse de récurrence les p premiers points A1 , A2 , A3 , ... Ap sont alignés sur une droite que nous appellerons (d1 ). Et toujours avec cette hypothèse de récurrence les p derniers points A2 A3 ... Ap+1 sont alignés sur une droite que nous nommerons (d2 ). Mais les deux droites (d1 ) et (d2 ) ont en commun les deux points A2 et A3 . Ces deux droites sont donc forcément confondues. On a (d1 ) = (d2 ) = (A2 A3 ). Les p + 1 points sont donc alignés. Trouvez la faille. La solution est page ?? Exercice 0.5 (♥ Fausse démonstration par récurrence totale). On se propose de démontrer que, pour n entier supérieur ou égal à 1 : s r q p n = 1 + (n − 1) 1 + n 1 + (n + 1) 1 + (n + 2) . . . On admettra pour commencer que cette expression a un sens, c’est à dire qu’elle converge quand n augmente indéfiniment (ce qui est vrai). La démonstration par récurrence se fait alors ainsi : p p Initialisation. Pour n = 1, la formule devient n = 1 + (n − 1)(. . .) = 1 + 0(. . .) = 1 ; elle est donc vraie. Récurrence. Supposons-là vraie pour tout j, 1 ≤ j ≤ n et démontrons-là pour n + 1. Il faut donc démontrer : s r q p n + 1 = 1 + n 1 + (n + 1) 1 + (n + 2) 1 + (n + 3) . . . En élevant au carré la formule pour n, on obtient : r q n2 = 1 + (n − 1) 1 + n p 1 + (n + 1) 1 + (n + 2) . . . 0.2. EXERCICES 11 D’où : n2 − 1 =n+1= n−1 r 1+n C’est bien ce que nous voulions démontrer. q p 1 + (n + 1) 1 + (n + 2) . . . Question 1 : Où est l’erreur de raisonnement ? Question 2 : Cette formule est pourtant vraie. Comment pourrait-on la démontrer ? La solution est page ?? 12 CHAPITRE 0. DÉMONSTRATIONS, RÉCURRENCES. Chapitre 1 Complexité d’un algorithme 1.1 Rappels 1.1.1 Ordres de grandeur de complexité : rappel des définitions. f : N → R+ O(f) = {t : N → R+ tel que : ∃C ∈ R+ , C 6= 0, ∃n0 ∈ N : ∀n ≥ n0 , t(n) ≤ Cf(n)} Ω(f) = {t : N → R+ tel que : ∃C ∈ R+ , C 6= 0, ∃n0 ∈ N : ∀n ≥ n0 , t(n) ≥ Cf(n)} t ∈ O(f) et t ∈ Ω(f) ⇔ t ∈ Θ(f) 1.1.2 Ordres de grandeur de complexité : remarques. 1. Un ordre de grandeur de complexité n’est pas une limite. Par exemple, n2 (2 + sin(n)) ∈ Θ(n2 ) mais n’a pas de limite quand n croît vers l’infini. De plus, les fonctions traitées pas les ordres de complexité ne sont pas de R → R mais de N → R+ . 2. Rappelons quelques définitions classiques1 pour des fonctions f et g : R → R. f(x) = 0. On note : (a) On dit que f est négligeable devant g au voisinage de a quand : lim g(x) x→ a f ∈ oa (g). Par convention, o+∞ est noté simplement par o. f(x) (b) On dit que g et f sont équivalentes en a si g(x) → 1 quand x → a. On note : f ∼a g. Par convention, ∼+∞ est noté simplement par ∼. (c) On dit que g domine f au voisinage de a s’il existe une constante M > 0 et un voisinage de a dans lequel |f(x)| ≤ M|g(x)|. On note : f ∈ Oa (g). Par convention, O+∞ est noté simplement par O. Cette nouvelle notation O est cohérente avec celle définie plus haut, qui en est une restriction aux fonctions de N dans R+ et qui prend toujours a infini. La définition de Oa est plus faible que celle de oa , car f ∈ oa (g) ⇒ f ∈ Oa (g). 3. On écrit souvent par abus de langage : f = o(g) au lieu de f ∈ o(g), f = O(g) au lieu de f ∈ O(g) et f = Θ(g) au lieu de f ∈ Θ(g). Ceci permet par exemple d’écrire la formule de Taylor-Young sous la forme : n k X (x − a) (k) f (a) + o ((x − a)n ) . f(x) = k! k=0 Cet abus de langage n’est en général pas nécessaire aux informaticiens. 1.1.3 Temps de calcul pratique. Chaque case du tableau suivant indique le nombre approximatif de données que peut traiter un algorithme dont la complexité exacte est en colonne, dans le temps donné en ligne, pour un temps élémentaire d’instruction de 1ms. Par exemple, en 1 heure, un algorithme en n3 peut traiter un problème de taille 1500. 1 Voir le polycopié ENSSAT Analyse : support de cours (Chapitre 2). de P. Struillou et N. Barbot. 13 14 CHAPITRE 1. COMPLEXITÉ D’UN ALGORITHME 1 seconde 1 minute 1 heure 1 jour 1.2 log10 (n) 6 1010 7 106.10 8 104.10 10 109.10 n 106 6.107 4.108 9.1010 n2 103 8.103 2.104 105 n3 102 4.102 1500 4400 2n 19 25 31 36 n! 10 11 13 16 Exercices Exercice 1.1 (♥ Ordre de grandeur : 1.). Dire si les affirmations suivantes sont vraies, pour toute fonction f : N → R∗ : Question 1 : 2n+1 ∈ O(2n ) Question 2 : (n + 1)! ∈ O(n!) Question 3 : f(n) ∈ O(n) ⇒ [f(n)]2 ∈ O(n2 ) Question 4 : f(n) ∈ O(n) ⇒ 2f(n) ∈ O(2n ) Question 5 : nn ∈ O(2n ) La solution est page ?? Exercice 1.2 (♥ Ordre de grandeur : 2.). Montrer que : Si O(f(n)) = O(g(n)) ⇔ [f(n) ∈ O(g(n)) et g(n) ∈ O(f(n))] Alors O(f(n) + g(n)) = O (max(f(n), g(n))) La solution est page ?? Exercice 1.3 (♥ Ordre de grandeur : polynômes.). Prouver en utilisant le résultat précédent que n3 − 3n2 + 6n − 8 ∈ O(n3 ) La solution est page ?? Exercice 1.4 (♥ Ordre de grandeur : polynômes (version complète).). Prouver que, pour ap > 0, f(n) = ap np + ap−1 np−1 + . . . + a1 n + a0 ∈ Θ(np ) La solution est page ?? Exercice 1.5 (♥ Ordre de grandeur : un autre.). Prouver que pour k ∈ N, on a 1k + 2k + . . . + nk + ∈ Θ(nk+1 ) La solution est page ?? Exercice 1.6 (♥ suivant : Ordre de grandeur : paradoxe ?). Trouver l’erreur dans le raisonnement n n(n + 1) X i = 2 i=1 Or n X i=1 et i ∈ O(1 + 2 + . . . + n) O(1 + 2 + . . . + n) = O(max(1, 2, . . . , n)) = O(n) Donc n(n + 1) ∈ O(n) 2 1.2. EXERCICES 15 La solution est page ?? Exercice 1.7 (♥ Complexité du calcul récursif de la suite de Fibonacci.). On note F(n) le terme courant de la suite de Fibonacci définie pour n strictement positif. Par définition : F(1) = 1 F(2) = 1 F(n) = F(n − 1) + F(n − 2) pour n ≥ 2 On peut aussi montrer que " √ !n √ !n # 1+ 5 1− 5 1 − F(n) = √ 2 2 5 Question 1 : Donner une raison pour laquelle on n’utilise pas la formule ci-dessus pour calculer F(n) Question 2 : Donner une procédure récursive Fibo(n) calquée sur la définition récurrente pour calculer F(n). Question 3 : Donner l’arbre des appels récursifs pour n = 6 Question 4 : Calculer le nombre d’appels récursifs engendrés par l’appel Fibo(n). La solution est page ?? Exercice 1.8 (♥ maximal.). Calculs exacts de complexité pour trouver les éléments minimal et Un algorithme naïf L’algorithme suivant permet de trouver la valeur de l’élément maximal et l’élément minimal dans un tableau d’entiers S[1..n]. précondition : n ≥ 1 1: Procédure Maxmin1(S, n : in ; max, min : out) 2: début 3: min ← S[1] ; 4: max ← S[1] ; 5: i ← 1 ; 6: tant que i < n faire 7: i ← i+1 ; 8: si S[i] > max alors 9: max ← S[i] 10: fin si 11: si S[i] < min alors 12: min ← S[i] 13: fin si 14: fin tant que 15: fin Question 1 : Quelle est sa complexité exacte en nombre de comparaisons ? Un algorithme moins naïf Dans l’algorithme précédent certaines comparaisons peuvent être évitées en remarquant que S[i] ne peut pas être supérieur à max s’il est inférieur à min. Question 2 : Ecrire cet algorithme. Question 3 : Quelle est sa complexité minimale et maximale en nombre de comparaisons ? Décrire les situations où ces extréma sont atteints. 16 CHAPITRE 1. COMPLEXITÉ D’UN ALGORITHME Question 4 : Que peut-on dire sur sa complexité moyenne ? Un algorithme optimal On peut ruser un peu plus en prenant les éléments par paires : on compare les éléments d’une paire entre eux, puis le plus petit au minimum courant et le plus grand au maximum courant. Question 5 : Ecrire cet algorithme. Distinguer pour l’initialisation le cas où n est pair ou impair. Question 6 : Donner le nombre de comparaisons exact. Question 7 : Pourquoi ne pas prendre les éléments par triplets ? Un algorithme diviser pour régner L’algorithme suivant traite le même problème par la méthode « diviser pour régner ». 1: Procédure Maxmin2(S, p, q : in ; fmin, fmax : out) 2: /* Cette procédure renvoie le minimum et le maximum de la partie S[p..q] du tableau S */ 3: début 4: si q=p alors fmax ← S[p] ; fmin ← S[p] 6: sinon 7: m= ⌊ p+q 2 ⌋; 8: Maxmin2(T, p, m, gmin, gmax) ; 9: Maxmin2(T, m+1, q, hmin, hmax) ; 10: fmax ← maximum(gmax, hmax) ; 11: fmin ← minimum(gmin, hmin) 12: fin si 13: fin 5: Question 8 : Montrer par récurrence que cet algorithme est valide. Question 9 : Quelle est sa complexité exacte en nombre de comparaisons quand n est une puissance de 2 ? (N.B. : Ne pas oublier les comparaisons faites par les fonctions maximum et minimum). Conclusion sur l’utilité de Diviser pour Régner danss ce problème ? Question 10 : Donner son déroulement sur le tableau T =(22, 13, -5, -8, 15, 60, 17, 31, 47). La solution est page ?? Exercice 1.9 (♥ La plus longue coupe équilibrée.). Il s’agit de trouver le plus grand sous-tableau d’un tableau binaire B de taille n qui soit équilibré en 0 et en 1. L’algorithme naïf est est en O(n3 ), un algorithme plus rusé en O(n2 ) et l’algorithme optimal est en O(n). Ce dernier n’est pas facile à trouver... La solution est page ?? Exercice 1.10 (♥ Division du périmètre d’un polygone.). Il s’agit de trouver la paire de sommets d’un polygone qui le divise son périmètre le plus exactement possible. L’algorithme naïf est est en O(n3 ), un algorithme plus rusé en O(n2 ) et l’algorithme optimal est en O(n). Il n’est pas facile à trouver... La solution est page ?? Exercice 1.11 (♥ Un calcul de complexité en moyenne.). L’algorithme ci-dessous prend en donnée un tableau T [1, n] d’entiers tous différents et un entier x. Si x est dans T , il donne en résultat l’indice où x se trouve. Sinon, il produit la valeur n + 1. 1: Procédure Recherche(x : in) 2: début 3: T [n + 1] ← x ; i ← 1 4: tant que T [i] 6= x faire 1.2. EXERCICES 5: 17 i←i+1 6: fin tant que 7: résultat(i) 8: fin Quelle est la complexité exacte minimale, maximale et moyenne de cet algorithme, en nombre de comparaisons ? Pour la complexité en moyenne, il faut faire une hypothèse probabiliste. On supposera que les entiers du tableau sont tirés équiprobablement sans remise entre 1 et N, avec N ≥ n, et que x est tiré équiprobablement entre 1 et N. La solution est page ?? Exercice 1.12 (♥ Variation sur les ordres de grandeurs). Question 1 : Trouver un contre-exemple à l’affirmation suivante : Si f ∈ Θ(s) et g ∈ Θ(s) alors f − g ∈ Θ(s). Question 2 : Trouver un contre-exemple à l’affirmation suivante : Si f ∈ O(s) et g ∈ O(r) alors f − g ∈ O(s − r). La solution est page ?? Exercice 1.13 (♥ Autre variation). Question 1 : Trouver deux fonctions f et g, monotones croissantes, telles que f 6∈ O(g) et g 6∈ O(f). Indication : faire croître rapidement f sur les nombres pairs, lentement sur les nombres impairs, et vice versa. La solution est page ?? 18 CHAPITRE 1. COMPLEXITÉ D’UN ALGORITHME Chapitre 2 Invariants, itération. 2.1 Rappels sur la logique de l’itération. 2.2 Exercices. 19 20 CHAPITRE 2. INVARIANTS, ITÉRATION. Chapitre 3 Diviser et Diminuer pour Régner 3.1 Rappels 3.1.1 Formule de récurrence 1 (Diviser n en en b parties) : T (n) = a.T ( n b ) + f(n) T (1) = d ∈ Θ[1] Solution générale pour n puissance entière de b : j=logb (n)−1 T (n) = Θ[nlogb (a) ] + X aj .f( j=0 n ) bj Quelques solutions particulières pour n quelconque : Cas 1 : f(n) ∈ Θ[1] a = 1 =⇒ T (n) ∈ Θ[Log(n)] a > 1 =⇒ T (n) ∈ Θ[nlogb (a) ] Cas 2 : f(n) ∈ Θ[Log(n)] a=b =⇒ T (n) ∈ Θ[n] a < b =⇒ T (n) ∈ Θ[n] a = b =⇒ T (n) ∈ Θ[n.Log(n)] a > b =⇒ T (n) ∈ Θ[nlogb (a) ] Cas 3 : f(n) ∈ Θ(n) Cas 4 : f(n) ∈ Θ[nα .(Log(n))δ ] a < bα =⇒ T (n) ∈ Θ[nα .(Log(n))δ ] a = bα =⇒ T (n) ∈ Θ[nα .(Log(n))δ+1 ] a > bα =⇒ T (n) ∈ Θ[nlogb (a) ] 21 22 CHAPITRE 3. DIVISER ET DIMINUER POUR RÉGNER 3.1.2 Formule de récurrence 2 (Diminuer n à n − 1) : T (n) = a.T (n − 1) + c.nk T (1) = d ∈ Θ[1] Solution générale : T (n) = d.an−1 + n−2 X ai .(n − i)k i=0 Quelques solutions particulières : k = 0 a = 1 =⇒ T (n) ∈ Θ[n] k = 0 a > 1 =⇒ T (n) ∈ Θ[an ] k = 1 a = 1 =⇒ T (n) ∈ Θ[n2 ] 3.2. EXERCICES 3.2 23 Exercices Exercice 3.1 (♥ Recherche dichotomique dans un tableau trié.). On considère un tableau d’entiers trié par ordre croissant, de taille n. On cherche à savoir si l’entier m s’y trouve. Question 1 : Ecrire un programme de recherche dichotomique qui compare m avec l’élément milieu du tableau (ce terme est à définir précisément), choisit un des demi-tableaux et recommence récursivement. Calculer son nombre exact de comparaisons pour n puissance de deux. Question 2 : Ecrire un programme sur le même principe qui divise le tableau en trois. Calculer son nombre maximal de comparaisons pour n puissance de trois. Conclusion ? Question 3 : (Répondre informellement.) Peut-on améliorer la recherche dichotomique en faisant des hypothèses statistiques sur les éléments du tableau. Indication : comment cherchez-vous un mot dans un dictionnaire ? Anonyme Exercice 3.2 (♥ Suite récurrente.). On considère la suite récurrente à deux indices définie par : a0,j = j + 1 pour j ≥ 0, ai,0 = 2ai−1,0 + ai−1,1 pour i > 0, ai,j = ai−1,j−1 + 2ai−1,j + ai−1,j+1 autrement. Question 1 : Donner les valeurs de ai,j pour 0 ≤ i ≤ 3 et 0 ≤ j ≤ 3. Question 2 : Donner un programme de calcul de cette suite. Exercice 3.3 (♥ Le tracé de triangles.). On veut tracer la figure suivante : Chaque petit triangle est isocèle (45, 45, et 90 degrés), angle droit en bas, de hauteur d ; sa base (horizontale) a donc pour longueur 2d. La figure ci-dessus représente 6 « couches »de tels triangles. La couche du bas comporte 1 triangle, celle de dessus 2, ainsi de suite jusqu’à 6. Comme on le voit, un assemblage de n couches forme un grand triangle de même forme, mais n fois plus grand. Une telle figure est dite de taille n. Pour un triangle élémentaire, si le sommet du bas a pour coordonnées (x, y), les autres ont pour coordonnées (x − d , y + d) et (x + d , y + d). On dispose d’un traceur capable de répondre aux commandes suivantes : placer(x , y), qui place la « plume »au dessus du point de coordonnées (x , y) et tracer(x , y) qui trace une ligne droite depuis l’endroit où la plume est placée jusqu’au point de coordonnées (x , y), où la plume se trouve alors placée. On veut que le traceur démarre sur la pointe du bas, à laquelle on donnera les coordonnées (0 , 0) et termine au même endroit. 24 CHAPITRE 3. DIVISER ET DIMINUER POUR RÉGNER Préliminaire Question 1 : Quel est le nombre minimum de triangles de taille 1 qu’il faut tracer pour tracer un triangle de taille n ? Une méthode longue On remarque que le tracé de la figure de taille n se ramène à tracer le côté gauche du triangle du bas, puis une figure de taille n − 1, puis le côté horizontal du triangle du bas, puis une seconde figure de taille n − 1, puis le côté droit du triangle du bas. Question 2 : Ecrire le programme correspondant. Question 3 : Quel est le nombre de triangles de taille 1 qui sont effectivement tracés ? Question 4 : Exécuter le programme à la main sur une figure de taille 5. Une méthode optimale On remarque que le tracé de la figure de taille n se ramène à tracer le côté gauche du triangle du bas, puis une figure de taille n − 1, puis le côté horizontal du triangle du bas, puis un « bandeau »de taille n − 1, puis le côté droit du triangle du bas. Un bandeau de taille 4 est la figure suivante : Question 5 : Ecrire le programme correspondant. Vérifier sur une figure de taille 6 qu’il éxécute un tracé minimum. Question 6 : Calculer sa complexité exacte et démontrer qu’il exécute un tracé de longueur minimum. Exercice 3.4 (♥ La bâtière.). On considère un tableau à deux dimensions de valeurs entières positives telles que les valeurs d’une même ligne et celles d’une même colonne sont ordonnées, non strictement. Un tel tableau est appelé bâtière. Exemple : le tableau ci-dessous est une bâtière à 4 lignes et 5 colonnes. 2 3 7 20 14 15 15 28 25 28 32 36 30 30 43 58 69 81 100 101 Diviser pour régner (1, n − 1) On étudie la recherche d’une valeur v fixée dans une bâtière B. Pour simplifier, on supposera que v figure au moins une fois dans B. Une première solution consiste en un balayage séquentiel de B par ligne ou par colonne jusqu’à trouver v. 3.2. EXERCICES 25 Question 1 : Décrire le principe de cette première stratégie en terme de méthode « diviser pour règner » Question 2 : Quelle en est la classe de complexité au pire (en terme de nombre de comparaisons) si m est le nombre de lignes et n le nombre de colonnes de B. Diviser pour régner (n/2, n/2) On envisage maintenant une solution dans le cas particulier où la bâtière est un carré de côté n = 2k . On distingue les deux valeurs : n n n x = B[ n 2 , 2 ], y = B[ 2 + 1, 2 + 1] Question 3 : Montrer que si la valeur recherchée v est telle que : v > x, on peut éliminer une partie (à préciser) de la bâtière pour poursuivre la recherche. Préciser ce qu’il est possible de faire quand v < y. Question 4 : En déduire un modèle de résolution de type « diviser pour régner »en réduction logarithmique : Pb(n) = aPb(n/b) + f(n). Question 5 : Etablir l’ordre de complexité au pire de cette méthode (en terme de nombre de comparaisons). Question 6 : Comment adapter cette stratégie au cas d’une bâtière quelconque ? De plus en plus fort On considère maintenant la recherche d’une valeur v dans une bâtière B de dimension quelconque (m lignes, n colonnes) en distinguant la valeur z = B[1, n]. Question 7 : Comment peut-on poursuivre la recherche selon que (v < z) ou (v > z) ? Question 8 : En déduire un modèle de résolution de type « diminuer pour régner »de la forme : Pb(m, n) = Pb(m′ , n′ ) + test(z) en précisant les valeurs de m′ et n′ . Question 9 : Ecrire en pseudo-code la procédure récursive correspondante, qui a pour en-tête : procedure bâtière(ldeb, cfin) et est appelée du programme principal par : bâtière(1, n) Elle doit rendre dans un doublet de type place une localisation de la valeur recherchée (v) dans la bâtière B[1 : m, 1 : n]. Question 10 : Etablir la classe de complexité au pire de cette dernière méthode (en terme de nombre de comparaisons) et conclure sur la démarche adoptée pour résoudre cet exercice. La solution est page ??. Exercice 3.5 (♥ Double tri.). Soit un tableau T [1 : n , 1 : n] dont les éléments sont des entiers relatifs. Ce tableau possède la propriété de double tri, c’est à dire que toutes les colonnes sont composées d’éléments strictement croissants, et toutes les lignes d’éléments strictement croissants. Autrement dit, pour 1 ≤ i < n et 1 ≤ j < n, on a : T [i, j] < T [i + 1, j] et T [i, j] < T [i, j + 1] Le problème est de compter le nombre de zéros dans T . Question 1 : On suppose que n = 2k , avec k entier. Donner un algorithme Diviser pour Régner qui coupe T en quatre. Quelle est sa complexité ? Question 2 : Donner un algorithme de complexité inférieure. Question 3 : Que se passe-t’il dans le cas où les lignes et les colonnes ne sont pas forcément strictement croissantes ? La solution est page ?? 26 Exercice 3.6 (♥ CHAPITRE 3. DIVISER ET DIMINUER POUR RÉGNER Multiplication de deux nombres complexes.). Question 1 : Montrer comment multiplier les deux nombres complexes a+bi et c+di en n’utilisant que trois multiplications réelles (les additions et les soustractions ne comptent pas). L’algorithme a pour entrées a, b, c et d et pour sorties la composante réelle ac − bd et la composante imaginaire ad + bc. Ce n’est pas vraiment du DpR, mais ça aide pour les suivants. Exercice 3.7 (♥ Multiplication de grands nombres.). On s’intéresse à des nombres écrits en base b ; on peut donc les décomposer de manière unique sous la forme : N = n0 + n1 b1 + ... + nq bq , où les ni sont des chiffres compris au sens large entre 0 et b−1 (avec la restriction : nq n’est pas nul). On mesure la complexité des algorithmes selon l’opération élémentaire de la multiplication de deux chiffres. Question 1 : Quelle est la complexité de la multiplication de deux nombres N et M quand on la fait « à la main » ? Vérifier sur un exemple simple (M et N à quatre chiffres). Question 2 : On suppose que N et M ont le même nombre de chiffres et que ce nombre est une puissance de deux . Décrire l’algorithme élémentaire « diviser pour régner »de multiplication de N et M. Quelle est sa complexité ? Reprendre le même exemple. Question 3 : En partant de cet algorithme, trouver une variante qui réduise la complexité en utilisant l’identité : ad + bc = (a + b)(c + d) − (ac) − (bd) Reprendre le même exemple. Question 4 : Que faire quand N etM ont un nombre différent et quelconque de chiffres ? L’efficacité de la variante précédente est-elle conservée ? Question 5 : Pourquoi ce exercice s’appelle-t’il : « multiplication de grands nombres » ? Exercice 3.8 (♥ Multiplication de polynômes.). On cherche à multiplier deux polynômes A(x) = an xn + an−1 xn−1 + . . . + a1 x + a0 et B(x) = bm xm + bm−1 xm−1 + . . . + b1 x + b0 . L’opération élémentaire pour mesurer la complexité est la multiplication des coefficients comme ai par bj . La multiplication de xi par xj ne compte pas. Question 1 : On suppose que n = m = 2k . Décrire l’algorithme élémentaire « diviser pour régner »de multiplication de A et B. Quelle est sa complexité ? Question 2 : En partant de cet algorithme, trouver une variante qui réduise la complexité en utilisant l’identité : ad + bc = (a + b)(c + d) − (ac) − (bd) Question 3 : Que faire quand n etm sont quelconques ? L’efficacité de la variante précédente estelle conservée ? Semblable à l’exercice 3.7, multiplication de grands nombres. Exercice 3.9 (♥ Suite de Fibonacci.). On note F(n) le terme courant de la suite de Fibonacci. Question 1 : Montrer que le calcul de la suite de Fibonacci se ramène à celui de la résolution F(n) d’une équation matricielle récurrente. On notera V(n) = un vecteur colonne à deux F(n − 1) éléments et F une matrice carrée (2 × 2). On cherche F telle que : V(2) = constante V(n) = F × V(n − 1) Donner la matrice F. 3.2. EXERCICES 27 Question 2 : Montrer que la solution de cette équation de récurrence s’écrit V(n) = Fn−2 × V(2). Question 3 : En déduire un algorithme de type « diviser pour régner »calculant F(n) pour tout n, de complexité log2 (n) en terme de nombre de multiplication de matrices (2 × 2). Question 4 : Donner un minorant et un majorant (en explicitant les cas où ils sont atteints) de la complexité exacte de cette procédure en terme de nombres de multiplications de matrices (2 × 2). Question 5 : Montrer par un cas particulier que cet algorithme d’élévation à la puissance n n’est pas optimal. Prendre par exemple : n = 15. Exercice 3.10 (♥ Suite de Fibonacci, autre technique.). Soit la suite de Fibonacci : F0 = F1 = 1 et Fn = Fn−1 + Fn−2 pour n ≥ 2 Question 1 : Montrer par récurrence sur p que : ∀ n , p ≥ 1 : Fn+p = Fn Fp + Fn−1 Fp−1 Question 2 : Appliquer pour p = n, p = n − 1 et p = n + 1 pour en déduire F2n , F2n−1 et F2n+1 en fonction de Fn et de Fn−1 . Fn−1 , en déduire que T2n et T2n+1 se Question 3 : En notant Tn le couple de valeurs Fn calculent en fonction des éléments de Tn , donc de Tn . Question 4 : En déduire un algorithme diviser pour régner pour calculer Tn , donc Fn pour tout n et donner son pseudo-code. Calculer son ordre de grandeur de complexité. Cette technique est donnée dans [Que00], pages 10 et 68. Exercice 3.11 (♥ Les deux points les plus proches.). On cherche un algorithme du type « diviser pour régner »(DpR) pour résoudre le problème suivant : on a n points dans un plan. Quels sont les deux plus proches et à quelle distance sont-ils ? Plus formellement : soit l’espace R2 , muni de la métrique euclidienne notée ∆ . Soit S un ensemble fini de n points dans R2 (on note son cardinal |S| = n). On cherche la valeur D et les deux éléments y et z de S tels que : D = ∆(y, z) = Minimum∆(x1 , x2 ), pour x1 ∈ S, x2 ∈ S et x1 6= x2 . En notant abs(x) l’abscisse d’un point x et ord(x) son ordonnée, on a : ∆(u, v) = 1 (abs(u) − abs(v))2 + (ord(u) − ord(v))2 2 Question 1 : Quelle est l’ordre de complexité (opération élémentaire : le nombre de calculs de la distance ∆ ) de l’algorithme naïf de calcul de D, y et z ? On trie les n points par ordonnée croissante. Les données sont rangées dans un tableau T [1 : 2, 1 : n], où T [1, i] est l’abscisse du point numéro i et T [2, i] son ordonnée, avec T [2, i] ≤ T [2, i + 1]. Soit un réel m et S1 = {x ∈ S|abs(x) ≤ m} et S2 = {x ∈ S|abs(x) > m}. Le shéma du programme DpR est alors le suivant : 1: Procedure PlusProches(S, x, y, D) 2: début 3: si |S| = 2 alors 4: Soit u et v les deux points de S. 5: x ← u ; y ← v ; D ← ∆(u, v) ; 6: sinon 7: Choisir m ; 8: PlusProches(S1 , p1 , p2 , D1 ) ; 28 CHAPITRE 3. DIVISER ET DIMINUER POUR RÉGNER PlusProches(S2 , q1 , q2 , D2 ) ; d ← minimum(D1 , D2 ) ; 11: si d = D1 alors 12: w ← p1 ; z ← p2 13: sinon 14: w ← q1 ; z ← q2 15: fin si 16: Rassembler 17: fin si 18: fin 9: 10: Question 2 : Le calcul de D2 étant toujours l’opération élémentaire, quel doit être l’ordre de complexité de Rassembler pour que le programme soit meilleur que le programme naïf, en supposant que S11 et S2 sont de même taille ? Quel est son ordre de complexité si Rassembler est en O(n) ? En O(1) ? Question 3 : Pourquoi Rassembler ne peut-il pas s’écrire simplement : D ← d; x ← w; y ← z Donner un exemple. Soit P1 la partie du plan située entre la droite d’équation x = m et celle d’équation x = m − d ; soit P2 celle située entre la droite d’équation x = m et celle d’équation x = m + d . Question 4 : Montrer que si deux points r1 ∈ S1 et r2 ∈ S2 sont tels que : ∆(r1 , r2 ) ≤ d, alors r1 ∈ P1 et r2 ∈ P2 . Question 5 : Soit r1 un point de P1 . Montrer que la zone du plan où peut se trouver un point r2 ∈ P2 tel que ∆(r1 , r2 ) ≤ d est incluse dans le rectangle de la figure suivante : r1 • 2d d d Question 6 : Montrer que ce rectangle contient au plus 6 points de S. Question 7 : Donner le schéma d’une procédure Rassembler utilisant le tableau T et les valeurs m et d. Montrer que son ordre de complexité est en O(n). NB : Il est recommandé de créer deux tableaux triés par ordonnées, l’un contenant les points de P1 , l’autre ceux de P2 . Question 8 : Donner un schéma pour la procédure Choisir qui ne change pas l’ordre de complexité obtenu. Question 9 : Une telle procédure DpR est-elle réalisable en dimension 3 ? Pourquoi ? Exercice 3.12 (♥ Pavage d’un carré par des triminos.). Pavage particulier d’un carré On considère un échiquier carré de côté n = 2m avec m > 0 et les quatre motifs ci-après composés d’un trimino (un carré 2 × 2 auquel une case a été enlevée) : 3.2. EXERCICES On se pose le problème de recouvrir intégralement l’échiquier, à l’exception d’une case spéciale donnée de coordonnées (x, y), à l’aide des motifs précédents, de sorte que chaque case soit recouverte par un seul motif. Exemple. On a un échiquier de côté 8 = 23 et la case spéciale est en (2, 6). Les motifs ont été distingués uniquement par souci d’identification. Le but de l’exercice est de concevoir une solution de type « diviser pour régner »au problème posé. On va tout d’abord valider l’hypothèse de récurrence suivante : on sait recouvrir tout échiquier de côté n = 2m dans lequel une case spéciale a été retirée. Question 1 : Vérifier que cette hypothèse est valide pour m = 1. On suppose l’hypothèse vérifiée au rang m et on demande de décrire une procédure de recouvrement de l’échiquier de côté 2m+1 . On peut placer arbitrairement dans l’un des sous-échiquiers de côté 2m et on fera apparaître un (des) motif(s) de recouvrement. 29 30 CHAPITRE 3. DIVISER ET DIMINUER POUR RÉGNER Question 2 : On suppose l’hypothèse vérifiée au rang m et on demande de décrire une procédure de recouvrement de l’échiquier de côté 2m+1 . On pourra positionner la case spéciale arbitrairement dans l’un des quatre sous-échiquiers de côté 2m et on fera apparaître un (des) motif(s) de recouvrement. Question 3 : Exprimer la division du problème initial Recouvre(2m ) en faisant apparaître des problèmes de même nature et la pose de motif(s). On précisera les problèmes élémentaires Question 4 : Établir que la complexité temporelle exacte de la procédure associée est égale à (n2 − 1)/3 si on prend la pose de motif comme opération élémentaire. Question 5 : Donner une condition nécessaire (relative à n, le côté de l’échiquier) pour que le problème posé ait une solution (indépendamment de la façon de le résoudre). Question 6 : Quand cette condition est satisfaite, peut-on appliquer la méthode de type « diviser pour régner »proposée auparavant ? Anonyme Exercice 3.13 (♥ Distance entre séquences : algorithme de Hirshberg). Voir [KLE06], pages 284 et suivantes. Bien que cet exercice soit situé dans le texte avant celui intitulé Distance entre séquences : algorithme de Wagner et Fisher. (exercice 99.1, page 107), on ne peut pas le résoudre avant lui. Il est donc nécessaire de traiter le second d’abord. Toutes les notations sont identiques. Cet algorithme calcule le coût de la trace optimale entre deux séquences, comme celui de Wagner et Fisher. Il peut également reconstituer la (ou les) trace(s) de coût minimal. La différence est qu’il est construit sur une technique Diviser pour Régner, tout en utilisant le principe de Programmation Dynamique. L’avantage proclamé de cette méthode est le gain de place. Hirshberg l’ayant imaginé dans les années 70, cet argument avait plus de force que de nos jours (encore que si l’on traite des séquences biologiques de milliers de lettres, il a encore son utilité de ce point de vue). Mais c’est surtout un exemple extraordinaire, à notre connaissance unique, de combinaison entre ces deux techniques si différentes. La question 8 de l’exercice 99.1 est la clé de la construction de cet algorithme. Elle demande de montrer (ce que l’on admettra ici) que la trace optimale entre les phrases miroir u et v se déduit directement de celle entre u et v, si elle est unique. On ne va pas donner ici la réponse à cette question, mais explorer cette idée un peu différemment avant de construire l’algorithme Diviser pour Régner. Soit les phrases u et v, de longueur respective n et m. Nous supposerons la trace optimale unique, pour simplifier. Cette contrainte se relâche sans difficulté. 3.2. EXERCICES 31 La trace optimale entre u et v correspond au chemin optimal dans le graphe défini dans l’exercice 99.1 entre les nœuds (0/0) et (n/m). Celle entre u et v correspond au chemin optimal entre les nœuds (n/m) et (0/0), c’est à dire le même, mais emprunté en sens inverse (appelons-le chemin optimal inverse). Notons que l’opération d’inversion d’un chemin est involutive, c’est à dire qu’inverser deux fois un chemin le transforme tout simplement en lui-même. Nous allons généraliser cette propriété dans la première question. Question 1 : Nous nous intéressons ici aux chemins du graphe qui vont du nœud (0/0) au nœud (n/m) en passant par un certain nœud (i/j). Montrer que le chemin optimal (supposé unique) entre les nœuds (0/0) et (n/m), sous la contrainte de passer par (i/j) peut se décomposer en deux parties : – Le chemin optimal entre (0/0) et (i/j). – L’inverse du chemin optimal inverse entre (n/m) et (i/j). Question 2 : Notons L(i/j) la longueur du chemin optimal entre (0/0) et (i/j) et L(i/j) la longueur du chemin optimal inverse entre (n/m) et (i/j). Montrer que tout j, on a : L(n/m) = Min L(i/j) + L(i/j) i Question 3 : En assignant à j la valeur ⌊ m 2 ⌋, montrer que la formule précédente permet de trouver un nœud du chemin optimal qui divise v en deux moitiés. On supposera qu’il existe deux procédures WFAvant(x, y) et WFArriere(x, y) qui calculent la distance d’édition par l’algorithme de Wagner et Fisher, l’une entre x et y, l’autre entre x et y. Question 4 : En déduire l’algorithme Diviser pour Régner de Hirshberg qui utilise ces procédures. Question 5 : Donner sa formule de récurrence de complexité (l’opération élémentaire est la comparaison). Question 6 : Résoudre la formule de récurrence dans le cas où m = n. Que pensez-vous du cas général ? Question 7 : On prend l’exemple : u = namely et v = meanly, pour la distance sur l’alphabet définie de la façon suivante : – pour toute lettre α : δ[α, ǫ] = δ[ǫ, α] = 2 ; – si α et β sont deux consonnes ou sont deux voyelles : δ[α, β] = δ[β, α2] = 1 ; – si α est une consonne et β une voyelle : δ[α, β] = δ[β, α] = 3 ; Calculer la première valeur de q que produit l’algorithme de Hirshberg. En quoi cet algorithme permet-il d’économiser de la place ? Il faut se reporter à la question 5 de l’exercice 99.1, qui donne la version « linéaire »WFL de l’algorithme de Wagner et Fisher (dont la taille est en O(Max(n, m) au lieu de O(nm) pour WF), permettant de calculer le coût de la trace optimale, mais pas de reconstituer cette trace. Question 8 : Montrer que l’algorithme de Hirshberg permet de reconstituer la trace en espace linéaire, s’il utilise WFLAvant et WFLArriere au lieu de WFAvant et WFArriere. La solution est page ?? Exercice 3.14 (♥ Transformée de Fourier rapide (FFT)). Une transformée de Fourier discrète, ou DFT , est une transformation linéaire de CN dans CN . Soit x = {xn }, 0 ≤ n ≤ N − 1 une suite de nombres complexes (xn ∈ C). Pour un certain entier k, la valeur Xk de la DFT de la suite x se définit par : Xk = N−1 X n=0 2πi xn e− N nk , 0≤k≤N−1 Avec e la base du logarithme naturel et i le nombre complexe bien connu. (3.1) 32 CHAPITRE 3. DIVISER ET DIMINUER POUR RÉGNER On peut calculer Xk pour k de 0 à N − 1 et obtenir la totalité X de la DFT de x par le produit matriciel X = Vx, ou : X0 X1 X2 . .. XN−1 x0 x1 V x2 . .. = xN−1 avec : V 1 1 1 . .. = 1 1 w1 w2 1 w2 w4 ··· ··· ··· wN−1 w2(N−1) wN−1 w2(N−1) ··· w(N−1)(N−1) 1 2iπ Dans ce tableau, on a remplacé e N par le symbole w. Il sera bientôt explicité que w est la première racine N-ième de l’unité. La définition de la DFT a la propriété de permettre de recalculer x à partir de X, car la matrice V est inversible. La formule explicite est la suivante : N−1 xn = 2πi 1 X Xk e N nk , N (3.2) 0≤k≤N−1 n=0 On va donner maintenant le principe d’un algorithme Diviser pour Régner pour calculer X, la DFT de x, en se fondant sur les propriétés des racines N-ièmes de l’unité. On va supposer pour simplifier que N est une puissance de 2. Pour voir sur un exemple, examinons une transformée discrète sur N = 8 points. La matrice V est la suivante : V = 1 1 1 1 1 1 1 1 1 w w2 w3 w4 w5 w6 w7 1 w2 w4 w6 w8 w10 w12 w14 1 w3 w6 w9 w12 w15 w18 w21 1 w4 w8 w12 w16 w20 w24 w28 1 w5 w10 w15 w20 w25 w30 w35 √ iπ 1 w6 w12 w18 w24 w30 w36 w42 √ 1 w7 w14 w21 w28 w35 w42 w49 Dans cet exemple, w = e 4 . Cette valeur est aussi égale à 22 + i 22 . Il n’y a pas en réalité tellement de valeurs différentes dans cette matrice, en raison des propriétés des racines complexes de l’unité : c’est ce qui va permettre de simplifier le calcul. La figure 3.1 représente dans le plan complexe les racines N-ièmes de l’unité pour N = 8. Les racines sont situées de manière equidistante sur le cercle complexe unité, ce qui permet de déduire les propriétés suivantes : w2k+N = w2k wk+N/2 = −wk De la sorte, la matrice précédente peut s’écrire avec seulement quatre valeurs : 1, i, w = √ 2 2 2 +i 2 √ 3.2. EXERCICES 33 Fig. 3.1 – Racines N-ièmes de l’unité dans le plan complexe avec N=8. et w = √ √ 2 2 2 −i 2 V = 1 1 1 1 1 1 1 1 1 w i w −1 −w −i −w 1 i -1 -i 1 i −1 −i 1 -1 1 -1 1 −1 1 −1 1 w −i w −1 −w i −w 1 −w i −w −1 w −i w 1 -i -1 i 1 −i −1 i 1 −w −i −w −1 w i w De plus, il est facile de déduire des propriétés précédentes que les carrés des N racines N-ièmes de N l’unité sont les N 2 racines 2 -ièmes de l’unité. C’est ainsi que dans l’exemple précédent les valeurs encadrées forment la matrice d’une DFT sur 4 points : 1 1 1 w4 1 w2 4 1 w34 1 w24 w44 w64 1 w34 = w64 w94 1 1 1 1 1 i −1 −i 1 −1 1 −1 1 −i −1 i Le principe du calcul rapide de la Tranformée de Fourier Discrète par l’algorithme FFT s’appuie sur cette propriété des carrés des racines de l’unité. Le principe est de décomposer un problème de DFT sur N points en deux sous-problèmes de DFT sur N 2 points. L’astuce est de décomposer la suite d’entrée x en deux sous-suites, l’une comportant les valeurs d’indice pair, l’autre comportant les valeurs d’indice impair : xe = xo = x0 x1 x2 ··· x2p ··· x3 ··· x2p+1 xN−2 , ··· xN−1 , N −1 2 N 0≤p≤ −1 2 0≤p≤ De la même façon, on note Xe la transformée de xe et Xo la transformée de xo . 34 CHAPITRE 3. DIVISER ET DIMINUER POUR RÉGNER La récurrence générale de l’algorithme FFT peut maintenant s’expliciter : N−1 X = Xk xn wnk n=0 N/2−1 = N/2−1 X xep w2pk + X xep (wpk )2 + wk p=0 xop w(2p+1)k p=0 N/2−1 = X p=0 N/2−1 X xop (wpk )2 p=0 On a par définition : N/2−1 Xek = X xep (wpk )2 , 0≤k≤ N −1 2 X xop (wpk )2 , 0≤k≤ N −1 2 p=0 N/2−1 Xok = p=0 A partir de la décomposition paire/impaire et des propriétés précédentes, il est maintenant facile de conclure que : Xk = Xek + wk Xok Xk+ N = Xek − wk Xok 2 N −1 2 N 0≤k≤ −1 2 0≤k≤ Question 1 : Donner l’algorithme FFT permettant de calculer la DFT X d’une suite x = {xn }, n ≤ N − 1 pour N puissance de 2. Question 2 : Quelle est sa complexité (l’opération élémentaire est la multiplication) ? 0≤ La solution est page ?? Produit de convolution). Etant donné deux vecteurs de taille (n − 1) : Exercice 3.15 (♥ a = (a0 , a1 , . . . , an−1 ) et b = (b0 , b1 , . . . , bn−1 ), on définit leur produit de convolution comme un vecteur a ⋆ b de taille (2n − 1) dont la coordonnée de rang k s’écrit : X ai bj i+j=k 0≤i≤n−1 0≤j≤n−1 Pn−1 Pn−1 Question 1 : Soit les polynômes A(y) = i=0 ak yk et B(y) = i=0 bk yk . Montrer que le coefficient de rang k du polynôme C(y) = A(y)B(y) n’est autre que la coordonnée de rank k du produit de convolution des vecteurs a et b. Question 2 : En déduire une méthode pour calculer le vecteur a ⋆ b en un nombre de multiplications en O(nLog(n)). Question 3 : Montrer que ce calcul peut être étendu au cas où la taille des deux vecteurs (et donc le degré des polynômes) sont différents. La solution est page ?? Exercice 3.16 (♥ Titre). En électrostatique, la loi de Coulomb exprime la force F1→ 2 électrique exercée par une charge électrique 3.2. EXERCICES 35 q1 placée en un point M1 sur une charge q2 placée en un point M2 . Cette loi s’exprime sous forme vectorielle par la formule suivante : → − F 1→ 2 = → −r q1 q2 12 −r ||2 ||→ −r || 4πǫ0 ||→ 12 12 −−−→ −r = − où → M1 M2 est le vecteur qui relie le premier corps au deuxième. ǫ0 est une constante. 12 Considérons un ensemble de n charges q1 , . . . , qn disposées régulièrement sur une ligne droite. → − Notons F •i la somme des forces exercées par les (n − 1) autres charges sur la charge qi . Question 1 : Montrer que l’on a la relation suivante : X Cqi qj X Cqi qj → − − || F •i || = 2 (j − i) (j − i)2 i<j i>j où C est une constante. → − Question 2 : Donner une méthode pour calculer l’ensemble (pour i = 1, n) des valeurs F •i en un nombre de multiplications en O(nLog(n)) La solution est page ?? 36 CHAPITRE 3. DIVISER ET DIMINUER POUR RÉGNER Chapitre 4 Essais successifs 4.1 Rappels 4.1.1 Principe La programmation par essais successifs (ou backtracking) est une méthode d’énumération sans répétition de toutes les solutions possibles d’un problème donné. Si on cherche la meilleure solution (à supposer que chacune puisse être notée), ce n’est qu’après avoir tout essayé qu’on pourra la connaître. Ici, on considère que le problème se décrit sous la forme d’un vecteur X, dont chaque composante xi prend ses valeurs dans un domaine Si , de taille finie. Si le vecteur X est de taille n et si (un cas classique), la taille de chaque domaine est exactement 2, le nombre de solutions possibles est 2n . Par exemple, étant donné un ensemble de n éléments, on cherche à énumérer tous ses sousensembles pour trouver le meilleur, au sens d’un critère qui n’importe pas encore ici. Il s’agit donc d’énumérer sans répétition les 2n sous-ensembles, pour chacun de calculer sa valeur, et de retenir finalement le meilleur. Un autre cas classique est celui de la meilleure permutation des éléments d’un ensemble. La combinatoire est plus importante, puisqu’il s’agit d’examiner n! possibilités. On verra que la méthode des essais successifs permet aussi de résoudre ce genre de problème. Heureusement, il n’est pas toujours nécessaire d’examiner toutes les possibilités : certaines peuvent être éliminées avant leur construction complète. Mais il faut pour cela prendre garde à organiser l’énumération de façon à faire demi-tour dès que l’on s’aperçoit que l’on est dans un cul-de-sac. La technique de programmation par essais successifs est récursive. La récursion porte sur l’indice du vecteur X et développe l’arbre de toutes les possibilités en profondeur d’abord. Typiquement, l’énumération de tous les sous ensembles d’un ensemble E à quatre élements consiste à produire tous les vecteurs binaires dans l’ordre suivant : (0, 0, 0, 0), (0, 0, 0, 1), (0, 0, 1, 0), (0, 0, 1, 1), . . . , (1, 1, 1, 0, ), (1, 1, 1, 1). Grâce à la procédure récursive, on construit un arbre dont les feuilles sont de gauche à droite les 16 vecteurs binaires ci-dessus, et dont les nœuds sont des solutions partielles, qui s’écrivent avec les feuilles dans l’ordre de création : (∗, ∗, ∗, ∗), (0, ∗, ∗, ∗), (0, 0, ∗, ∗), (0, 0, 0, ∗), (0, 0, 0, 0), (0, 0, 0, 1), (0, 0, 1, ∗), . . . Sous forme graphique, les nœuds et les feuilles sont construits dans l’ordre indiqué dans la figure 4.1, correspondant à la recherche profondeur d’abord 1 . L’élagage permet, en testant chaque solution partielle, d’éviter quand c’est possible de développer les solutions qui se trouvent en-dessous dans l’arbre , soit parce qu’on sait que la solution partielle ne mènera jamais à une solution, soit parce que elle mènera peut-être à des solutions, mais dont on sait déjà qu’elle ne seront pas optimales. 1 On pourrait faire des essais successifs en largeur d’abord en gardant le caractère récursif de l’algorithme. 37 38 CHAPITRE 4. ESSAIS SUCCESSIFS **** 1 0*** 2 1*** 17 00** 3 000* 4 0000 5 0001 6 01** 10 001* 7 0010 8 0011 9 010* 11 0100 12 0101 13 011* 14 0110 15 0111 16 Fig. 4.1 – Arbre de récursion à moitié complet, avec l’ordre de construction des nœuds en profondeur d’abord. 4.1.2 Algorithmes génériques 1: Procedure ToutesSolutions(i :in ) 1: Procedure SolutionOptimale(i : in) 2: début 2: début 3: Calculer le domaine Si de xi ; 3: Calculer le domaine Si de xi ; 4: pour xi parcourant Si faire 4: pour xi parcourant Si faire si Satisfaisant(xi ) alors 6: Enregistrer(xi ) ; 7: si SolutionTrouvée alors 8: EcrireSolution 9: sinon 10: si SolutionEncorePossible alors 11: ToutesSolutions(i+1) 12: fin si 13: fin si 14: Défaire(xi ) /% le contraire de Enregistrer %/ 15: fin si 16: fin pour 17: fin si Satisfaisant(xi ) alors 6: Enregistrer(xi ) 7: si SolutionTrouvée alors 8: si SolutionMeilleure alors 9: Conserver 10: fin si 11: sinon 12: si SolutionEncorePossible alors 13: SolutionOptimale(i+1) 14: fin si 15: fin si 16: Défaire(xi ) /% le contraire de Enregistrer %/ 17: fin si 18: fin pour 19: fin 5: 5: 4.2. EXERCICES 4.2 39 Exercices Exercice 4.1 (♥ Les n reines.). Jouez-vous aux échecs ? Savez-vous comment mettre 8 reines sur un échiquier de sorte qu’aucune ne soit en prise par une autre ? Rappellons qu’une reine, au jeu d’échecs, met en prise toute autre pièce située sur la même colonne, la même ligne ou l’une de ses deux diagonales (à condition qu’elle ne soit pas masquée par une pièce intermédiaire). On peut poser ce problème de manière plus générale : comment mettre n reines sur un échiquier de taille n × n de sorte qu’aucune ne soit en prise par une autre ? Par exemple, pour n = 4, une solution est la suivante : (4, 2) ❍ (3, 4) ❍ (2, 1) ❍ (1, 3) ❍ Il est facile de constater qu’une solution, si elle existe, doit comporter une reine et une seule par ligne. Une solution peut donc se représenter par un tableau X[1 : n], tel que X[i] = j signifie que la reine sur la ligne i est sur la colonne j. La solution ci-dessus peut donc s’écrire : (3 , 1 , 4 , 2). On va d’abord générer tous les vecteurs X dont les éléments sont les permutations de {1, 2, 3, 4}, que l’on peut appeller des solutions potentielles. Une solution potentielle représente donc un ensemble de reines qui ne sont en prise ni par les lignes ni par les colonnes. Parmi elles il y aura peut-être des vraies solutions, on le saura quand on aura vérifié en plus qu’il n’y a pas de prise par les diagonales. Question 1 : Ecrire un algorithme itératif effectuant 44 comparaisons qui trouve toutes les solutions potentielles au problème des 4 reines. Question 2 : Ecrire un algorithme itératif effectuant 4! comparaisons résolvant le même problème. Question 3 : Ecrire un algorithme (récursif) par essais successifs résolvant le même problème. Quel est son avantage par rapport aux précédents ? Question 4 : Ecrire un algorithme par essais successifs qui intègre la contrainte de non-prise par les diagonales et se moque bien de la taille de l’échiquier. Question 5 : Décrire l’arbre de récursion et ses élagages pour le cas n = 4. Anonyme Exercice 4.2 (♥ Le parcours d’un cavalier.). On se pose le problème suivants : en combien de coups minimum un cavalier peut-il, sur un jeu d’échecs où il est seul, aller de la case (m , n) à la case (k , l) ? Les indices m, n, k, et l varient de 1 à 8. Le cavalier respecte ses règles de déplacement : s’il est sur la case (m , n), il peut aller sur les huit cases : (m − 2 , n − 1) (m − 2 , n + 1) (m − 1 , n − 2) (m − 1 , n + 2) (m + 1 , n − 2) (m + 1 , n + 2) (m + 2 , n − 1) (m + 2 , n + 1) à condition évidemment de ne pas sortir de l’échiquier. La figure ci-dessous représente les déplacements possibles du cavalier en case (4 , 4). 40 CHAPITRE 4. ESSAIS SUCCESSIFS ❡ ❡ ❡ ❡ ❡ ❡ ❡ (8, 8) ❡ ❡ ❡ ❡ ❡ ❡ ❡ ❡ ❡ (1, 1) ❡ ❡ La figure suivante montre deux parcours pour aller de la case (4 , 4) à la case (4 , 3) ou l’inverse , l’un de longueur 4, l’autre (optimal) de longueur 3. ❡ ❡ ❡ ❡ ❡ ❡ ❡ ❡ ❡ ❡ ❡ ❡ ❡ ❡ ❡ ❡ ❡ ❡ ❡ ❡ L’élément du vecteur de la récursion sera une position du cavalier, sous la forme d’un couple (X[i].abs , X[i].ord) qui décrit l’abscisse et l’ordonnée de la position. Question 1 : Donner l’algorithme qui décrit le parcours optimal du cavalier. Anonyme Exercice 4.3 (♥ Le voyageur de commerce.). Un voyageur de commerce doit visiter n villes. On suppose qu’il y a une route entre chaque paire de villes, avec une certaine longueur connue à l’avance. Le voyageur part d’une certaine ville et doit y revenir. Question 1 : Dans quel ordre le voyageur doit-il visiter les villes pour minimiser le la longueur totale du chemin parcouru ? Anonyme Exercice 4.4 (♥ Circuits eulériens.). Un graphe G = (N, V) est composé d’un ensemble N fini de nœuds et d’un ensemble V ⊂ N × N d’arcs. Chaque arc peut donc être vu comme un lien d’un nœud vers un autre. Sur l’exemple de la figure, on a : N = {A, B, C} V = {(A, B), (A, C), (B, A), (B, B), (B, C), (C, A), (C, B)} 4.2. EXERCICES 41 B A C Etant donné un nœud q, l’ensemble des nœuds s tels qu’il existe un arc (q, s) est l’ensemble des nœuds successeurs de q. On le note succ(q). L’ensemble des nœuds p tels qu’il existe un arc (p, q) est l’ensemble des nœuds prédécesseurs de A. On le note pred(q). Sur le graphe de la figure : pred(B) = {A, B, C} et succ(C) = {A, B} Un graphe est dit eulérien si chaque nœud possède le même nombre de successeurs et de prédécesseurs et que ce nombre n’est nul pour aucun nœud. Par exemple, le graphe de la figure est eulérien, puisque A, B et C ont respectivement deux, trois et deux successeurs et deux, trois et deux prédécesseurs. On appelle circuit dans un graphe une suite d’arcs (n1 , n2 ), (n2 , n3 ) . . . , (ni , ni+1 ), (ni+1 , ni+2 ), . . . (np , n1 ) On l’écrit plus simplement : n1 , n2 , . . . , ni , . . . np , n1 Un circuit est composé de p arcs et passe par p nœuds. Un circuit est dit eulérien pour le graphe G s’il utilise une fois et une seule tous les arcs de G. Il a été démontré par Euler en 1736 qu’un graphe possède (au moins) un circuit eulérien si et seulement si il est eulérien. Par exemple, sur le graphe de la figure, les circuits suivants sont eulériens : ABBCACBA ABCACBBA Question 1 : Donner tous les circuits eulériens du graphe de la figure. Question 2 : Donner en pseudo-code un algorithme par essais successifs permettant de lister tous les circuits eulériens d’un graphe eulérien. On supposera que les structures de données suivantes sont accessibles : l’ensemble N des nœuds du graphe, l’ensemble V des arcs et pour chaque nœud s, les ensembles succ(s) et pred(s). Question 3 : Expliquer en détail comment sont construites les « briques » de l’algorithme : domaine, SolutionEncorePossible, etc. Anonyme Exercice 4.5 (♥ Chemins hamiltoniens : les dominos.). On considère le jeu suivant : six dominos portent chacun un mot de quatre lettres, tiré d’un dictionnaire de mots valides. On peut juxtaposer deux dominos si les deux dernières lettres du premier forment un mot de quatre lettres avec les deux premières lettres du second domino. Par exemple, le domino SEME peut être mis après le domino REMI, puisque MISE est un mot (dans notre exemple, le dictionnaire est celui du frnançais, on ne tient pas compte des accents, on accepte les noms propres). 42 CHAPITRE 4. ESSAIS SUCCESSIFS On représente un tel jeu par un graphe : les six dominos sont représentés dans les nœuds du graphe et un arc représente la possibilité de placer deux dominos l’un après l’autre. Le jeu consiste à créer une chaîne de six dominos. Une solution est, par exemple : BETE TELE VECU REMI SEME LESE On affecte un numéro à chaque domino : 1 BETE 2 SEME 3 VECU 4 LESE 5 TELE 6 REMI Le but de cet exercice est d’écrire un programme qui donne toutes les solutions à ce problème. D’une manière plus générale, il s’agit de trouver tous les chemins dans un graphe qui passent une fois et une seule par tous les nœuds. Un tel chemin est dit hamiltonien. Question 1 : Tracer le graphe du jeu Question 2 : On impose de commencer par le domino de numéro 1 (BETE) puis d’aller au domino numéro 5 (TELE). Trouver toutes les solutions en développant un arbre. Question 3 : Donner un programme qui trouve tous les chemins hamiltoniens dans un graphe. Anonyme Exercice 4.6 (♥ Isomorphisme de graphes). Un graphe orienté G est constitué d’un ensemble fini S de sommets et d’un ensemble A d’arcs, avec A sous-ensemble de S × S . Deux graphes G = (S, A) et H = (T, B) sont isomorphes s’il existe une relation bijective entre S et T qui fasse correspondre bijectivement A et B. Par exemple, les deux graphes G1 et G2 donnés sur la figure sont isomorphes pour la bijection : 1 c 2 a 3 b 4 e 5 d L’arité positive (resp : négative) d’un noeud est le nombre d’arcs qui en partent (resp : qui y arrivent). Dans l’exemple, le noeud 1 du sommet G1 a pour arité positive 3, pour arité négative 1, comme le sommet c du graphe G2 . On s’intéresse à l’écriture d’un algorithme dont l’entrée est un couple de graphes (G, H) ayant le même nombre de sommets et la sortie un entier indiquant le nombre d’isomorphismes possibles entre G et H. a 1 2 3 b c 4 5 d e Le graphe G1 . Le graphe G2 . Question 1 : Donner tous les isomorphismes possibles entre G1 et G2 . Conclusion ? Question 2 : Donner le schéma d’un algorithme de base du type « essais successifs »pour résoudre ce exercice. Spécifier la représentation vectorielle du exercice et l’instanciation des procédures de l’algorithme général. Dans cette première version, la procédure Satisfaisant rendra toujours la valeur VRAI et on ne fera pas intervenir les arités des sommets. Décrire soigneusement ce que doit faire la procédure SolutionEncorePossible. Quelle est la complexité au pire de l’algorithme ? 4.2. EXERCICES 43 Question 3 : Décrire une procédure Satisfaisant utilisant l’arité des noeuds de G et H. Question 4 : Anonyme. ARCHIVES ENSSAT 1996-97. Exercice 4.7 (♥ Coloriage d’un graphe). Un graphe non orienté G = (N, V) est composé d’un ensemble N fini de nœuds et d’un ensemble V ⊂ N × N d’arêtes. Chaque arête peut être vue comme un lien symétrique entre deux nœuds . b a e c d Sur l’exemple de la figure, on a : N = {a, b, c, d, e} V = {(a, b), (a, c), (b, c), (b, e), (c, d), (d, e)} On peut représenter un graphe avec n nœuds par sa matrice d’adjacence G[i, j], pour i et j = 1, n, avec G[i, j] = 1 s’il existe un arête entre les nœuds i et j et avec G[i, j] = 0 sinon. Pour ce type de graphe non orienté, G[i, j] = G[j, i]. De plus on suppose ici qu’il n’y a pas d’arête en boucle sur un nœud : G[i, i] = 0 pour i = 1, n. Dans notre exemple, on a : 0 1 1 0 0 1 0 1 0 1 G= 1 1 0 1 0 0 0 1 0 1 0 1 0 1 0 Un coloriage d’un graphe non orienté consiste à affecter à chaque nœud une couleur parmi m, de sorte que deux arêtes de la même couleur ne soient pas reliés par un arête. Par exemple, avec trois couleurs notées 1, 2 et 3, on peut colorier le graphe précédent de cette manière : b 2 1 a e 3 3 c d 1 44 CHAPITRE 4. ESSAIS SUCCESSIFS Ce coloriage peut être représenté par le vecteur X[1 : 5] = (1, 2, 3, 1, 3), dont la première composante est la couleur du premier nœud a, la seconde est la couleur du second nœud b, et ainsi de suite pour les cinq nœuds . Question 1 : Donner un autre coloriage de ce graphe qui conserve la couleur des nœuds a et b. Question 2 : On veut écrire un algorithme par Essais Successifs qui écrit tous les coloriages différents à m couleurs d’un graphe quelconque, ayant n nœuds , dont on connaît la matrice d’adjacence G. 4.2. EXERCICES 45 Le schéma de l’algorithme sera le suivant (noter que Domaine est un appel de procédure) : 1: Procedure Coloriage(i) 2: début 3: Si ← Domaine(x) ; 4: pour x parcourant Si faire 5: si Satisfaisant(x) alors 6: Enregistrer(x) ; 7: si SolutionTrouvée alors 8: EcrireSolution 9: sinon 10: si SolutionEncorePossible alors 11: Coloriage(i + 1) 12: fin si 13: fin si 14: Défaire(x) /% le contraire de Enregistrer %/ 15: fin si 16: fin pour 17: fin a) Que représente x ? b) Expliquer comment définir Domaine(x) et Satisfaisant ? c) Montrer que les autres paramètres du programme (SolutionTrouvée, SolutionEncorePossible) sont très simples. d) Ecrire les procédures Coloriage, Domaine et Satisfaisant (les entiers n et m, le vecteur X et la matrice G sont des variables globales). e) Dessiner la partie de l’arbre de récursion sur le graphe exemple où le nœud a prend la couleur 1 et b la couleur 2. Préciser l’ordre des appels récursifs. f) Combien y a-t’il de coloriages différents à trois couleurs pour le graphe exemple ? Anonyme Exercice 4.8 (♥ Partition de tableau.). On considère un tableau T [1 : n] d’entiers positifs, triés par ordre croissant. n est un nombre pair strictement positif. Question 1 : On désire partitionner T en deux sous-tableaux de dimension n 2 , nommés T1 et T2 tels que : n n 2 2 X X S1 = T1 (i) S2 = T2 (i) avec S1 ≤ S2 et S2 − S1 maximal i=1 i=1 Donner le principe de construction de T1 et T2 . Quelle est la complexité de la procédure ? Question 2 : On désire maintenant partitionner T en deux sous-tableaux de taille n/2, appelés T3 et T4 tels que : S3 = n n 2 X 2 X i=1 En notant S = Pn T3 (i) S4 = i=1 T4 (i) avec S3 ≤ S4 et S4 − S3 minimal i=1 T (i), montrer que l’exercice posé est équivalent à : S − 2S3 minimal Question 3 : Proposer une solution par une procédure à essais successifs n’utilisant aucun élagage, où la solution est un tableau binaire T3 de taille n/2. T3 (i) = 1 signifie que T (i) est affecté à T3 . Question 4 : On n’a pas utilisé dans la question précédente le fait que T est trié. Peut-on proposer des élagages améliorant la procédure précédente en tenant compte de cette propriété ? 46 CHAPITRE 4. ESSAIS SUCCESSIFS Remarque : Ce problème est un cas particulier de sac à dos 0/1 (voir le chapitre 8.1.1, page 75). Par conséquent, il existe aussi un algorithme de programmation dynamique pour le résoudre, de complexité temporelle en Θ(nS). Cet algorithme est développé pour le problème voisin du sousensemble de somme donnée dans l’exercice 99.17, page 95. Anonyme Exercice 4.9 (♥ Les élections présidentielles à l’américaine.). Les élections présidentielles américaines se déroulent en gros de la façon suivante : dans chacun des 50 états, les électeurs votent pour un des deux candidats, démocrate ou républicain. Si c’est le candidat républicain qui dépasse l’autre en nombre de voix dans cet état, l’état enverra à Washington des « grands électeurs », tous républicains. Si c’est le candidat démocrate qui gagne dans cet état, l’état enverra à Washington le même nombre de grands électeurs, tous démocrates. Le nombre des grands électeurs dépend de la taille de l’état. Pour finir, les grands électeurs se retrouvent à Washington et votent conformément à leur étiquette. Le président élu est celui qui a le plus de voix de grands électeurs. En pratique, sur un total de 538 grands électeurs, la Californie en a 54. Le Texas en compte 32, l’état de New York 33, la Floride 25, la Pennsylvanie 23, l’Illinois 22, etc. Les autres chiffres sont plus faibles. Question 1 : Donner un algorithme pour énumérer toutes les configurations où les deux candidats se retrouvent avec le même nombre de voix de grands électeurs. Cet algorithme doit fonctionner pour les Etats-Unis et pour tous les pays où se pratique ce type d’élections. Evidemment, on supposera connaître le nombre d’états et le nombre de grands électeurs par état. Question 2 : Montrer que le nombre de telles configurations est pair. Question 3 : Donner une modification de l’algorithme qui n’en produit que la moitié. Question 4 : Proposer une modification très simple de la constitution américaine pour que l’élection soit toujours effective, c’est à dire que les deux candidats ne puissent pas avoir le même nombre de voix de grands électeurs. Remarque : Ce problème est un cas particulier de sac à dos 0/1 (voir le chapitre 8.1.1, page 75). Par conséquent, il existe aussi un algorithme de programmation dynamique pour le résoudre, de complexité temporelle en Θ(nN), où N est le nombre total de grands électeurs. Cet algorithme est développé pour le problème voisin du sous-ensemble de somme donnée dans l’exercice 99.17, page 95. Anonyme Exercice 4.10 (♥ Crypto-arithmétique.). On cherche à résoudre des problèmes qui se présentent sous la forme d’une opération arithmétique sur des lettres. Le but est d’affecter un chiffre (0 à 9) à une lettre, sous la condition que le même chiffre ne soit pas affecté à deux lettres, et qu’une lettre vaille toujours le même chiffre. Le résultat de cette affectation doit fournir une opération arithmétiquement correcte en base 10. Par exemple le problème de crypto-arithmétique : NEUF + UN + UN = ONZE que l’on peut aussi écrire : N E U U O N U Z F + N + N E 4.2. EXERCICES 47 a une solution que l’on peut représenter par : (N = 1 , E = 9 , U = 8 , O = 2 , Z = 4 , F = 7) parce que : 1987 + 81 + 81 = 2149 Les trois exemples suivants montrent des affectations incorrectes : – La proposition 1988 + 81 + 81 = 2150 suppose : U = F = 8 – La proposition 1986 + 82 + 82 = 2150 suppose que 1 et 2 sont affectés à N et 0 et 9 à E. – Quant à l’affectation (N = 2 , E = 9 , U = 8 , O = 3 , Z = 4 , F = 7), qui correspond à 2987 + 82 + 82 = 3242, elle est arithmétiquement fausse. On supposera qu’il existe une procédure og CalculExactfg qui, appelée pour une affectation des lettres, rend VRAI si l’opération est arithmétiquement correcte et FAUX sinon. Question 1 : Donner le principe d’un algorithme permettant de trouver toutes les solutions à tout problème de crypto-arithmétique. On emploiera la méthodologie « essais successifs », sans chercher d’élagages. Question 2 : Définir les paramètres du programme : le vecteur de la récursion, sa taille, le domaine de chaque composante, les procédures og Satisfaisantfg , og SolTrouvéfg , og Enregistrerfg et og Défairefg . Question 3 : Ecrire le pseudo-code. Question 4 : Pour un problème particulier comme celui donné ci-dessus, comment pourrait-on utiliser la procédure og SolEncorePossiblefg pour faire des élagages ? Vous paraît-il facile de trouver une technique générale pour l’utiliser ? Anonyme Exercice 4.11 (♥ Détermination de tous les déplacements d’un cavalier aux échecs.). Question 1 : On s’intéresse aux parcours par un cavalier partant de la case (xd , yd ) et devant se rendre à la case d’arrivée (xa , ya ). Question 2 : Faire l’analyse du problème posé en spécifiant les paramètres de la solution générique Question 3 : Donner le code d’un algorithme associé à cette analyse Anonyme Exercice 4.12 (♥ Les anagrammes.). On cherche à trouver tous les anagrammes d’un mot donné, c’est à dire tous les mots qui s’écrivent avec exactement les mêmes lettres, mais dans un ordre différent. Par exemple, le mot DINAR est un anagramme du mot RADIN. Par extension, on peut dire qu’un mot est un anagramme de lui-même et donc que le mot RADIN possède deux anagrammes, DINAR et RADIN (dans l’ordre alphabétique). Un cas simple. On supposera d’abord que toutes les lettres du mot de départ sont différentes, comme dans l’exemple précédent. La liste des mots est donnée par un dictionnaire D. On suppose qu’il existe une procédure chercher (mot, D) qui répond VRAI si le mot mot est dans D, FAUX sinon. On ne s’intéresse pas à la manière dont fonctionne cette procédure. La structure de données utilisée pour représenter un mot est un vecteur, dont la première composante est la première lettre du mot, la seconde composante la seconde lettre, et ainsi de suite. On commence par fabriquer un ensemble L, composé de toutes les lettres du mot de départ, sous la forme d’une liste dans l’ordre alphabétique. Dans notre exemple : L = {A, D, I, N, R}. On veut écrire un programme, par un schéma Essais Successifs (Toutes Solutions), qui produise tous les anagrammes du mot de départ une fois et une seule, y compris ce mot de départ. 48 CHAPITRE 4. ESSAIS SUCCESSIFS Question 1 : Définir le vecteur de récursion et le domaine de chaque composante, en utilisant un ensemble DéjàPris dans lequel se trouvent les lettres qui composent la solution partielle. Question 2 : Ecrire le pseudo-code de la procédure récursive et de son appel dans le programme principal. Question 3 : Dans quel ordre le programme écrit-il les anagrammes ? Pourquoi ? Question 4 : Quelle est la complexité au pire de la procédure, en nombre d’appels à chercher ? Le cas général. On enlève maintenant la contrainte que toutes les lettres du mot de départ sont différentes. On part de mots qui peuvent avoir au moins une lettre qui arrive deux fois, comme RATER. Dans ce cas, L n’est plus un ensemble, mais un sac de lettres. Dans cet exemple : L = {A(1), E(1), R(2), T (1)}. Question 5 : Comment aménager l’algorithme précédent pour écrire – Tous les anagrammes avec les répétitions dues à la multiplicité des lettres ? Par exemple, à partir de RATER, la liste de ces anagrammes sera ARRET , ARRET , RATER, RATER, TARER, TARER (on suppose pour cet exemple que le dictionnaire ne tient pas compte des accents). – Tous les anagrammes une fois et une seule chaque anagramme ? Dans ce cas, à partir de RATER, la liste des anagrammes doit être ARRET , RATER, TARER. Un autre algorithme. Voici un autre algorithme, complètement différent, qui n’utilise pas la méthode Essais Successifs. Pour chaque mot du dictionnaire, on regarde si le sac de lettres qui le compose est égal au sac de lettres du mot dont on cherche les anagrammes. Question 6 : Quelle est la complexité de cette méthode en nombre de comparaisons ? Conclusion ? Archives ENSSAT EII2 03-04 Exercice 4.13 (♥ Carrés latins.). Un carré latin d’ordre n est un tableau carré dans lequel les cellules contiennent les n éléments d’un ensemble S, qui sont disposés de telle manière qu’ils apparaissent une et une seule fois dans chaque ligne et dans chaque colonne. Chacune des lignes et des colonnes est donc constituée par une permutation des n éléments. Par exemple, pour n = 4 et S = {1, 2, 3, 4}, on a (parmi 576) les trois carrés latins suivants : 1 3 4 2 3 1 2 4 4 2 1 3 2 4 3 1 1 2 3 4 2 4 1 3 3 1 4 2 4 3 2 1 2 1 4 3 1 3 2 4 3 4 1 2 4 2 3 1 Le premier possède une particularité : chacune des deux diagonales est entièrement composée d’éléments identiques. Un tel carré latin est dit antidiagonal. Le second possède une autre particularité : les éléments de S apparaissent dans un ordre imposé (ici, l’ordre numérique croissant) sur la première ligne et sur la première colonne. Un tel carré latin est dit normalisé. Il y a 4 carrés latins normalisés et 18 antidiagonaux d’ordre 4. Le dernier n’a aucune propriété particulière. Question 1 : Donner le pseudo-code d’un programme permettant d’écrire tous les carrés latins normalisés à un ordre n donné. Question 2 : Démontrer qu’il n’y a pas de carré latin antidiagonal d’ordre impair. Donner le pseudo-code d’un programme permettant d’écrire tous les carrés latins antidiagonaux à un ordre n donné. Question 3 : Donner le pseudo-code d’un programme permettant d’écrire un carré latin antidiagonal et normalisé à un ordre n donné. LM 4.2. EXERCICES 49 Exercice 4.14 (♥ Le jeu de Sudoku.). Le but du jeu est de remplir un carré de neuf cases de côté, subdivisé en autant de carrés identiques, appelées régions, de façon à ce que chaque ligne, chaque colonne et chaque région contienne une fois et une seule les chiffres de 1 à 9. Au début du jeu, un certain nombre de chiffres sont déjà en place (dévoilés). Par exemple, il s’agit de terminer de remplir la grille suivante : 5 6 3 1 9 8 4 7 7 9 5 8 6 6 5 3 1 6 3 2 6 2 4 1 8 8 9 7 5 9 Question 1 : Donner un programme de type « essais successifs »qui compte toutes les solutions quand la grille de départ est vide (NB : le résultat est environ 7.1021 ; quel serait le temps de calcul de ce programme ?). Question 2 : Donner un programme de type « essais successifs »qui donne toutes les solutions à une grille dans laquelle un certain nombre de chiffres sont dévoilés. Voir Wikipedia à l’entrée : Sudoku. 50 **** 1*** 12** 13** ✂ ✂ 2*** 14** 141* 1421 1422 1423 1424 ✕ ✕ ✖ ✕ 143* 144* ✂ ✄ 22** 23** ✂ ✄ ✂ ... 24** 241* 2411 2412 2413 2414 ✕ ✕ ✰ ✕ 242* 243* 244* ✄ ✂ ✄ CHAPITRE 4. ESSAIS SUCCESSIFS ✄ 142* 21** ... Chapitre 5 Programmation par Séparation et Evaluation Progressive 5.1 Rappels 5.1.1 Principe : comparaison avec Essais Successifs La méthode PSEP (Programmation par Séparation et Evaluation Progressive) ou Branch and Bound est comparable à la méthode des essais successifs, en ce qu’elle recherche par énumération sans répétition, toutes les solutions d’un problème donné, ou (plus souvent) la meilleure solution à ce problème. Elle construit aussi un arbre des solutions partielles et procède autant que possible à des élagages. La principale différence est que l’ordre de développement des nœuds et des feuilles n’est pas fixé (rappelons qu’il est profondeur d’abord pour les essais successifs). PSEP choisit la solution partielle la plus prometteuse, sur un critère à définir pour chaque problème, et la développe en calculant ses successeurs dans l’arbre. Cette technique conduit à une programmation itérative. Toujours dans l’optique de comparaison avec les essais successifs, on cherche donc la meilleure valeur de vecteur X, dont chaque coordonnée xi peut prendre ses valeurs (s’instancier) dans un domaine fini Si . Comme dans la technique des essais successifs, on construit implicitement un arbre dont les nœuds internes sont des solutions partielles et les feuilles sont des solutions avec une certaine valeur. On cherche la solution de plus grande valeur. On part d’un vecteur X dont aucune coordonnée n’est instanciée (la racine de l’arbre, voir les figures 4.1 et 5.1). 5.1.2 Algorithme générique Le mot nœud désigne dans l’algorithme soit un nœud interne, soit une feuille. Les fils d’un nœud interne sont les nœuds obtenus en instanciant (dans le domaine qui lui correspond) la première coordonnée de X non instanciée. Le principe est de maintenir une liste de nœuds vivants, c’est à dire pouvant mener à une solution meilleure que la solution courante SolCourante. Cette liste s’appelle OPEN. La solution est initialisée par SolVide, qui signifie qu’on a pas trouvé de solution. Cet algorithme mènera par exemple à une construction comme sur la figure 5.1. dans laquelle les nœuds qui ne peuvent pas mener à une solution ne sont pas développés. L’ordre de développement (c’est à dire de choix dans OPEN) est indiqué. Il est important de remarquer que, de la manière dont les nœuds sont développés, il n’y a pas de risque de mettre dans OPEN un nœud qui y est déjà, ou qui y a déjà été. 51 52 CHAPITRE 5. PROGRAMMATION PAR SÉPARATION ET EVALUATION PROGRESSIVE 1: Procedure PSEP() 2: début 3: OPEN ← racine 4: SolCourante ← SolVide 5: tant que OPEN n’est pas vide faire Choisir dans OPEN le nœud le plus prometteur : n ; Enlever n de OPEN 8: pour tous les fils nj de n faire 9: si nj est une solution alors 10: si nj est meilleure que SolCourante alors 11: SolCourante ← nj 12: fin si 13: sinon 14: si nj peut mener à des solutions alors 15: ajouter nj à OPEN 16: fin si 17: fin si 18: fin pour 19: fin tant que 20: fin 6: 7: **** 1 0*** 2 00** 5 000* 11 01** 3 001* 6 0010 7 1*** 8 010* 10 0011 13 011* 4 0110 12 0111 9 Fig. 5.1 – Un exemple d’arbre que peut construire PSEP. L’ordre de développement, c’est à dire de choix dans OPEN, est indiqué pour chaque nœud. 5.2. EXERCICES 53 Dans cet exemple, la suite des listes OPEN créées au cours des itérations est la suivante (le nœud le meilleur est en gras) : 1 **** 2 0*** 1*** 3 1*** 00** 01** 4 1*** 00** 010* 011* 5 1*** 00** 010* 0110 0111 6 1*** 000* 001* 010* 0110 0111 7 1*** 000* 0010 0011 010* 0110 0111 8 1*** 000* 0011 010* 0110 0111 9 000* 0011 010* 0110 0111 10 000* 0011 010* 0110 11 000* 0011 0110 12 0011 0110 13 0011 14 5.1.3 Un point de vue plus général. 5.2 Exercices Exercice 5.1 (♥ Titre.). Question 1 : Question 2 : Question 3 : Anonyme Exercice 5.2 (♥ Le taquin.). Le taquin est un jeu dont le principe est de reconstituer un objet dans sa configuration standard, à partir d’une situation aléatoire. L’objet est une grille de taille 5 × 5, composée de petites tuiles numérotées de 1 à 15 qui peuvent glisser les une par rapport aux autres, en utilisant une place vide. Par exemple, à partir la configuration de départ du dessus, on peut en un coup atteindre les quatre configurations suivantes. 1 2 7 8 1 2 7 8 3 6 9 4 5 11 10 15 12 14 13 1 7 8 3 2 6 9 4 5 11 10 3 6 9 15 12 14 13 4 5 11 10 15 12 14 13 1 2 7 8 3 5 6 9 Le but du jeu est de rejoindre la position finale, celle-ci : 1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 4 11 10 15 12 14 13 1 2 7 8 3 6 9 4 5 11 10 15 12 14 13 54 CHAPITRE 5. PROGRAMMATION PAR SÉPARATION ET EVALUATION PROGRESSIVE en un nombre de coups minimum. Il est à noter que parmi les 16! ≃ 211012 positions de départ possibles, seulement la moitié permet de rejoindre la position finale. Pour savoir si la partie a une solution, un calcul préliminaire est possible. Donnons-le par l’exemple, sur la position de départ D : 1 2 7 8 3 6 9 4 5 11 10 15 12 14 13 Supposons que les cases de D sont numérotées comme dans la position d’arrivée (la case vide portant le numéro 16). Dans la configuration de départ D, on calcule les valeurs P(i), pour i = 1, 16, où p(i) est le numéro de la case où se trouve le chiffre i. Par exemple P(13) = 16, P(3) = 2. On calcule ensuite la valeur L(i), qui est égale au nombre de cases de D de numéro j, avecj < i telles que P(j) > P(i). Par exemple, L(1) = 0, L(4) = 1, L(12) = 6. P16 Pour finir, on calcule S = B + i=1 L(i), avec B = 1 si la case vide est sur une des positions marquées 1 du tableau ci-dessous, 0 sinon. 0 1 0 1 1 0 1 0 0 1 0 1 1 0 1 0 Si S est pair, on peut atteindre le but à partir de D. Sinon, ce n’est pas la peine d’essayer. Le but de cet exercice est d’adapter la méthode PSEP pour trouver une suite de coups optimale qui mène à la configuration finale à partir de n’importe quelle configuration de départ. Les éléments que l’on va mettre dans OPEN sont des configurations du jeu, notées n, associées avec un certain coût c(n) qui mesure le nombre minimal (ou provisoirement connu comme tel) de coups joués à partir de la situation de départ pour arriver à cette configuration. Le principe de base est celui de PSEP : on choisit le meilleur élément dans OPEN (celui de coût minimal) et on le remplace par ses fils. Question 1 : Qu’appelle-t’on ici le fils d’une situation de jeu ? Question 2 : Dans PSEP classique, il n’y a pas de risque de mettre dans OPEN un nœud qui y est déjà, ou qui y a déjà été. Ce n’est pas le cas ici. Pourquoi ? Que faire pour remédier à ce problème ? Question 3 : Montrer que l’algorithme conduit à un développement de type largeur d’abord. Question 4 : On appelle h(n) une valeur qui estime inférieurement la distance en nombre de coups P16 de la configuration n à la configuration finale. Par exemple h(n) = i=1 h(i), où h(i) est la distance de Manhattan entre la case numéro i dans n et la case numéro i dans la configuration finale. Que se passe-t’il si on utilise c(n) + h(n) au lieu de c(n) comme valeur dans OPEN ? (Réponse intuitive). Sam Lloyd ([HOR78]). Cet exercice est une introduction informelle à l’algorithme A∗ ([RUS95]). Chapitre 6 Programmation dynamique. 6.1 Présentation Comme la méthode diviser pour régner, la programmation dynamique permet de résoudre des problèmes d’optimisations en combinant les solutions de sous-problèmes. En revanche, cette méthode est plus avantageuse lorsque les sous-problèmes ne sont pas indépendants, c’est à dire lorsqu’ils ont des sous-sous-problèmes en commun. Ici le terme de "programmation"1 dénote une méthode tabulaire qui consiste à construire une table mémorisant la solution de chaque sous-problème, un sous-problème est alors résolu au plus une seule fois. Les problèmes à résoudre doivent cependant satisfaire le principe d’optimalité de Bellman : “une2 solution optimale d’un problème peut s’obtenir à partir des solutions optimales de sous-problèmes”. Un exemple classique consiste à calculer le plus court chemin dans un graphe. On montre par l’absurde qu’un chemin est de longueur optimale si et seulment si il est composé de sous-chemins de longueur optimale. Soit un graphe G et un chemin A − B − C − D de longueur optimale (voir la Figure 6.1). Le coût de ce chemin est la somme des coûts des chemins A − B, B − C et C − D. Supposons qu’il existe dans le graphe G un chemin de B à C de coût moindre (en pointillé sur la figure) que le chemin de B à C choisi, alors il existe un chemin plus court de A à D. A B C D Fig. 6.1 – Application du principe d’optimalité à la recherche du plus court chemin dans un graphe. Cependant, le principe d’optimalité ne s’applique toujours comme par exemple dans le cas du calcul du plus long chemin sans cycle dans un graphe : soit le graphe de la Figure 6.2, le plus long chemin sans cycle de A à C est A − B − C, or le plus long chemin sans cycle de B à C est B − A − C. L’écriture d’un algorithme de programmation dynamique consiste, en général, à exprimer le problème en problèmes de taille inférieure et à établir une relation de récurrence entre ces derniers qui conduit à une définition de la table, cette dernière pouvant avoir un nombre quelconque de dimensions. Le remplissage de la table peut se faire alors de deux façons : récursivement, ce qui est trivial grâce à la définition de la table, ou de façon itérative. À la base, les algorithmes de programmation dynamique calculent des tables contenant les coûts des solutions optimales des sous-problèmes et non 1 Historiquement, Richard Bellman, alors qu’il était chercheur à la Rand Corporation, aurait choisi le nom de “programmation dynamique” pour satisfaire son supérieur qui ne supportait pas les mots “recherche” et “mathématiques”. (D’après un article de J.-C. Culioli dans la Recherche 2003). 2 Notons qu’un problème n’a pas nécessairement une solution optimale unique. 55 56 CHAPITRE 6. PROGRAMMATION DYNAMIQUE. A B C Fig. 6.2 – Exemple où le principe d’optimalité ne s’applique pas : le plus long chemin sans cycle de A à C est de longueur 2 (A − B − C) or le plus long chemin de A à B n’est pas de longueur 1 mais de longueur 2 (A − C − B). les solutions elle-mêmes. Il sera alors nécessaire en outre, soit de produire une méthode qui calcule une solution à partir des coûts des sous-solutions, soit d’enrichir la table afin de retrouver aisément la solution optimale produite. Parmi les applications classiques de la programmation dynamique on compte : – l’algorithme de Floyd-Warshall, qui calcule le plus court chemin dans un graphe, – la construction d’arbres binaires optimaux, – la triangulation d’un polygone, – le calcul de la distance entre deux chaînes, – les problèmes d’alignement de séquences, – etc . Pour terminer, on notera que l’efficacité de la programmation dynamique peut être améliorée grâce à la technique de l’évaluation paresseuse où toute valeur est calculée à la demande (c’est à dire uniquement si on en a besoin). 6.2 Exercices 6.2.1 Séquences et automates. Exercice 6.1 (♥ La plus longue sous-séquence ascendante). Les objets sur lesquels on travaille sont des séquences de nombres entiers de longueur strictement supérieure à 1. Par exemple, une telle séquence (de longueur 9) est : T = (11, 5, 2, 8, 7, 3, 1, 6, 4). On note de manière générale S = (s1 , . . . , si , . . . , sn ) une séquence de longueur n > 1. On appelle sous-séquence croissante (SSC) de S une séquence de longueur inférieure ou égale à n, dont : – les éléments sont pris de gauche à droite dans S ; – les éléments croissent strictement de gauche à droite. Par exemple, (2, 3, 4) et (1, 6) sont des SSC de T = (11, 5, 2, 8, 7, 3, 1, 6, 4). La seconde est particulière, puisque ses éléments se suivent dans T . On dit qu’elle est une sous-séquence croissante contiguë (SSCC) de T . Le but de cet exercice est de trouver la longueur des plus longues SSCC et SSC d’une séquence quelconque S, notées LSSCC(S) et LSSC(S). Par exemple, les plus longues SSCC de T sont (2, 8) et (1, 6), donc LSSCC(T ) = 2. Les plus longues SSC de T sont (2, 3, 4) et (2, 3, 6), donc LSSC(T ) = 3. L’opération élémentaire de mesure de la complexité est la comparaison. Question 1 : Montrer que, pour toute séquence S, on a : LSSC(S) ≥ LSSCC(S). Question 2 : Donner le principe d’un algorithme en Θ(n) pour calculer LSSCC(S). Question 3 : Pourquoi un algorithme comme le précédent ne permet-il pas de calculer LSSC(S) avec un algorithme en Θ(n) ? Question 4 : Pour calculer LSSC(S), on va remplir un tableau à deux lignes et n colonnes. La première ligne contient si (l’élément de rang i de S), la seconde li , qui est par définition la longueur de la plus longue sous-séquence de S dont le dernier élément est si . Dans notre exemple : 6.2. EXERCICES 57 i si li 1 11 1 2 5 1 3 2 1 4 8 2 5 7 2 6 3 2 7 1 1 8 6 3 9 4 3 Montrer et expliquer la relation de récurrence suivante. Quelle valeur faut-il donner au terme Max quand on n’a aucun j tel que 0 < j < i pour lequel sj < si ? l1 = 1 li = 1 + Max lj 0<j<i sj <si Question 5 : Ecrire le programme qui calcule LSSC(S) pour toute séquence S de longueur n. Quel est son ordre de grandeur de complexité ? La solution est page ??. Exercice 6.2 (♥ La plus longue sous-séquence commune.). Définitions On utilise l’alphabet des 26 lettres du français. On appelle séquence sur cet alphabet toute suite finie de lettres, par exemple s1 = befddzhh, s2 = kpedzmh, s3 = altitude, s4 = piteux. On appelle longueur d’une séquence le nombre de ses lettres. Par exemple, la longueur de s1 vaut 8. On note x(i) la ième lettre de la séquence x. Par exemple, s3 (4) = i. On note par convention ǫ la séquence de longueur nulle. On appelle préfixe, noté x[1..i], d’une séquence le début x = x(1) . . . x(i) de cette séquence. Par exemple bef et befd sont des préfixes de s1 , mais pas bed. On appelle sous-séquence d’une séquence x une séquence obtenue en enlevant des lettres à x. Par exemple eh est une sous-séquence de s1 et de s2 . On dit dans ce cas que eh est une sous-séquence commune à s1 et s2 . Le problème Etant donné deux séquences x et y, le problème est de trouver la longueur lssc(x, y) de leur(s) plus longue(s) sous-séquence(s) commune(s). Par exemple, lssc(s3 , s4 ) = 3 car ite et itu sont leurs plus longues sous-séquences communes. Soit |x| = m la longueur de x et |y| = n la longueur de y. On va remplir un tableau LSSC(i, j), avec i = 0, |x| et j = 0, |y|, dont l’élément courant est la longueur de la plus longue sous-séquence commune au préfixe de longueur i de x et au préfixe de longueur j de y. Par exemple, si x = s3 = altitude et y = s4 = piteux, LSSC(6, 1) = 0 et LSSC(5, 2) = 1. Question 1 : Notons z = z(1) . . . z(k) une plus longue sous-séquence commune à x et y. Montrer que (a) Si x(m) = y(n) alors z(k) = x(m) = y(n) et z[1..k − 1] est la plus longue sous-séquence commune à x[1..m − 1] et y[1..n − 1] (b) Si x(m) 6= y(n) et z(k) 6= x(m) alors z est une plus longue sous-séquence commune à x[1..m − 1] et y (c) Si x(m) 6= y(n) et z(k) 6= y(n) alors z est une plus longue sous-séquence commune à x et y[1..m−1] Question 2 : En déduire la formule de récurrence générale qui permet de calculer LSSC(i, j) en fonction de LSSC(i−1, j−1), LSSC(i−1, j) et LSSC(i, j−1) (ne pas oublier de donner son initialisation). Question 3 : Ecrire le pseudo-code de programmation dynamique qui, étant donné deux séquences, calcule la longueur de leur(s) plus longue(s) sous-séquence(s) commune(s). Comment retrouver la (ou une des) plus longue(s) sous-séquence(s) commune(s) en mémorisant des informations complémentaires dans cet algorithme ? Comment les retrouver toutes ? Traiter en exemple les séquences x = abcbdab et y = bdcaba. 58 CHAPITRE 6. PROGRAMMATION DYNAMIQUE. La solution est page ?? Exercice 6.3 (♥ Distance entre séquences : algorithme de Wagner et Fisher.). Définitions On se donne un alphabet X, c’est-à-dire un ensemble fini de lettres dont la taille est notée |X| ; soit par exemple l’alphabet X = {a, b, c}. Une séquence (ou chaîne, ou mot ) sur X est une suite finie de lettres de X. La longueur |u| d’une séquence u est son nombre de lettres. Soit par exemple les séquences u = acbb et v = cab. On a |u| = 4. On appelle préfixe d’une séquence une séquence par laquelle elle commence. Par exemple, le préfixe de longueur 2 de u est ac. On note u[i] la lettre numéro i de u. Par exemple : u[3] = b On note ǫ la séquence de longueur nulle. Cette notion sera utile ici à deux choses : d’abord, le préfixe de longueur 0 de toute séquence est donc ǫ. Ensuite, étant donnée une séquence u = u1 u2 ...un , on peut y insérer autant de ǫ que l’on veut sans en changer la signification. On appellera « superséquence »de u la séquence u avec de telles insertions. Par exemple, si u = bcaa, une superséquence de u est : ǫbcǫǫaa. Par abus de langage, on dira que la longueur de cette surperséquence est 7. Soit deux séquences u = u1 u2 ...un et v = v1 v2 ...vm et deux superséquences de u et de v de même longueur. On appelle « alignement » entre u et v la mise en correspondance lettre à lettre des deux superséquences. On exige de plus qu’un alignement n’associe pas deux ǫ. Par exemple, entre les mots u = bcaa et v = acbca, on peut créer l’alignement : ǫ | a ǫ | c b | b c | ǫ a | c a | a Une formulation alternative de l’alignement entre deux séquences est celle de « trace », dans laquelle on utilise les séquences sans y insérer de caractère ǫ. Dans l’exemple précédent, la trace correspondante est : b a c c a b a c a Une trace doit être telle que deux traits d’association entre lettres ne se croisent jamais. Sous cette contrainte, on peut construire un3 alignement équivalent à une trace et construire de façon unique un alignement à partir d’une trace. Un alignement ou une trace peut s’interpréter comme une suite d’opérations élémentaires d’édition entre séquences : insertions, suppressions et transformations de lettres pour former la seconde séquence à partir de la première. Dans l’exemple précédent, l’alignement s’interprète comme la suite de transformations, entre autres possibilités : 1. insertion de a 4. suppression de c 2. insertion de c 5. transformation de a en c 3. transformation de b en b 6. transformation de a en a Pour donner une valeurs aux coûts des insertions, des suppressions et des transformations, on définit une matrice δ de nombres réels positifs ou nuls de taille |X| + 1 × |X| + 1. C’est une matrice qui définit une distance : elle symétrique, de diagonale nulle et vérifiant l’inégalité triangulaire. La valeur d’un élément de cette matrice peut s’interpréter comme le coût de transformer une lettre en l’autre ou comme le coût de suppression et d’insertion pour chaque lettre. Par exemple, sur un alphabet de trois lettres, une telle matrice pourrait être : 3 Parfois plusieurs, mais ils ont la même interprétation. 6.2. EXERCICES 59 δ ǫ a b c ǫ 0 1 1 1 a 1 0 1.5 1.2 b 1 1.5 0 1.7 c 1 1.2 1.7 0 Dans cet exemple, le coût de suppression de a est 1 (δ(a, ǫ) = 1), le coût d’insertion de b est 1 (δ(ǫ, b) = 1), le coût de transformation de a en c est 1.2 (δ(a, c) = 1.2). On appelle le coût d’un alignement la somme des coûts élémentaires des opérations qui la constituent. Dans l’exemple précédent, le coût de l’alignement est donc : 1 + 1 + 0 + 1 + 1.2 + 0 = 5.2 Un autre alignement entre les mots u = bcaa et v = acbca est par exemple, pour un coût de 1.5 + 0 + 1.5 + 1 + 0 = 4 : b | a c | c a | b ǫ | c a | a Le problème. Le problème est de trouver le coût d’un alignement optimal, c’est à dire le moins coûteux, entre deux séquences u et v. On définit pour cela une matrice ∆(0 : n , 0 : m), de taille (n + 1) × (m + 1), telle que ∆(i, j) est le coût de l’alignement optimal entre le préfixe de longueur i de u et le préfixe de longueur j de v. On cherche donc la valeur ∆(|u| + 1, |v| + 1) = ∆(n + 1, m + 1). Questions. Question 1 : Comment calculer ∆(0, j) pour j = 1, |v| et ∆(i, 0) pour i = 1, |u| ? Quelle valeur donner à ∆(0, 0) ? Question 2 : Cette question vise à établir une relation de récurrence qui calcule ∆(i, j) à partir de ∆(i, j − 1), ∆(i − 1, j), ∆(i − 1, j − 1), δ(u[i], ǫ), δ(ǫ, v[j]) et δ(u[i], v[j]). Pour cela, on remarque qu’un alignement peut s’interpréter comme un chemin dans un graphe. Cette interprétation est illustrée ci-dessous sur l’exemple d’alignement : ǫ ǫ b c a a | | | | | | a c b ǫ c a Un chemin dans le graphe entre le nœud étiqueté (0/0) et le nœud étiqueté (4/5) représente un alignement entre les séquences u = bcaa et v = acbca. L’alignement précédent est marquée en gras. a 4/0 4/1 4/2 4/3 4/4 4/5 a 3/0 3/1 3/2 3/3 3/4 3/5 c 2/0 2/1 2/2 2/3 2/4 2/5 b 1/0 1/1 1/2 1/3 1/4 1/5 0/0 0/1 0/2 0/3 0/4 0/5 a c b c a (a) Définir dans le cas général un tel graphe pour deux séquences quelconques u et v. (b) Montrer, en donnant des longueurs calculées à partir de δ aux arcs du graphe, que’un alignement optimal entre deux mots correspond au calcul d’un plus court chemin dans ce graphe. (c) Compte tenu de la forme particulière du graphe, donner une relation de récurrence pour calculer la longueur d’un plus court chemin entre le nœud (0/0) et tout autre nœud de ce graphe. 60 CHAPITRE 6. PROGRAMMATION DYNAMIQUE. (d) En déduire la relation de récurrence du calcul du coût d’un alignement optimal en remplissant la matrice ∆(0 : n , 0 : m). Le coût de l’alignement optimal doit être la valeur de ∆(n, m). Question 3 : Combien y a-t’il d’alignements différents entre deux mots u et v ? Question 4 : Donner la procédure de Wagner et Fisher WF qui calcule le coût d’un des alignements de coût minimal entre deux séquences quelconques sur un alphabet Σ, connaissant la matrice δ, avec une complexité en temps O(nm) (l’opération élémentaire est la comparaison) et en même complexité pour la taille O(nm) (rappelons que n = |u| et m = |v|). Question 5 : Montrer que si l’on peut réduire la complexitéé en taille à O(Max(n, m)) est suffisante. Ecrire ce programme (appelé Wagner et Fisher « linéaire », ou WFL). Question 6 : On prend l’alphabet du français, avec : – pour toute lettre α : δ[α, ǫ] = δ[ǫ, α] = 2 ; – si α et β sont deux consonnes ou sont deux voyelles : δ[α, β] = δ[β, α2] = 1 ; – si α est une consonne et β une voyelle : δ[α, β] = δ[β, α] = 3 ; Donner la matrice ∆ pour les séquences malin et coquine. Question 7 : Comment est-il possible de reconstituer un alignement optimal à partir de ∆ ? Dans l’exemple précédent, cet alignement est unique. Est-ce cas en général (indication :essayer u = rien et v = est). Question 8 : Montrer que l’alignement optimal entre les phrases miroir u et v se déduit directement de celui entre u et v, s’il est unique. Et s’il ne l’est pas ? Question 9 : Montrer que, puisque δ définit une distance, alors ∆ définit aussi une distance (d’où le titre de cet exercice). La solution est page ?? Exercice 6.4 (♥ Dissemblance entre séquences.). Présentation. On se donne un alphabet X, c’est-à-dire un ensemble fini de lettres dont la taille est notée |X| ; soit par exemple l’alphabet X = {a, b, c}. Une chaîne (ou séquence) sur X est une suite finie de lettres de X telles que les séquences u = acbb et v = cab. On dispose d’une distance d sur X qui se représente par une matrice de taille |X| × |X|, symétrique, de diagonale nulle et vérifiant l’inégalité triangulaire. On cherche à calculer à partir de d une association optimale entre deux chaînes sur X. On appelle « association »entre deux chaînes u = u1 u2 ...un et v = v1 v2 ...vm une suite de couples (i, j), où i indice une lettre de u et j une lettre de v. Une association entre u et v doit respecter les contraintes suivantes : – aucune lettre ne peut être détruite ou insérée : à chaque lettre de u doit en correspondre au moins une dans v et réciproquement. – si plusieurs lettres de u (respectivement v) correspondent à une lettre de v (respectivement u), elles doivent être contigües. Par exemple, entre les mots u = bcaa et v = acbca, on peut établir, parmi beaucoup d’autres, la correspondance : (b, a), (b, c), (b, b), (c, b), (c, c), (a, a)(a, a) qui se décrit sur les indices : (1, 1), (1, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5) ou par la figure qui suit. De manière plus formelle, une association est une suite de couples d’indices telle que : 1. le premier terme de la suite est le couple (1, 1) ; 2. le dernier terme de la suite est le couple (n, m) ; 3. le terme (i, j) ne peut être suivi que de l’un des trois termes : (i, j + 1), (i + 1, j) ou (i + 1, j + 1). 6.2. EXERCICES 61 b c a a a c b c a A chaque couple (i, j) composant une association correspond une valeur de la matrice d : la distance entre les lettres de rang i et j dans X. Le coût de l’association est défini comme la somme de la valeur de tous ses couples. Soit la matrice d suivante : a b c a 0 2 1.5 b 2 0 1 c 1.5 1 0 Par exemple, entre les mots u = bcaa et v = acbca, la correspondance vue ci-dessus : (1, 1), (1, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5) a le coût de : d(b, a) + d(b, c) + d(b, b) + d(c, b) + d(c, c) + d(a, a) + d(a, a) = 2 + 1 + 0 + 1 + 0 + 0 + 0 = 4 La dissemblance entre deux chaînes est définie comme le coût de l’association qui a le coût le plus faible parmi toutes les associations possibles entre ces deux chaînes. On cherche un algorithme efficace pour la calculer. Questions. Question 1 : (3 pts) Quelle est la dissemblance entre les chaînes aaa et a pour la matrice d donnée ci-dessus ? Entre les mots aab et abb ? Entre les mots ab et bac ? Donner un exemple non trivial d’un couple de mots de dissemblance nulle. Question 2 : (4 pts) Formuler le problème comme un algorithme de programmation dynamique, en expliquant comment s’applique le principe d’optimalité. Proposer une structure de données et donner la relation de récurrence. Indication : utiliser le fait que, pour tout couple de séquences, la dernière lettre de la première est associée avec la dernière lettre de la seconde. Question 3 : (3 pts) Ecrire le programme en pseudo-code. Quelle est son ordre de grandeur exact de complexité en nombre de comparaisons ? Question 4 : (3 pts) Le faire tourner sur u = bcaa et v = acbca pour la matrice d ci-dessus. Donner les deux associations optimales. Question 5 : (2 pts) Comment un tel programme pourrait-il servir dans un correcteur orthographique de traitement de texte ? Que pourrait-on choisir comme valeurs pour remplir la matrice d ? Donner deux exemples de cas où il fonctionnerait mal. La solution est page ??. Exercice 6.5 (♥ Reconnaissance d’un mot par un automate fini non-déterministe.). On va définir informellement une machine à reconnaître les mots sur un alphabet Σ. Une telle machine, appelée automate fini non-déterministe (AFN) est un graphe (voir la définition d’un graphe à l’exercice 4.4, page 40) dont les nœuds sont appelés des états. On supposera pour le moment que chaque arc d’un AFN porte une lettre de Σ. On définit deux états particuliers appelés initial et final. Un mot est dit reconnu ou accepté par un AFN s’il existe au moins un chemin (une suite d’arcs) menant de l’état initial à l’état final dont la suite de lettres forme le mot. S’il n’en existe aucun, il est refusé. Dans l’AFN de la figure suivante, l’état initial p est indiqué par une flèche rentrante, l’état final r par une flèche sortante. L’alphabet est Σ = {a, b, c}. Les mots aaaab, abbccc, par exemple, sont 62 CHAPITRE 6. PROGRAMMATION DYNAMIQUE. acceptés, les mots aab ou b ne le sont pas. On remarque que le non-déterminisme vient du fait que deux arcs portant la même lettre partent de l’état p. a p b a q r b c Question 1 : Donner un algorithme de programmation dynamique que décide si un mot donné est accepté par un AFN donné. Indication : calculer par récurrence si un préfixe du mot peut se trouver entre l’état initial et un état quelconque de l’AFN (la définition de ce qu’est un préfixe se trouve à l’exercice ??). On peut voir le mot vide ǫ comme le préfixe de longueur nulle. On élargit la définition d’un AFN : les arcs peuvent maintenant porter n’importe quel mot sur Σ∗ , y compris le mot vide ǫ. Par exemple, dans l’AFN qui suit, les mots abbc ou aabbcc sont acceptés, le mot abba est refusé. a p b ab q r ǫ a c Question 2 : Est-ce que l’algorithme de la première question peut encore s’appliquer, après modifications ? On élargit encore la définition d’un AFN : il peut de plus y avoir plusieurs états initiaux et finaux. Question 3 : Est-ce que l’algorithme de la première question peut encore s’appliquer, après modifications ? La solution est page ?? 6.2.2 Optimisation combinatoire. Exercice 6.6 (♥ Le voyageur dans le désert). Un voyageur veut aller d’une oasis à une autre sans mourir de soif. Il connaît la position des puits sur la route et sait qu’il consomme exactement 1 litre d’eau au kilomètre. Il est muni d’une gourde pleine à son départ. Quand il arrive à un puits, il peut choisir de remplir sa gourde ; dans ce cas, il en vide le contenu restant dans le sable et la remplit entièrement au puits. A l’arrivée, il vide aussi ce qui reste dans la gourde. Question 1 : Le voyageur veut faire le moins d’arrêts possible. Comment va-t’il choisir les puits où il doit s’arrêter ? Indication : montrer qu’une stratégie gloutonne est optimale. Question 2 : Le voyageur veut verser dans le sable le moins de litres d’eau possible. Comment doit-t’il choisir les puits où il doit s’arrêter ? Indication : montrer que la même stratégie gloutonne est encore optimale. 6.2. EXERCICES 63 Question 3 : A chaque puits, y compris celui de l’oasis d’arrivée, un gardien lui fait payer autant d’unités de la monnaie locale que le carré du nombre de litres d’eau qu’il a versé. Comment doit-t’il choisir les puits où il doit s’arrêter pour payer le moins possible ? Indication : montrer sur un exemple que la même stratégie gloutonne n’est plus optimale. Construire une stratégie par programmation dynamique qui utilise les valeurs – P(i) : somme minimale payée au total depuis le puits numéro 1 (l’oasis de départ). jusqu’au puits numéro i, étant donné que le voyageur vide sa gourde au puits numéro i. – d(i, j) : nombre de kilomètres entre le puits numéro i et le puits numéro j. – D : volume de la gourde. Question 4 : Application : une gourde de 10 litres et des puits situés à 8, 9, 16, 18, 24 et 27 km de l’oasis de départ. L’arrivée est à 32 km. La solution est page ?? Exercice 6.7 (♥ Arbres binaires de recherche.). Soit un ensemble d’entiers {x1 , x2 , . . . , xn }, que l’on suppose strictement ordonnés : x1 < x2 . . . < xn . On sait que l’on peut organiser ces entiers en arbre binaire de recherche (ABR) de nombreuses manières différentes. Par exemple, pour ∀i = 1, 5, xi = i, deux ABR possibles sont : 4 2 1 2 5 3 1 3 4 ∗ ∗ 5 Fig. 6.3 – Deux ABR possibles pour l’ensemble {1, 2, 3, 4, 5}. On suppose maintenant que chaque élément xi a la probabilité pi d’être recherché. On peut donc Pn appeller coût d’un ABR A la valeur C(A) = 1 pi (di + 1), où di est la profondeur de xi : ce coût est l’espérance du nombre de tests que l’on doit faire pour trouver un élément existant dans cet ABR. On cherche à construire l’ABR de coût minimal, connaissant l’ensemble des xi et des pi associées. Pour les deux exemples précédents, les coûts respectifs valent 3p1 + 2p2 + 3p3 + p4 + 2p5 et 2p1 + p2 + 2p3 + 3p4 + 4p5 . Question 1 : Montrer que tout sous-arbre de l’ABR de coût minimal est lui-même de coût minimal. Question 2 : Soit SAG(A) le sous-arbre gauche d’un ABR A, SAD(A) son sous-arbre droit et N (A) la somme des valeurs pi pour tous les nœuds de A (les feuilles sont des nœuds). Si A est vide, N (A) vaut 0. Montrer que C(A) = C(SAG(A)) + C(SAD(A)) + N (A) On peut s’aider pour raisonner de l’application de la question 3, pour i = 1, 2, i = 2, 3, puis i = 1, 2, 3. Question 3 : On remplit un tableau S, tel que, pour chaque couple (i, t) : S[i, t] = i+t−1 X pj j=i Donner l’algorithme de calcul du tableau C, avec C[i, t] le coût de l’ABR de coût minimal ne contenant que les éléments {xi , . . . , xi+t−1 } (initialiser par C[1, i] pour tout i). 64 CHAPITRE 6. PROGRAMMATION DYNAMIQUE. Question 4 : Application sur un exemple. On prend les valeurs suivantes : i 1 2 3 4 5 xi 1 2 3 4 5 pi 0.05 0.1 0.2 0.15 0.5 Calculer le tableau S et remplir C au moins jusqu’à C[1, 4] La solution est page ?? Exercice 6.8 (♥ Produit chaîné de matrices.). Il s’agit de faire le produit des matrices : M1 × M2 × ..... × Mn avec le moins de multiplications possible. Les dimensions de ces matrices sont décrites dans un tableau D[0 : n], avec Mi [1 : D[i − 1], 1 : D[i]] On appelle cout(i, j) le nombre minimal de multiplications pour réaliser le produit de la chaîne partielle de matrices : Mi × ... × Mi+j . On cherche donc cout(1, n − 1). Question 1 : Soit les matrices M1 [10, 20], M2 [20, 50], M3 [50, 1] et M4 [1, 100]. Comparer le nombre de multiplications quand on fait les opérations dans l’ordre donné par ce parenthésage : (M1 × (M2 × (M3 × M4 ))) avec le nombre donné par ce parenthésage : ((M1 × (M2 × M3 )) × M4 ) Question 2 : Appliquer le principe d’optimalité pour trouver une récurrence. Question 3 : Donner le programme. Quelle est son ordre de grandeur de complexité ? Question 4 : Le faire tourner sur l’exemple des quatre matrices M1 [10, 20], M2 [20, 50], M3 [50, 1] et M4 [1, 100] Remarque. Le nombre de façons de faire le produit de n matrices est le nombre de parenthésages possibles d’une séquence de n éléments. On l’appelle nombre de Catalan C(n). Il est donné par la récurrence : n−1 X C(k)C(n − k) C(n) = k=1 et se calcule par récurrence comme : 1 2n − 2 ∈ Ω(4n /n3/2 ) C(n) = n n−1 La solution est page ?? Exercice 6.9 (♥ Formatage d’un paragraphe.). Pour un logiciel de traitement de texte, on cherche à disposer la suite des mots qui forment un paragraphe de manière à laisser le moins de blancs possible. Pour simplifier, on se donne les règles suivantes : – Chaque mot a une longueur égale à son nombre de caractères. – Un signe de ponctuation compte pour une lettre. – Les lignes commencent par un mot calé à gauche. – Chaque espace a la longueur d’une lettre. – On ne peut pas dépasser la longueur d’une ligne. – Il y a au moins un mot par ligne. – Les espaces sur la dernière ligne ne comptent pas. Par exemple, pour des lignes de taille égale à 25 caractères, on a les deux possibilités suivantes, parmi un grand nombre (les espaces sont représentées par le caractère « = ») : 6.2. EXERCICES 65 Ce=paragraphe=est=formaté de=deux=façons=========== différentes.============= Ce=paragraphe=est======== formaté=de=deux=façons=== différentes.============= Le premier formatage compte 3 espaces sur la première ligne, qui est complètement remplie, et 13 sur la seconde ligne. Le second compte 10 espaces sur la première ligne et 6 sur la seconde ligne. Ils comptent tous les deux 16 espaces. Pour éviter ce genre d’égalité, la mauvaise qualité d’un formatage (son coût ) se mesure par la somme des carrés du nombre d’espaces sur toutes les lignes, sauf la dernière. Le coût du premier formatage vaut 32 + 132 = 178 et celui du second 102 + 62 = 136. Le second est donc meilleur que le premier. Le but de cet exercice est de trouver, pour un texte donné et une longueur de ligne donnée, le formatage de coût minimal. Question 1 : Donner deux formatages analogues aux précédents pour le texte « Ce paragraphe est formaté de deux façons très différentes. »avec une longueur de ligne égale à 25. Calculer leur coût. Auraient-ils été égaux si on n’avait pas élevé le nombre d’espaces au carré ? Question 2 : Un algorithme glouton consiste à remplir chaque ligne le plus possible. Qu’en pensezvous ? Question 3 : Donner toutes les façons de formater la phrase « Racine est un musicien. »sur des lignes de longueur 10. Question 4 : On note les mots m1 , . . . , mN de longueur long(mi ), et L la longueur de la ligne. On note cout(i, j) le coût qui résulte de l’écriture des mots mi , . . . , mj sur la même ligne, quand c’est possible. Si c’est impossible (j < i ou j trop grand), cout(i, j) prend une valeur arbitrairement grande. On note C(i) le coût optimal de l’écriture de mi , . . . , mN sous la contrainte que mi soit au début d’une ligne. Donner la formule de récurrence qui calcule C(i) en fonction de cout(i, j) et C(j) pour j = i, N − 1. Question 5 : En déduire le pseudo-code d’un programme qui calcule cout(i, j) puis qui calcule le formatage un texte avec un coût minimal et, finalement, l’écrit. Question 6 : Faire tourner ce pseudo-code sur l’exemple de la Question 3. Question 7 : Faire tourner ce pseudo-code sur l’exemple suivant, avec L = 25 : Ce paragraphe est formaté de deux manières différentes La solution est page ?? Exercice 6.10 (♥ Découpe d’une planche.). On dispose d’une planche de longueur entière N que l’on veut découper en n segments de longueurs Pn entières l1 , l2 , . . . , ln avec i=1 li = N. Les segments doivent découper la planche de gauche à droite selon leur indice. Par exemple, si N = 10 et l1 = 2, l2 = 5, l3 = 3, les découpes doivent se faire aux abcisses 2 et 7 sur la planche. Découper la planche aux abcisses 5 et 8 donnerait trois segments de même longueur, mais pas dans le bon ordre : c’est interdit. On peut illustrer cette contrainte par exemple en imaginant que sur la planche est écrit un texte et que la découpe doit donner un segment par mot. On peut bien sûr procéder de gauche à droite. Mais on cherche à minimiser un certain coût pour la découpe, qui est fondé sur le principe suivant : découper en deux un segment de taille m coûte m unités (il faut transporter le segment de taille m vers la scie). On cherche dans quel ordre pratiquer les découpes pour minimiser le coût total. Dans l’exemple précédent, il n’y a que deux manières de faire : soit couper d’abord la planche à l’abcisse 7, puis à l’abcisse 2, ce qui coûte 10 + 7 = 17 unités, soit procéder en sens inverse, ce qui coûte 10 + 8 = 18 unités. Ces deux manières peuvent s’exprimer par les parenthésages : (l1 (l2 l3 )) et ((l1 l2 )l3 ) ou par les arbres binaires : 17 b 7 b l3 66 CHAPITRE 6. PROGRAMMATION DYNAMIQUE. 18 b 2 l1 = 2 1 l2 = 5 l3 = 3 b8 l1 l2 l3 1 l1 = 2 2 l2 = 5 l3 = 3 Question 1 : Donner la récurrence et le pseudo-code du programme qui calculent la découpe optimale. Question 2 : Que devient le problème quand la planche est circulaire (un beignet plat), comme sur l’exemple ci-dessous ? l3 = 2 l2 = 1 l4 = 3 l1 = 2 La solution est page ?? Exercice 6.11 (♥ Chemin de coût minimal sur un échiquier avec trois déplacements type et des pénalités.). On considère un échiquier n × n (n > 1) et on s’intéresse au calcul du meilleur chemin allant de la case (n, 1) à la case (1, n) sachant que : 1. chaque case a un coût (entier positif, négatif ou nul) et que le coût d’un chemin est la somme des valeurs des cases qu’il emprunte, 2. un meilleur chemin est un chemin de valeur minimale, 3. on autorise les déplacements suivants : (i, j) → (i, j + 1) si 1 ≤ i, j + 1 ≤ n (i, j) → (i − 1, j − 1) si 1 ≤ i − 1, j − 1 ≤ n (i, j) → (i − 1, j + 1) si 1 ≤ i − 1, j + 1 ≤ n Exemple. j= i= 1 2 3 4 1 2 3 4 8 3 1 6 4 2 7 5 3 6.2. EXERCICES 67 Chemin : (4, 1), (4, 2), (4, 3), (3, 2), (3, 3), (2, 2), (2, 3), (1, 4) On veut calculer par programmation dynamique la valeur MC[n, 1] qui est le coût associé au (à un) meilleur chemin allant de la case (n, 1) à la case (1, n). Question 1 : Connaissant les valeurs associées aux cases de l’échiquier données dans le tableau C[i, j], établir la formule de récurrence pour calculer MC[i, j], le coût associé au (à un) meilleur chemin allant de la case (i, j à la case (1, n) avec 1 ≤ i, j ≤ n. Par hypothèse, on supposera que pour tout échiquier : C[1, n] = C[n, 1] = 0. Question 2 : Expliciter l’évolution du calcul effectué par l’algorithme mettant en œuvre ces formules en précisant comment on pourra mémoriser le meilleur chemin et le reconstituer. Question 3 : Donner les valeurs de MC dans le cas du tableau C ci-dessous : 1 2 3 4 j= i= 1 2 3 4 2 -6 5 0 -4 2 -2 10 1 -1 -3 2 0 3 3 7 La solution est page ?? 6.2.3 Dénombrements. Exercice 6.12 (♥ Nombre de partitions de l’ensemble à N éléments.). On note S(j, N) le nombre de partitions à j blocs de l’ensemble à N éléments (ou nombre de Stirling). Par exemple, [(1, 3), (2, 4)] et [(2), (1, 3, 4)] sont deux des sept partitions à deux blocs de l’ensemble à 4 éléments. Question 1 : Donner toutes les partitions à un, deux, trois et quatre blocs de l’ensemble à 4 éléments. Question 2 : Quelle est la relation de récurrence générale sur S(j, N) ? Quelles en sont les initialisations ? Question 3 : Donner le pseudo-code du programme qui calcule S(j, N) pour j = 1, N. La solution est page ?? Exercice 6.13 (♥ Le problème du franchissement de l’escalier.). On considère un escalier de m marches que l’on veut gravir exectement avec des sauts, au choix, de de a1 ou a2 . . . ou ap marches. Le problème est de trouver le nombre N(s, m) de façons différentes de gravir exactement m marches en s sauts. Par exemple, pour m = 12 marches et avec des sauts de a1 = 2 ou a2 = 3 ou a3 = 5, on peut gravir exactement les 12 marches par les suites de sauts suivants. (2, 2, 2, 2, 2, 2) ou (2, 3, 2, 3, 2) ou (3, 3, 3, 3) ou (2, 2, 3, 5) ou (3, 2, 5, 2), etc. Question 1 : Donner une façon de gravir exactement les 12 marches en trois sauts. Question 2 : Donner la formule de récurrence du calcul de N(s, m). N.B. : le mieux est de l’étudier sur un cas simple. Il faut particulièrement examiner les cas de succès et d’échec. Question 3 : Préciser l’évolution du calcul de N(s, m). Question 4 : Calculer les valeurs N(s, m) pour m = 12, a1 = 2, a2 = 3, a3 = 5. Question 5 : On remarque dans l’exemple précédent que certaines zones du tableau N valent 0. Expliquer pourquoi et proposer une amélioration de l’algorithme. La solution est page ??. 68 CHAPITRE 6. PROGRAMMATION DYNAMIQUE. Exercice 6.14 (♥ Le jeu avec deux piles de pièces.). On considère un jeu où on dispose de deux piles de pièces P et Q contenant respectivement p et q pièces (p, q > 1). Le but est d’atteindre une des deux situations (p = 0, q = 1) ou (p = 1, q = 0), sachant que l’on passe d’un état des piles au suivant de la façon suivante : on enlève deux pièces à P (resp. Q) et on en ajoute une à Q (resp. P). Exemple. (p = 4, q = 6) mène à : (p = 2, q = 7) ou (p = 5, q = 4). On veut calculer par programmation dynamique le nombre de façons différentes N[p, q] permettant d’atteindre l’un des des deux états terminaux (p = 0, q = 1) ou (p = 1, q = 0) à partir d’une situation où le tas P a p pièces et le tas Q en a q. Question 1 : Donner la formule de récurrence complète exprimant N[p, q]. Question 2 : Préciser l’évolution du calcul permettant d’obtenir N[p, q] (on ne demande pas l’algorithme). Question 3 : À l’aide d’un tableau calculer N[4, 2] en ne remplissant que les cases utiles. Question 4 : Que vaut N[3, 3] ? La solution est page ?? Exercice 6.15 (♥ Déplacements d’un cavalier aux échecs, sous certaines contraintes.). On s’intéresse aux parcours d’un cavalier sur un échiquier de taille quelconque : combien a-t’il de façons différentes de partir de la case (1, 1) pour se rendre à la case d’arrivée (n, m) ? On suppose que n > 4 et m > 3 On impose que le cavalier ne puisse se déplacer qu’en augmentant l’abcisse (le numéro de colonne), comme sur la figure. ❡ ❡ ❡ ❡ ❡ ❡ ❡ (n, m) ❡ ❡ ❡ ❡ ❡ ❡ (1, 1) ❡ Question 1 : Soit N(i, j) le nombre de parcours différents partant de la case (1, 1) et menant à la case (i, j). Essayer pour n = m = 5. Question 2 : Donner la relation de récurrence générale sur N(i, j). Vérifier pour n = m = 5 Question 3 : Montrer que l’initialisation de la relation de récurrence se fait sur les colonnes 1 et 2, puis, pour chaque colonne, sur les lignes 1 et 2 et sur les lignes m − 1 et m − 2. Question 4 : Donner le pseudo-code de l’algorithme. La solution est page ?? 6.2.4 Graphes : plus courts chemins et autres. Le nom de ces algorithmes est le même que dans [FRO90]. Il y a parfois des légères variantes dans d’autres ouvrages. 6.2. EXERCICES Exercice 6.16 (♥ 69 Algorithme de Floyd.). Présentation. L’algorithme de Floyd recherche le plus court chemin dans un graphe entre tous les couples de sommets. Ceux-ci sont ordonnés de manière arbitraire. La récurrence porte sur le rang des sommets qui se trouvent sur le chemin le plus court (non compris les sommets extrémités). Définitions. Un graphe G = (N, V) est composé d’un ensemble N fini de n nœuds ou sommets et d’un ensemble V ⊂ N × N d’arcs. Chaque arc peut donc être vu comme un lien d’un sommet vers un autre. Si le graphe est valué, chaque arc possède une valeur réelle strictement positive, appelée longueur. On suppose pour ce problème qu’il y a au plus un arc entre deux sommets. Un graphe valué peut se représenter par une matrice L[1 : n ; 1 : n], telle que L[i, j] est la longueur de l’arc entre le sommet i et le sommet j. S’il n’y a pas d’arc entre i et j, on donne la valeur +∞ à L[i, j]. On suppose qu’aucun sommet n’a d’arc qui se termine sur lui-même, ce qui permet d’écrire L[i, i] = 0 et que, en général, L[i, j] 6= L[j, i]. On appelle chemin dans un graphe une suite d’arcs (n1 , n2 ), (n2 , n3 ) . . . , (ni , ni+1 ), (ni+1 , ni+2 ), . . . (np ) On l’écrit plus simplement : n1 , n2 , . . . , ni , . . . np Un chemin est donc composé de p − 1 arcs et passe par p nœuds. Sa longueur est la somme des longueurs des arcs qui le composent. Etant donné un graphe, on cherche le plus court chemin entre tout couple de sommets, c’est à dire celui de longueur minimale. Application. 15 1 4 30 5 5 50 15 5 2 3 15 On note LCCk [i, j] le plus court chemin entre les sommets ni et nj , sous la contrainte que les sommets intermédiaires soient de rang inférieur ou égal à k. Par exemple, pour k = 2 les chemins ni , nj et ni , 2, 1, nj répondent à cette contrainte. 70 CHAPITRE 6. PROGRAMMATION DYNAMIQUE. Question 1 : Pourquoi le chemin ni , 2, 1, 1, 2, nj , qui répond à la même contrainte, n’a-t’il pas à être pris en compte ? Question 2 : Donner LCC1 [i, j] pour l’exemple et dans le cas général. Question 3 : Donner la formule de récurrence définissant LCCk+1 [i, j] en fonction de la matrice LCCk . Indication. Il y a deux cas exclusifs : soit le plus court chemin entre les sommets i et j passe par le sommet intermédiaire de rang k + 1, soit il n’y passe pas. Question 4 : Quand arrêter le calcul ? Question 5 : Donner un schéma de programme pour l’algorithme de Floyd. Question 6 : Quel est son ordre de grandeur de complexité (l’opération élémentaire est la comparaison). Question 7 : L’appliquer à l’exemple : calculer LCCk pour k croissant. Question 8 : Que devient cet algorithme s’il peut y avoir plusieurs arcs entre deux sommets ? La solution est page ?? Exercice 6.17 (♥ Plus court chemins : l’algorithme de Bellman-Ford.). Cet algorithme recherche le plus court chemin dans un graphe entre un sommet donné et tous les sommets. La récurrence porte sur le nombre d’arcs du chemin le plus court. Les définitions sont exactement les mêmes que celles de l’exercice 6.16, à part la suivante : on note ici LCCk [i, j] la longueur du plus court chemin entre les sommets i et j, comportant au plus k arcs. Question 1 : Donner la formule de récurrence définissant LCCk+1 [i, j] en fonction de la matrice LCCk . Question 2 : Question 3 : Commentaire. Il existe un algorithme plus rapide que celui-ci pour calculer le plus court chemin dans un graphe entre un sommet donné et tous les sommets. Il s’agit de l’algorithme glouton de Dijkstra (voir l’exercice ??). Cependant, celui qui est présenté ici (algorithme de Bellman-Ford) permet de traiter des graphes ayant des arcs de longueur négative, à condition qu’ils soient sans boucle de longueur négative (ce que cet algorithme permet aussi de détecter). La solution est page ?? Chapitre 7 Algorithmes gloutons Rappels Les algorithmes gloutons ou voraces (greedy) ont la particularité est de ne jamais remettre en cause les choix qu’ils effectuent. La succession des choix ne mène pas forcément à une bonne solution ou à une solution optimale. Il existe cependant des algorithmes gloutons qui trouvent toujours la solution cherchée, qu’on appelle exacts. Il faut prouver ou infirmer qu’un algorithme glouton est exact pour chaque problème traité. L’avantage des algorithmes gloutons est leur faible complexité. Dans certains problèmes d’optimisation, il est parfois préférable d’obtenir une solution approchée par un algorithme glouton qu’une solution optimale par un algorithme trop coûteux. Exercices. ✍✍ Exercice 7.1 ( Choix d’activités.). Le problème consiste à répartir une ressource parmi plusieurs activités concurrentes. Soit un ensemble S = {1, ..., n} de n activités concurrentes souhaitant utiliser une même ressource laquelle ne peut supporter qu’une activité à la fois. Chaque activité a un horaire de début di et un horaire de fin fi avec di ≤ fi . Si elle est choisie l’activité i a lieu pendant l’intervalle de temps [di , fi [. Les activités i et j sont compatibles lorsque di ≥ fj ou dj ≥ fi Le problème consiste à choisir le plus grand nombre d’activités compatibles entre elles. Un algorithme glouton résolvant ce problème est indiqué par le pseudo-code suivant. On suppose les activités triées de manière à ce que f1 ≤ f2 ≤ ... ≤ fn . Si tel n’est pas le cas cette liste de n activités peut être triée en O(n.log(n)). procedure Choix − activites − glouton debut A←1 j←1 pour i ← 2 jqa n faire si di ≥ fj alors A ← A ∪ {i} j←i fait 71 72 CHAPITRE 7. ALGORITHMES GLOUTONS fin L’algorithme opère comme suit. L’ensemble A collecte les activités sélectionnées. La variable j indique l’ajout le plus récent à A. D’après le tri initial des activités, fj = max{fk | k ∈ A}. L’activité 1 est la première sélectionnée, puis l’algorithme ajoute systématiquement à A, la première activité i compatible avec celles déjà présentes dans A. Remarque : Choix − activites − glouton est de complexité linéaire. Question. Montrer que Choix − activites − glouton donne les solutions optimales pour le problème du choix d’activités. D’après [PIN] La solution est page ?? ✍✍ Plus court chemin dans un graphe.). Soit un graphe G = (V, E) et un Exercice 7.2 ( nœud particulier s. La longueur (strictement positive) de l’arc entre les nœuds u et v est notée C[u, v]. Elle est mise à une valeur arbitrairement grande s’il n’y a pas d’arc entre u et v. Le problème est de trouver la longueur des plus courts chemins entre s et tous les nœuds de G de manière gloutonne. L’algorithme1 maintient un ensemble S pour lesquels la longueur du plus court chemin D(u) entre s et u (avec u ∈ S) est déjà connue. Il tient aussi à jour pour tous les nœuds un tableau D dont la valeur D[u] donne la longueur du plus court chemin entre s et u dont tous les nœuds (sauf éventuellement u si u 6∈ S) sont dans S. Si u ∈ S, la valeur D[u] est définitive. – Initialement, S = {s} et D[s] = C[s, s]. – Chaque étape consiste à choisir le nœud w de V − S qui minimise la quantité D[w]. D est réactualisé pour tous les nœuds v ∈ V − S par Min D[w] + C[w, v]) D[v] et w est ajouté à S. – La procédure se termine quand S est vide. 1: Procedure Dijkstra 2: début 3: /% Initialisation. %/ 4: S ← {s} 5: pour v ∈ V faire 6: D[v] ← C[s, v] 7: fin pour 8: /% Progression. %/ 9: tant que S 6= ∅∅ faire Choisir le w ∈ V − S qui minimise D[w] pour v ∈ V − S faire 12: si D[w] + C[w, v] < D[v] alors 13: D[v] ← D[w] + C[w, v] 14: fin si 15: fin pour 16: fin tant que 17: fin 10: 11: Question 1 : Montrer que cet algorithme est correct. Pour cela, montrer d’abord qu’à toute étape, pour tout nœud v ∈ S, avec v 6= s, D[v] contient la longueur du plus court chemin entre s et v dans G, et que cette valeur n’a plus à être remise en question. Question 2 : Appliquer cet algorithme sur le graphe suivant (la longueur de chaque arc est indiquée près de son point départ, le nœud de départ est A) : 1 Proposé et démontré par E. Dijkstra en 1959 73 7 6 A 10 5 6 3 E 30 4 65 4 B 50 44 70 2 23 1 C 20 D 0 1 2 3 4 5 6 7 La solution est page ?? ✍✍ Carrés magiques d’ordre impair.). Exercice 7.3 ( Remplir un carré magique d’ordre n impair. Les nombres 1 à n2 sont utilisés une fois et une seule. La somme des lignes, des colonnes et des diagonales vaut la même valeur. Application : n = 5. Il existe au total 275 305 224 carrés magiques d’ordre 5, on n’en demande qu’un ! La solution est page ?? ✍✍ Exercice 7.4 ( Fusion de fichiers.). Cet enoncé semble avoir une solution triviale. Il vient de Heuristique, de J. Pearl, traduction française chez Cépaduès. Exercice 1.3, page 36. Où est l’erreur ? Dans la traduction ? Dans l’original ? Dans ma compréhension ? A vérifier. Deux fichiers triés contenant n et m enregistrements peuvent être fusionnés en un seul fichier trié en n + m opérations. Donner une statégie gloutonne de fusion de p fichiers. Exemple : 5 fichiers de taille 20, 30, 10, 5, 25. Si on fusionne le premier avec le second, on a 50 opérations. Puis le résultat avec le troisième : au total 60. Puis le résultat avec le quatrième : 65. Le résultat avec le cinquième : au total 90. Avec cet énoncé, l’ordre n’a pas d’importance. Changer n + m en 2n + m ? ✍✍ Code de Huffman.). ✍✍ Arbres binaires de recherche.). Exercice 7.5 ( Exercice 7.6 ( Reprendre l’exercice 6.7, page 63 et définir un algorithme glouton. Donner un exemple pour montrer qu’il n’est pas exact. La solution est page ?? ✍✍ Exercice 7.7 ( Cherchez la star.). voir le poly de l’ENS, exo 1.7.2 74 CHAPITRE 7. ALGORITHMES GLOUTONS Chapitre 8 Mélanges 8.1 Un problème d’optimisation particulier : le "sac à dos" 8.1.1 Trois problèmes légèrement différents. Le sac à dos 0/1 Le problème du sac à dos (« KP » pour Knapsack problem) dans sa version entière (aussi appelée version 0/1) s’énonce ainsi. Il s’agit de déterminer le vecteur X = (xi )i=1...n ∈ {0, 1}n qui maximise l’expression n X ui .xi i=1 sous la contrainte n X i=1 wi .xi ≤ W pour des valeurs wi , ui et W données. wi et W sont des nombres entiers, ui est réel positif. Un exemple de problème de sac à dos 0/1 est celui, justement, d’un sac à dos que l’on souhaite remplir d’objets numérotés de 1 à n. L’objet numéro i a un certain prix ui et pèse le poids wi . On cherche à charger le sac à dos par le sous-ensemble d’objets de plus grande valeur, dont le poids total n’excède pas W. Par exemple, prenons W = 5 et : i ui wi 1 2 3 6 10 12 1 2 3 La solution optimale est Xopt = {0, 1, 1} pour une valeur totale de 22. Le sac à dos continu Le problème KP a aussi une version continue. Elle diffère du problème précédent par le fait que les objets sont supposés « divisibles » et que les valeurs wi sont réelles positives. C’est à dire que l’on veut trouver le vecteur X = (xi )i=1...n ∈ [0, 1]n qui maximise l’expression n X ui .xi i=1 75 76 CHAPITRE 8. MÉLANGES sous la contrainte n X i=1 wi .xi ≤ W pour des valeurs réelles positives wi , ui et W données. Un exemple de problème de sac à dos continu est celui de l’édition d’un livre : le brouillon comporte 5 chapitres, chacun d’un certain nombre de pages et d’un certain intérêt. La somme totale des pages du brouillon est trop grande pour l’éditeur. Quelle nombre de pages doit-on prendre dans chaque chapitre pour maximiser l’intérêt total du livre ? Par exemple : Chapitres 1 2 3 4 5 Pages 120 150 200 150 140 Intérêt 5 5 4 8 3 En enlevant tout le chapitre 5 et 20 pages du chapitre 3, on obtient un ouvrage de 600 pages (le maximum demandé par l’éditeur) dont l’intérêt total vaut : 5+5+4 180 0 +8+3 = 21.6 200 140 On a dans cet exemple : n = 5, W = 600 X = (120, 150, 200, 150, 140) w1 = 1., w2 = 1., w3 = 0.9, w4 = 1., w5 = 10. u1 = 5., u2 = 5., u3 = 4., u4 = 8., u5 = 3. Un autre exemple est celui du chargement d’un sac à dos non pas dans un super-marché (où les denrées sont pré-emballées par boîtes de 1kg, 2kg, etc.), mais chez un épicier où les produits sont en vrac (mais cependant en quantité limitée au poids ui ). xi , qui vaut entre 0 et 1, indique la proportion de la quantité totale du produit que l’on prend (dans le cas binaire, on prend tout ou rien d’un produit). Une version intermédiaire Enfin, il existe une version intermédiaire dite sac à dos binaire (ou entier) à contraintes fractionnaires qui s’énonce comme le sac à dos binaire, sauf que l’on accepte que les valeurs de poids wi et W soient réelles, alors que les xi doivent toujours valoir 0 ou 1 (on prend tout l’objet ou on ne le prend pas, mais son poids n’est pas forcément entier). Résumé En résumé, la signification et les domaines d’appartenance des variables sont les suivants : n Nombre d’objets W Poids maximal dans le sac à dos wi Poids d’un objet sac à dos 0/1 sac à dos intermédiaire sac à dos continu W N R+ R+ ui Valeur d’un objet wi N R+ R+ ui R+ R+ R+ xi {0, 1} {0, 1} [0, 1] xi Proportion de l’objet mise dans le sac à dos 8.1. UN PROBLÈME D’OPTIMISATION PARTICULIER : LE "SAC À DOS" 8.1.2 Trois solutions extrêmement différentes Les trois problèmes de sac à dos peuvent évidemment se résoudre brutalement par essais successifs, en version récursive comme au chapitre 4, ou en version itérative par la technique PSEP (pour Programmation par Séparation et Evaluation Progressive, ou branch and bound). Mais il y a beaucoup mieux à faire... dans deux cas sur les trois. Le sac à dos continu La version continue a en effet une solution gloutonne exacte. Voir par exemple [JOH04], pages 313 et suivantes pour la démonstration. Elle demande de ranger les chapitres par utilité relative ui /xi décroissante : dans notre exemple, si ui est l’utilité du chapitre i et xi son nombre de pages, l’utilité relative du chapitre i est ui /xi . Par exemple, celle du chapitre 4 est : 8/150 = 0.0533. Dans l’ordre décroissant d’utilité relative, les chapitres sont dans l’ordre : 4, 1, 2, 5, 3. On prend alors le maximum de pages dans cet ordre, soit 150 du chapitre 4, 120 du chapitre 1, 150 du chapitre 2, 180 du chapitre 3 et 0 du chapitre 5, pour obtenir la solution déjà citée ci-dessus. Elle est optimale. Le sac à dos 0/1 Pour la version 0/1, cet algorithme glouton ne fournit pas toujours l’optimum. Pour preuve, dans l’exemple précédent, si le nombre de pages total admis par l’éditeur est 620, elle conduit à prendre totalement les chapitres 1, 2, 4 et 5, pour un intérêt total de 21. Or, prendre les chapitres 1, 2, 3, 4 donnerait aussi 620 pages, mais avec un intérêt total de 22. On peut démontrer de manière générale que le problème du sac à dos en version 0/1 n’a pas de solution gloutonne toujours exacte. C’est la même chose dans l’autre exemple donné plus haut pour le sac à dos 0/1, avec W = 5 et : i ui wi 1 2 3 6 10 12 1 2 3 L’algorithme glouton va donner Xglouton = {1, 1, 0} (valeur totale : 16), alors que la solution optimale est Xopt = {0, 1, 1} (valeur totale : 22). En revanche, la version 0/1 peut se résoudre par programmation dynamique. En effet1 , si on définit B(w, i) comme la valeur optimale du sac à dos de capacité w en ne prenant en compte que les i premiers articles numérotés, on trouve que : ∀w, ∀i ≤ n B(w, i) = max i X (xj )j=1...n ∈{0,1}i j=1 i P xj .wj ≤W xj uj j=1 Pi−1 max (xi ui + max j=1 xj. uj ) si w − wi ≥ 0 xi ∈{0,1} (xj )j=1...i−1 ∈{0,1}i−1 i−1 P xj .wj ≤w−xi wi j=1 Pi−1 = si w − wi < 0 max j=1 xj. uj i−1 (xj )j=1...i−1 ∈{0,1} i−1 P xj .wj ≤w j=1 = max [xi ui + B(w − xi wi , i − 1)] si w − wi ≥ 0 xi ∈{0,1} B(w, i − 1) si w − wi < 0 La condition initiale étant ∀w ≤ W, B(w, 0) = 0 et le problème à résoudre B(W, n). 1 Cette partie est reprise de la page web de E. Rouault, http ://eleves.ensmp.fr/P00/00rouaul/ 77 78 CHAPITRE 8. MÉLANGES Il est à remarquer que la numérotation choisie pour les objets n’a pas d’importance, car la "temporalité" induite par rapport à une programmation dynamique classique est ici purement factice : Pi en réordonnant les termes composant la somme j=1 xj uj , on serait arrivé à une autre expression strictement équivalente. Cette équivalence s’entend au sens où le maximum sera le même, mais le vecteur conduisant à ce maximum n’est pas unique. Avec des mots, l’équation de récurrence se traduit ainsi : – Soit le ième objet est plus lourd que la capacité du sac, auquel cas, l’optimum est atteint en ne considérant que les (i − 1) premiers objets – Soit on peut le prendre et il faut donc prendre une décision – Si on le prend, le bénéfice vaut la valeur de cet objet plus le bénéfice optimum en ne considérant que les (i − 1) premiers objets avec une capacité diminuée de la masse de l’objet – Si on ne le prend pas, le bénéfice vaut le bénéfice optimum en ne considérant que les (i − 1) premiers objets avec une capacité inchangée On prend alors le maximum de ces 2 valeurs. La version intermédiaire Quant au problème du sac à dos binaire à contraintes fractionnaires, il n’a pas de solution rapide : on ne peut trouver l’optimum que par essais successifs, en temps au pire d’ordre exponentiel en fonction du nombre n (il faut essayer au pire les 2n vecteurs binaires). 8.2 Exercices. ✍ ♥ DpR contre Programmation dynamique.). Exercice 8.1 ( On reprend le problème du produit chaîné de matrices (page ??). On rappelle qu’il s’agit de réaliser la multiplication de n matrices rectangulaires M1 × M2 . . . × Mi × . . . Mn en un nombre minimum de multiplications élémentaires, sachant que le produit de matrices est associatif et que le produit M×M′ des deux matrices M (de dimensions [p, q]) et M′ (de dimensions [q, r]) prend pqr multiplications élémentaires. Pour n matrices, les dimensions sont rangées dans le tableau D de taille n + 1, dont les indices vont de 0 à n. Par exemple, pour les deux matrices M et M′ , on a : D(0) = p, D(1) = q, D(2) = r, ou plus simplement : D = (p, q, r) Question 1 On propose le schéma d’algorithme suivant, du type « diviser pour régner », pour n ≥ 3. On suppose (ce qui est sans importance) que les valeurs dans D sont toutes différentes. – Choisir i tel que D(i) soit le minimum dans {D(1), . . . , D(n − 1)}. – Diviser en deux sous-problèmes : effectuer le produit chaîné des matrices M1 × . . . × Mi et effectuer le produit chaîné des matrices Mi+1 × . . . × Mn – Continuer récursivement Question 1 : Ecrire cet algorithme en pseudo-code. Question 2 : Détailler son fonctionnement pour D = (8, 3, 2, 19, 18, 7). Question 3 : Combien d’opérations élémentaires effectue-t’il ? Question 2 Un autre algorithme « diviser pour régner » est le suivant : on choisit i tel que D(i) soit le maximum dans {D(1), . . . , D(n − 1)} et on divise pareillement en M1 × . . . × Mi et Mi+1 × . . . × Mn . Répondre aux deux dernières questions précédentes. 8.3. TRAVAUX PRATIQUES. 79 Question 3 Répondre aux deux mêmes questions en prenant l’algorithme de programmation dynamique du cours. Quelles conclusions générales peut-on tirer de l’application des trois algorithmes à cet exemple ? Question 4 Quels sont les ordres de complexité minimal et maximal des algorithmes « diviser pour régner » en fonction du nombre de multiplications élémentaires ? Quel est l’ordre de complexité exact de l’algorithme de programmation dynamique ? Anonyme 8.3 Travaux Pratiques. Exercice 8.2 (TP Le petit commerçant). On s’intéresse aux différents algorithmes que peut utiliser un commerçant pour rendre la monnaie avec des pièces. Son problème est d’arriver exactement à une somme N donnée en choisissant dans sa caisse un multi-ensemble de pièces dont chacune possède une valeur fixée. Par exemple, dans le système numéraire français, si le client effectue un achat de 8€10 et donne 10€, le problème consiste pour le commerçant à composer un multi-ensemble de pièces qui totalise 1€90. Il y a un grand nombre de solutions, parmi lesquelles : – 1 pièce de 1€, 1 pièce de 50c, 2 pièces de 20c – 2 pièces de 50c, 4 pièces de 20c, 2 pièces de 5c – 19 pièces de 10c – ... Pour étudier le problème de manière générale, on appelle C l’ensemble des pièces de monnaie (il y en a n différentes) et on va supposer que le commerçant dispose d’un nombre illimité de chacune d’entre elles. On note C = {c1 , . . . , cn }. La pièce ci a pour valeur di . En France, en ne considérant que les pièces et pas les billets, on a un ensemble C de taille 8, avec les valeurs : d1 = 2€, d2 = 1€, d3 = 50c, d4 = 20c, d5 = 10c, d6 = 5c, d7 = 2c, d8 = 1c Ou, en centimes : d1 = 200, d2 = 100, d3 = 50, d4 = 20, d5 = 10, d6 = 5, d7 = 2, d8 = 1 Pour reprendre l’exemple précédent, la première solution peut se noter par le multi-ensemble : {c2 , c3 , c4 , c4 } ou de manière équivalente par un vecteur de dimension n indiquant combien de pièces de chaque type ont été prises pour la solution : (0, 1, 1, 2, 0, 0, 0, 0) Pour achever de définir le problème, on va supposer que le commerçant cherche à redonner le moins de pièces possibles. On peut donc l’énoncer comme suit : Le problème "rendre la monnaie de manière optimale" (RLMMO) On se donne un ensemble C. A chaque élément ci de C est associée une valeur di , un nombre entier strictement positif. Soit N un nombre entier strictement positif. Trouver un multi-ensemble S composé d’éléments de C tel que : – la somme des valeurs des éléments de S vaille exactement N, – le nombre des éléments de S soit minimum. S’il n’existe aucun multi-ensemble répondant au premier des deux critères ci-dessus, le problème est déclaré insoluble. 80 CHAPITRE 8. MÉLANGES 1. Un algorithme très rapide, mais pas toujours exact (glouton) La méthode employée en général par un commerçant peut se décrire ainsi : utiliser les pièces par valeur décroissante, en prenant le plus possible de chacune d’elle. C’est cet algorithme "glouton" qui, dans l’exemple introductif, produit la première solution {c2 , c3 , c4 , c4 }. Question 1 : Formaliser cet algorithme. Le programmer et le tester. Question 2 : Montrer que cet algorithme ne résoud pas le problème RLMMO quand C = {c1 , c2 , c3 }, avec d1 = 6, d2 = 4, d3 = 1 et N = 8. Trouver un autre couple (C, N) non trivialement déduit de celui-ci pour lequel cet algorithme ne marche pas non plus. N.B. : On peut montrer que cet algorithme résout le problème RLMMO seulement quand C possède certaines propriétés. En particulier, c’est bien le cas pour le système de pièces français ou pour des systèmes du type (1, 2, 4, 8, ...). On ne s’intéresse pas ici à ces propriétés. 2. Un algorithme lent, mais toujours exact Question 3 : Utiliser la méthode des Essais Successifs pour écrire un algorithme qui résolve le problème pour toutes les valeurs de C et de N. On donnera comme résultat soit un vecteur de dimension n indiquant combien de pièces de chaque type ont été prises pour la solution optimale, soit l’affirmation que le problème est insoluble. On remarquera que les pièces n’ont pas besoin d’être rangées par ordre décroissant. Y-a t’il une façon de les ranger qui élague efficacement ? Faire des tests. Question 4 : En donner un ordre de complexité maximal en nombre d’appels récursifs. 3. Un algorithme rapide et toujours exact Pour simplifier, on cherche ici simplement à trouver le nombre de pièces que comporte la solution optimale. Les pièces n’ont pas besoin d’être rangées par ordre décroissant. On suppose de plus que la première pièce vaut une unité : d1 = 1. On va construire un tableau T (i, j) dont chaque valeur donne le nombre minimal de pièces nécessaires pour payer la somme j en ne s’autorisant que le sous-ensemble des pièces {c1 , . . . , ci }. Si c’est impossible, T (i, j) prend une valeur arbitrairement grande. On cherche donc T (n, N). On remarque que T (i, 0) = 0 pour toute valeur de i de 0 à n. Question 5 : Montrer que, pour i ≥ 1 et j ≥ di : T (i − 1, j) T (i, j) = Min 1 + T (i, j − di ) Que devient cette relation si j < di ? Question 6 : En déduire un algorithme de programmation dynamique pour trouver le nombre de pièces que comporte la solution optimale. Quel est son ordre de complexité exact en nombre d’opérations Min ? Question 7 : Comment compléter l’algorithme pour savoir quelles pièces sont rendues ? Question 8 : Que se passe-t’il si d1 6= 1 ? 4. L’algorithme glouton est-il si inexact ? Question 9 : Choisir un système de pièces où l’algorithme glouton ne fonctionne pas toujours. Faire des tirages aléatoires (selon une loi à définir) de sommes à rendre, ou les examiner systématiquement par ordre croissant jusqu’à une certaine limite. Claculer un rapport moyen entre le nombre de pièces rendues par cet algorithme et le nombre de pièces optimal. Conclusion ? Anonyme Exercice 8.3 (TP L’élément majoritaire.). 8.3. TRAVAUX PRATIQUES. Enoncé du problème On considère un tableau T [1 : n] de nombres entiers positifs avec n = 2k (k > 0). T [1 : n] est dit majoritaire s’il existe un entier x tel que : (cardi|1 ≤ i ≤ n et T [i] = x) > n/2 ; x est appelé élément majoritaire de T et est unique. Un premier algorithme On part de l’algorithme ci-dessous : 1: procedure Majoritaire1 2: Début 3: i ← 0 ; n 4: tant que (nb ≤ n 2 et i ≤ 2 ) faire 5: i←i+1 6: nb ← 1 7: j←i+1 8: tant que j ≤ n faire 9: si T [j] = T [i] alors 10: nb ← nb + 1 11: j←j+1 12: fin si 13: fin tant que 14: fin tant que 15: si nb > n 2 alors 16: Ecrire("T [i] est élément majoritaire de T ") ; 17: sinon 18: Ecrire("T n’est pas majoritaire") ; 19: fin si 20: Fin Question 1 : Que fait cet algorithme ? Donner sa complexité temporelle exacte "au pire", en nombre de comparaisons (ne pas oublier celles qui sont "cachées" dans les instructions tant que). Un diviser pour régner efficace On considère la fonction nboc(x, i, t) qui délivre qui délivre le nombre d’occurrences de x dans le sous-tableau T [i, i + t − 1]. 1: procedure nboc(x, i, t) 2: Début 3: k ← 0 ; 4: pour j = i, i + t − 1 faire si T [j] = x alors k←k+1 7: fin si 8: fin pour 9: Résultat(k) 10: Fin 5: 6: On veut calculer pour T [1 : n] le doublet (X, NBX) tel que : – X = −1, NBX = 0 si T [1 : n] n’est pas majoritaire – X > 0, NBX > n 2 si X est majoritaire et apparaît NBX fois dans T . On applique une technique de type "diviser pour régner" travaillant sur deux moitiés de T [1 : n]. Le couple (X, NBX) est alors calculé à partir de (xg, nbxg) (issu de la moitié gauche de T ) et de (xd, nbxd) (issu de la moitié gauche de T ) en utilisant éventuellement la fonction nboc. 81 82 CHAPITRE 8. MÉLANGES Pae exemple : 2 2 2 1 1 3 1 2 3 1 1 1 1 2 1 1 2,1 2,1 2,1 1,1 1,1 3,1 1,1 2,1 3,1 1,1 1,1 1,1 1,1 2,1 1,1 1,1 2,2 -1,0 -1,0 -1,0 -1,0 1,2 -1,0 1,2 -1,0 1,3 1,3 2,3 1,6 -1,0 1,9 Question 2 : Ecrire un programme correspondant à la procédure récursive d’en-tête : procedure majoritaire(debut, taille, x, nbx) telle que l’appel : majoritaire(1, n, X, NBX) calcule le doublet (X, NBX) recherché. Question 3 : En donner la classe de complexité temporelle "au pire". Que dire par rapport à celle établie en 1 ? Question 4 : Donner le canevas d’un algorithme basé sur un tri efficace résolvant le problème du tableau majoritaire. Quelle en serait la complexité temporelle ? Qu’en conclure ? Un diviser pour régner encore plus efficace Afin d’abaisser encore la complexité, on envisage une méthode de type "diviser pour régner" fondée sur une procédure récursive n’utilisant plus la fonction nboc. Dans ce but, on veut calculer le doublet (X, NBX) tel que : – X = −1, NBX = n 2 si T [1 : n] n’est pas majoritaire n – X > 0, NBX > n 2 si nboc(X, 1, n) ≤ NBX et ∀y 6= X, nboc(y, 1, n) ≤ n − NBX ≤ 2 fois dans T . Autrement dit, dans ce second cas, on sait que tout y différent de x n’est pas majoritaire et que x est peut-être majoritaire. NBX représente le maximum possible du nombre d’occurrences de X. Exemple. 1 2 1 3 2 2 1 3 1 2 1 1 1 1 3 2 1,1 2,1 1,1 3,1 2,1 2,1 1,1 3,1 1,1 2,1 1,1 1,1 1,1 1,1 3,1 2,1 -1,1 -1,1 2,2 -1,1 -1,1 1,2 1,2 -1,1 -1,2 2,3 1,3 1,3 1,6 2,5 1,9 Toute valeur autre que 1 n’est pas majoritaire et 1 l’est peut-être (en fait ne l’est pas). Avec le tableau de la question 2, on obtiendrait aussi le couple (1, 9) et dans ce cas 1 est effectivement majoritaire. Avec le tableau T = 11212122, on obtiendrait (−1, 4) indiquant que T n’est pas majoritaire. On considère les deux sous-tableaux contigus T [i, i + t − 1], dans lequel on calcule (xg, nbg) et T [i + t, i + 2t − 1], dans lequel on calcule (xd, nbd). Question 5 : Etablir en les justifiant les règles de calcul de (x, nb) pour T [i, i + 2t − 1] à partir de (xg, nbg) et (xd, nbd), puis écrire le programme correspondant. Question 6 : Le doublet obtenu ne fournit pas forcément la réponse désirée. Quel traitement complémentaire proposer pour aboutir à la réponse finale ? Quelle est alors la complexité du traitement global ? 8.3. TRAVAUX PRATIQUES. 83 Question 7 : Le passage à un tableau de taille quelconque vous semble-t-il aisé ? Que dire de l’utilisation d’une technique de type "bourrage" avec des éléments "fantômes" ? N.B. : une solution pour un tableau de taille quelconque est donnée dans [KLE06], exercice 5.3, page 246. D’abord, une procédure de base appelée elimine sur un tableau de taille m quelconque. (a) Considérer les éléments deux par deux : T [1] avec T [2], T [3] avec T [4], etc. Si m est impair, T [m] reste seul. (b) Pour chaque paire telle que les deux éléménts n’ont pas la même valeur, supprimer les deux éléments. (c) Pour chaque paire dont les deux éléments ont la même valeur, en conserver un. (d) Conserver T [m] pour m impair. On remarque ensuite que s’il existe un élément majoritaire dans un tableau de taille m, alors le tableau après la procédure elimine aura aussi cet élément comme majoritaire. On applique successivement elimine sur T de taille n jusqu’à ce que le résultat ait une taille de 1. Si le tableau de départ a un élément majoritaire, c’est forcément cet élément-là qui est dans le tableau de taille 1. Il suffit de le compter dans le tableau T . Anonyme Exercice 8.4 (TP La triangulation d’un polygone convexe.). On se donne les n sommets d’un polygone convexe du plan2 et une mesure de la distance séparant chaque couple de sommets. Le problème consiste à sélectionner un ensemble de cordes (segments joignant deux sommets non adjacents) tel qu’il n’y ait aucune intersection de cordes et que le polygone entier soit divisé en triangles. La longueur totale (la somme des distances entre les extrémités de toutes les cordes choisies) doit être minimale. On appelle un tel ensemble de cordes une triangulation minimale. Exemple La figure suivante montre un heptagone et les coordonnées (x, y) de ses sommets. Les lignes en pointillé représentent une triangulation, qui n’est d’ailleurs pas minimale. Sa longueur totale est la somme des longueurs des cordes (s0 , s2 ), (s0 , s3 ), (s0 , s5 ) et (s3 , s5 ) soit : p p p p 82 + 162 + 152 + 162 + 222 + 22 + 72 + 142 = 77.56 s2 (8,26) s3 (15,26) s4 (27,21) s1 (0,20) s5 (22,12) s0 (0,10) s6 (10,0) 2 Un polygone du plan est convexe si et seulement si les n − 2 sommets restants sont du même côté de la droite construite sur deux sommets consécutifs, et ceci pour toute paire de sommets consécutifs. 84 CHAPITRE 8. MÉLANGES Nous supposerons par la suite que le contour du polygone étudié a n sommets classés par ordre de parcours rétrograde (contraire du sens trigonométrique) notés s0 , s1 , ..., sn−1 . Questions préliminaires : Question 1 : Combien peut-on tracer de cordes dans un polygone convexe à n sommets ? Question 2 : Montrer par récurrence que toutes les triangulations d’un polygone convexe à n sommets (avec n ≥ 3) ont le même nombre de cordes et le même nombre de triangles. N.B. : On ne demande pas le nombre de triangulations différentes d’un polygone. On cherche tout d’abord à résoudre le problème par une méthode de type essais successifs. Questions : Question 3 : On supposera que les cordes déjà tracées sont stockées dans un tableau. Ecrire une fonction validecorde(i, j) qui rend VRAI si la corde joignant les sommets si et sj n’a pas déjà été tracée et si elle ne coupe aucune corde déjà tracée. Question 4 : On considère un algorithme par essais successifs basé sur la stratégie suivante : à l’étape i, on trace l’une des cordes valides issues de si ou on ne trace rien. Montrer que cette stratégie est mauvaise pour deux raisons : elle calcule plusieurs fois certaines triangulations elle ne permet pas toujours d’obtenir toutes les triangulations. Question 5 : On veut pouvoir construire chaque triangulation une fois et une seule. – En supposant disponible le vecteur des cordes C tel que C[i] contient les numéros des sommets de départ et d’arrivée de la ieme corde parmi toutes celles possibles ainsi que sa longueur, proposer une stratégie par essais successifs. Ecrire le programme correspondant. – Donner l’ordre de grandeur de complexité de cet algorithme en nombre d’appels récursifs. – Proposer une (des) amélioration(s) pour déterminer qu’une branche de l’arbre des appels récursifs ne peut plus mener à une solution. Deuxième partie On envisage maintenant une solution de type programmation dynamique, dont la récurrence s’appuie sur les remarques suivantes : – Dans toute triangulation d’un polygone convexe ayant n sommets numérotés comme indiqué ci-dessus, il existe un triangle qui utilise le côté entre les sommets 1 et n. – Si on enlève le côté entre les sommets 1 et n, on se retrouve avec deux problèmes de même nature, mais de taille strictement inférieure. On peut maintenant construire une récurrence. On définit le sous-problème de taille t débutant au sommet si , noté (Ti , t) comme la triangulation minimale du polygone si , si+1 , ..., si+t−1 , formé par la suite des sommets du contour original débutant en si et continuant dans l’ordre rétrograde. La corde du polygone initial formant une arête de Ti , t est (si , si+t−1 ). Pour résoudre (Ti , t), nous considérons les trois options suivantes : – Nous pouvons choisir le sommet si+t−2 pour former un triangle avec les cordes (si , si+t−2 ) et (si , si+t−1 ) et le troisième côté (si+t−2 , si+t−1 ) puis résoudre le problème (Ti , t − 1). – Nous pouvons choisir le sommet si+1 pour former un triangle avec les cordes (si , si+t−1 ) et (si+1 , si+t−1 ) et le troisième côté (si , si+1 ) puis résoudre le problème (Ti+1 , t − 1). – Pour tout k entre 2 et t − 3, nous pouvons choisir le sommet si+k et former un triangle de côtés (si , si+k ), (si+k , si+t−1 ) et (si , si+t−1 ) puis résoudre les sous-problèmes (Ti , k+1) et (Ti+k , t−k). Comme la "résolution" de tout sous-problème de taille inférieure ou égale à deux ne demande aucune action, il est possible de résumer en disant que si l’on choisit un k entre 2 et t − 2, on est conduit à résoudre les sous-problèmes (Ti , k+1) et (Ti+k , t−k). La figure suivante illustre cette division en sous-problèmes. (Ti+k , t − k) 8.3. TRAVAUX PRATIQUES. • Si+t−1 85 (Ti, t) Si+k • (Ti , k + 1) • Si Il convient de préciser que cette stratégie est à la fois valide (on examine toute triangulation possible) et minimale (on examine chaque triangulation une seule fois). Questions : Question 6 : Etablir la formule de calcul d’une triangulation minimale d’un polygone convexe de n côtés sous forme d’une récurrence complète. Question 7 : Proposer un algorithme de type programmation dynamique découlant de la formule précédente. Ecrire le programme correspondant. Question 8 : Donner la complexité spatiale de l’algorithme, ainsi que sa complexité temporelle en terme de nombre de comparaisons (de valeurs de triangulations). Question 9 : On constate que, dans cette stratégie, deux sous-problèmes ont toujours un sommet commun. Expliquer en quoi cette façon de décomposer est intéressante du point de vue des triangulations considérées. Quelles modifications devraient être apportées à l’algorithme précédent si la décomposition se faisait en tirant deux cordes quelconques ? Compléments Question 10 : L’algorithme de programmation dynamique minimise la longueur totale tracée (la somme des distances entre les extrémités de toutes les cordes choisies). a) Montrer qu’il minimise en même temps la somme des périmètres des n − 2 triangles tracés dans le polygone. b) Soient a, b et c les longueurs des côtés d’un triangle. On considère la valeur r = |a−b|+|b−c|+|c−a|. Que vaut r pour un triangle équilatéral ? Pour un triangle plat ? On dira que r mesure "l’irrégularité" d’un triangle. c) Montrer que l’algorithme peut être facilement modifié pour trouver la triangulation d’un polygone minimisant la somme des irrégularités des triangles. Généraliser ce résultat à la minimisation de la somme de toute fonction positive calculée sur un triangle. d) Que devient l’algorithme si cette fonction est l’aire ? Question 11 : On a donné la définition suivante : Un polygone du plan est convexe si et seulement si les n − 2 sommets restants sont du même côté de la droite construite sur deux sommets consécutifs, et ceci pour toute paire de sommets consécutifs. En déduire un algorithme qui vérifie si un polygone défini par les coordonnées de ses sommets consécutifs est convexe. Anonyme 86 CHAPITRE 8. MÉLANGES Chapitre 99 Exercices en cours de rédaction. 8 mars 2007 Complexité Exercice 99.1 (♥ Recherche dans le noir). Vous êtes dans le noir face à un mur qui s’étend indéfiniment à gauche et à droite. Il y a une porte dans ce mur, mais vous ignorez où. Tout ce que pouvez faire est d’explorer le mur en marchant jusqu’à vous trouver devant la porte. Question 1 : Donnez un algorithme qui vous permet de trouver la porte en faisant un nombre de pas en O(n), où n est le nombre de pas qui vous sépare de la porte (évidemment, vous ne connaissez ni la valeur de n, ni si la porte est à gauche ou à droite). Question 2 : Quelle est la constante multiplicative entière la plus précise dans ce O(n) ? La solution est page ?? Exercice 99.2 (♥ Cherchez la star.). Dans un groupe de n personnes, une star est une personne que tout le monde connaît et qui ne connaît personne. Vous êtes extérieur au groupe et vous cherchez une star dans le groupe, s’il y en a une. La seule opération élémentaire qui vous est autorisée est de choisir une personne i du groupe et de lui demander si elle connaît la personne j du groupe. Les réponses sont obligatoires et sincères. Comme il y a n(n − 1)/2 paires de personnes, le problème est certainement résolu par n(n − 1) opérations élémentaires. Evidemment, vous cherchez à poser le moins de questions possibles. Question 1 : Montrez que s’il y a une star dans le groupe, elle est unique. Question 2 : L’opération élémentaire se note (i → j) et la réponse est VRAI ou FAUX. Montrez, en l’absence d’autre information, que quand vous posez la question (i → j) : (a) si la réponse est VRAI, alors j peut être la star, mais pas i ; (b) si la réponse est VRAI, alors i peut être la star, mais pas j. Question 3 : Décrire un algorithme en O(n) opérations élémentaires pour trouver la star ou pour prouver l’absence de star dans un groupe de n personnes. Indication : Dans une première passe, posez une question à chaque personne. A la fin, vous connaîtrez une personne qui peut être la star et qui est la seule à pouvoir être la star. Démontrez la justesse du calcul que vous faites. Dans une seconde passe, réinterrogez chaque personne, puis terminez par d’autres questions. Question 4 : Donnez le pseudo-code de cet algorithme. Question 5 : Donnez une borne supérieure exacte du nombre d’opérations élémentaires. La solution est page ?? 87 88 CHAPITRE 99. EXERCICES EN COURS DE RÉDACTION. Diviser pour régner Exercice 99.3 (♥ Blabla. Question 1 : Question 2 : Quicksort). La solution est page ?? Exercice 99.4 (♥ La valeur manquante). On dispose un tableau T de taille n contenant tous les entiers de {0, ..., n}, sauf un. On veut déterminer quel est l’entier absent de T . Question 1 : Donner un algorithme qui résout le problème en temps linéaire (l’opération élémentaire est l’addition). Question 2 : On prend une autre technique : on commence par trier le tableau T par valeur croissante. Montrer que le problème peut alors se résoudre par dichotomie. Quelle est alors la complexité (l’opération élémentaire est ici la comparaison) ? Question 3 : Peut-on améliorer le premier algorithme quand le tableau est trié ? Question 4 : Reprendre les questions quand le tableau contient tous les entiers de {0, ..., n + 1}, sauf deux. Anonyme Exercice 99.5 (♥ Les œufs par les fenêtres.). Quand on laisse tomber un œuf par la fenêtre d’un immeuble, il peut se casser ou non : cela dépend de la hauteur de la chute. On cherche à connaître la résistance des œufs, c’est à dire à partir de quel nombre f d’étages un œuf se casse si on le laisse tomber par la fenêtre. Il est entendu que tous les œufs sont identiques et qu’un œuf se casse toujours s’il tombe d’un étage de rang supérieur ou égal à f et jamais s’il tombe d’un étage de rang inférieur à f. Quand un œuf tombe sans se casser, on peut le ramasser et le réutiliser. S’il est cassé, on ne peut plus s’en servir. Etant donné un immeuble de n étages et un certain nombre k d’œufs, on cherche à trouver f. Si le dernier étage n’est pas assez haut pour briser cette sorte d’œufs, on écrira que f = n + 1. Une première technique Question 1 : On ne dispose que d’un œuf (k = 1). Donner un algorithme en O(n) pour trouver f. √ Question 2 : On suppose maintenant que k = 2. Donner un algorithme en O( n) pour trouver f. Donner son déroulement sur l’exemple n = 25, h = 23. √ Question 3 : On suppose maintenant que k = 3. Donner un algorithme en O( 3 n) pour trouver f. √ Question 4 : Donner un algorithme en O( k n) pour trouver f quand on possède k œufs. Prouver son exactitude et sa complexité par récurrence. Une seconde technique Réponse 5 : On suppose maintenant que k ≥ ⌈log2 (n)⌉. Décrire et donner le pseudo-code d’un algorithme qui calcule f en O(log2 (n)) lancers. Montrer que sa complexité exacte au pire est de ⌈log2 (n)⌉ lancers. Réponse 6 : Que peut-on faire si k = ⌈log2 (n)⌉ − 1 ? Décrire un algorithme dont la complexité exacte au pire est de (k − 1) + 2 lancers. n Réponse 7 : D’une manière générale, si k < ⌈log2 (n)⌉, proposer un algorithme en O(k + 2k−1 ) lancers. 89 Moralité Réponse 8 : Comparer les deux techniques Anonyme Exercice 99.6 (♥ Nombre d’inversions dans une permutation). Le but de cet exercice est de construire un algorithme rapide pour compter le nombre des inversions dans une permutation. Pour fixer les idées, on travaillera sur les permutations des n premiers nombres entiers, donc les nombres de 1 à n. Une permutation est rangée dans le tableau T [1 : n] et on dit que les nombres i et j, tels que 1 ≤ i < j ≤ n forment une inversion si T [i] > T [j]. Par exemple, pour n = 8, le nombre d’inversions de la permutation suivante vaut 13 : i T [i] 1 3 2 5 3 2 4 8 5 6 6 4 7 1 8 7 Ecrites comme la liste de deux termes (T [i] , T [j]), avec i < j et T [i] > T [j], les inversions sont les suivantes : (3,2) (6,4) (3,1) (6,1) (5,2) (4,1) (5,4) (8,6) (5,1) (8,4) (2,1) (8,1) (8,7) Question 1 : Ecrire un algorithme naïf qui donne le nombre d’inversions dans une permutation. Donner l’ordre de grandeur de complexité de cet algorithme (l’opération élémentaire est la comparaison). On va supposer maintenant que n = 2k , pour un certain k entier supérieur ou égal à 2. On peut partitionner, dans l’exemple précédent, les inversions en trois groupes : – Celles qui ont les deux termes dans la première moitié de T . Par exemple : (5, 2). – Celles qui ont les deux termes dans la seconde moitié de T . Par exemple : (4, 1). – Celles qui ont le premier terme dans la première moitié de T et le second dans la seconde moitié. Par exemple : (5, 4). Question 2 : Ecrire un algorithme Diviser pour Régner fondé sur la remarque précédente pour calculer récursivement le nombre d’inversions. Donner l’ordre de grandeur de complexité de cet algorithme. Supposons maintenant que chaque moitié de T soit triée par ordre croissant. T pourrait être par exemple : i T [i] 1 2 2 3 3 5 4 8 5 1 6 4 7 6 8 7 Montrer que dans ce cas, on arrive à compter les inversions en un temps linéaire. Question 3 : Déduire de la remarque précédente un algorithme Diviser pour Régner qui trie T et compte les inversions dans T avec un ordre de grandeur de complexité meilleur que le précédent. Question 4 : Faire tourner cet algorithme sur l’exemple présenté au début. Dans quel ordre les inversions sont-elles comptées ? Question 5 : On dit qu’on est en présence d’une inversion forte quand i < j et T [j] > 2T [j]. Donner un algorithme de même ordre de grandeur de complexité qui compte les inversions fortes dans une permutation. [KLE06], pages 221-225 Exercice 99.7 (♥ Le pic). On dispose d’un tableau de n entiers T tous différents, qui possède la propriété d’avoir un seul pic à la position p c’est à dire que les valeurs T [1] à T [p] sont croissantes, et celles de T [p] à T [n] sont décroissantes. Question 1 : La définition précédente vaut pour 1 < p < n. L’étendre à 1 ≤ p ≤ n. 90 CHAPITRE 99. EXERCICES EN COURS DE RÉDACTION. Question 2 : Donner un algorithme Diviser pour Régner qui trouve la position du pic. Quel est son ordre de grandeur de complexité (l’opération élémentaire est la comparaison). [KLE06], pages 242-244 Exercice 99.8 (♥ Minimum local dans un arbre). On dispose d’un arbre binaire complet avec n nœuds (on peut donc poser n = 2d − 1, avec d entier). A chaque chaque nœud est affectée une valeur réelle différente. Un nœud est un minimum local s’il est plus petit que son père et que tous ses fils. Question 1 : Montrer qu’il y a toujours un minimum local dans un tel arbre. Question 2 : Donner un algorithme qui trouve un minimum local avec une complexité temporelle en Θ(d). L’opération élémentaire est la comparaison des valeurs réelles affectées aux nœuds. [KLE06], pages 248 (N° 6 Exercice 99.9 (♥ Enveloppe convexe). Question 1 : Question 2 : Question 3 : Anonyme Exercice 99.10 (♥ Le meilleur intervalle). On dispose d’un tableau T [1 : n] de valeurs positives réelles. Il existe deux indices i etj, définissant l’intervalle [i, j], avec 1 ≤ i < j ≤ n, tels que la valeur T [j] − T [i] soit maximale. On cherche cette valeur maximale (la valeur du meilleur intervalle). Si le tableau est monotone décroissant, cette valeur est négative ou nulle : on impose alors qu’elle soit nulle. Par exemple, le tableau T comporte les valeurs quotidiennes de l’action de la société Machin le mois dernier. Vous vous demandez aujourd’hui quel aurait été votre gain optimal en achetant une action puis en la revendant au cours du mois dernier. Comme vous êtes un optimiste, vous considérez qu’une perte équivaut à un gain nul. Question 1 : Donner un algorithme naïf pour résoudre ce problème. Quel est son ordre de grandeur de complexité (l’opération élémentaire est la comparaison) ? Question 2 : Donner un algorithme Diviser pour Régner pour résoudre ce problème. On pourra dans un premier temps supposer n = 2d , avec d entier, puis on indiquera comment relâcher cette hypothèse. Quel est l’ordre de grandeur de complexité ? Commentaire. L’exercice 99.15, page 94, améliore encore ce résultat. Anonyme Exercice 99.11 (♥ Multiplication de polynômes). On a vu à l’exercice ?? une première manière d’utiliser la technique Diviser pour Régner pour multiplier deux polynômes. Celle-ci est en théorie encore plus efficace. Elle s’applique aux polynômes de la variable complexe, à coefficients complexes, donc en particulier aux polynômes classiques. Soit PN (x) = a0 + a1 x + . . . + ak xk . . . + aN−1 xN−1 un polynôme PN de degré inférieur ou égal à N − 1. Les coefficients ak et la variable x sont des nombres complexes. 2iπk Soit ΩN = (1, ω1 , . . . , ωk , . . . , ωN−1 ), avec ωk = e N , la suite des racines complexes N-ièmes de l’unité. On suppose que N est une puissance entière de 2. On note yk = P(ωk ) la valeur de PN pour ωk et YN = (y0 , . . . , yN−1 ) la suite des valeurs que prend PN sur ΩN . On admet pour le moment le résultat suivant : il est possible de reconstituer sans ambiguïté PN (c’est à dire de calculer les valeurs ak ) si l’on connaît seulement YN (on reviendra sur ce résultat à la question ??). Autrement dit, si deux polynômes de degré inférieur ou égal à N prennent les mêmes valeurs sur les racines N-ièmes de l’unité, ces deux polynômes sont identiques. 91 On s’intéressera d’abord à un algorithme de reconstruction de PN à partir de YN , puis à la complexité du calcul des valeurs YN avant d’en déduire un algorithme rapide de multiplication de polynômes. On reviendra pour finir sur le résultat admis en prémisse. Reconstruction de PN à partir de YN . e o e On note YN = (y0 , y2 , . . . , yN−2 ) et YN = (y1 , y3 , . . . , yN−1 ). On définit PN (x) comme le polynôme N e o (x) est le de degré inférieur ou égal à 2 − 1 que l’on peut reconstituer à partir de YN . De même, PN N o polynôme de degré inférieur ou égal à 2 − 1 que l’on peut reconstituer à partir de YN . N/2 N/2 2iπ e o (x) + 1−x2 PN (xe N ). Question 1 : Montrer que PN (x) = 1+x2 PN Indication : Il suffit de montrer que cette égalité est vraie pour toutes les racines N-ièmes de l’unité. Le mieux est de le faire en deux temps : pour les racines paires, puis impaires. Question 2 : En déduire que l’on peut reconstituer PN à partir de YN avec une complexité en O(NLog(N)). L’opération élémentaire est la multiplication de deux nombres complexes. Calcul de YN connaissant PN . On s’intéresse maintenant à la complexité du calcul de YN connaissant PN . Question 3 : Montrer qu’il existe deux polynômes R et S de degré inférieur ou égal à N/2 − 1 tels que que PN (x) = R(x2 ) + xS(x2 ). Question 4 : En déduire que l’on peut calculer YN à partir de PN avec une complexité en O(NLog(N)). Multiplication de deux polynômes Et maintenant, on multiplie deux polynômes ! Question 5 : Montrer que l’on peut multiplier deux polynômes P N (x) et Q N (x) de degré inférieur 2 2 ou égal à N − 1 par la séquence des opérations suivantes : 2 – Evaluer P N et Q N sur ΩN . 2 2 – En déduire l’évaluation YN sur ΩN du polynôme produit P N Q N . 2 2 – Reconstituer P N Q N à partir de YN . 2 2 Question 6 : En déduire un algorithme de multiplication de P N et Q N en O(NLog(N)). 2 2 Question 7 : Généraliser au cas où P et Q sont de degrés quelconques. Reconstitution d’un polynôme par des techniques moins efficaces. La formule de Lagrange : P(x) = N−1 X n=0 Y 0≤m≤N m6=n x − ωm ωn − ωm yn permet de démontrer facilement que l’on peut reconstituer PN à partir de YN . Mais elle n’est pas efficace du point de vue complexité. Une version itérative de la formule de Lagrange, due à Newton, permet d’accélérer le calcul. P(x) = Q(x) + yN−1 − Q(ωN−1 ) U(x) U(ωN−1 ) où Q est le polynôme défini par Q{ω0 , . . . , ωN−2 } = (y0 , . . . , yN−2 ) (c’est à dire qu’il prend les valeurs y0 à yN−2 sur les N − 1 premières racines de l’unité) et U(x) = (x − ω0 )(x − ω1 ) . . . (x − ωN−2 ). Question 8 : Quelle est la complexité de calcul de la multiplication de deux polynômes P N (x) et 2 Q N (x) de degré inférieur ou égal à N − 1 si on utilise la formule de Lagrange au lieu de la technique 2 2 vue ci-dessus ? Question 9 : Même question si on utilise la formule de Newton. La solution est page ?? Exercice 99.12 (♥ Le dessin du skyline ). 92 CHAPITRE 99. EXERCICES EN COURS DE RÉDACTION. 4 3 2 1 0 4 3 2 1 0 0 1 2 3 4 5 6 7 8 Figure 1 : Trois immeubles. 0 1 2 3 4 5 6 7 8 Figure 2 : Le skyline correspondant. On s’intéresse au dessin du skyline d’un centre-ville construit avec des immeubles rectangulaires, au bord de la mer. Le skyline est la ligne qui sépare le ciel des immeubles, quand on se place assez loin en mer et qu’on regarde en direction de la ville. Les deux premières figures montrent la projection à deux dimensions d’un centre-ville composé de trois immeubles et la seconde figure donne le skyline correspondant. On suppose que toutes les dimensions sont entières, et que les immeubles sont construits entre les coordonnées horizontales 0 et n. 1. Une première approche Un skyline est représenté dans cette première partie par un tableau S[1 : n], dont la composante i indique la hauteur du skyline entre les abcisses i − 1 et i. Pour l’exemple ci-dessus, le skyline se représente donc par : i S[i] 1 0 2 1 3 3 4 1 5 1 6 0 7 2 8 2 On choisit de représenter un immeuble par le skyline qu’il aurait s’il était tout seul. La représentation du troisième immeuble (le plus à droite) est donc : i I3 [i] 1 0 2 0 3 0 4 0 5 0 6 0 7 2 8 2 Un algorithme itératif de construction. On suppose connaître le skyline S1,m−1 d’un ensemble de m − 1 immeubles. On ajoute un nouvel immeuble Im , comme sur l’exemple ci-dessous. 4 3 2 1 0 4 3 2 1 0 0 1 2 3 4 5 6 7 8 Figure 3 : Ajout d’un immeuble. 0 1 2 3 4 5 6 7 8 Figure 4 : Le nouveau skyline. Question 1 : Donner un algorithme pour calculer S1,m à partir de S1,m−1 et de Im . Question 2 : Quelle est la complexité exacte en nombre de comparaisons du calcul itératif de S1,m , à partir de I1 , . . . , Im , en fonction de n et m ? 93 Un algorithme récursif. On suppose connaître le skyline S1,p des immeubles I1 à Ip d’une part, et le skyline Sp+1,m des immeubles Ip+1 à Im d’autre part. Question 3 : Donner un algorithme pour calculer le tableau S1,m à partir des tableaux S1,p et Sp+1,m . Question 4 : Quelle est sa complexité exacte en nombre de comparaisons ? Question 5 : Formuler le raisonnement par récurrence qui permet de construire S1,m . Donner un algorithme Diviser pour Régner utilisant une procédure récursive fondée sur ce raisonnement, que l’on formulera comme : SKY(p, q, S), avec 1 ≤ p ≤ q ≤ n. Cette procédure doit calculer le skyline correspondant aux seuls immeubles Ip à Iq et mettre le résultat dans S. Donner son appel depuis le programme principal. N.B. : Le mieux est d’utiliser trois tableaux auxiliaires de taille n : S1 , S2 et S. Question 6 : Quelle est son ordre de grandeur de complexité exact en nombre de comparaisons ? 2. Seconde partie Un skyline est représenté dans cette seconde partie par un tableau T , qui décrit de gauche à droite les coordonnées et les hauteurs caractéristiques. Pour l’exemple donné en Figure 1, le skyline se représente par : T = (1 , 1 , 2 , 3 , 3 , 1 , 5 , 0 , 6 , 2 , 8) Question 7 : Montrer que la taille d’un tableau T représentant le skyline de m immeubles est inférieure ou égale à 3m. Un algorithme itératif de construction. On suppose connaître le skyline T1,m−1 d’un ensemble de m − 1 immeubles. On ajoute un nouvel immeuble Im , représenté par un tableau de taille 3 noté Tm,m . Question 8 : Montrer que l’on peut calculer T1,m à partir de T1,m−1 et de Tm,m avec une complexité en O(m). N.B. On ne demande pas l’algorithme lui-même, mais une description informelle. On s’appuiera sur les exemples des Figures 3 et 4. T1,m−1 = (1 , 1 , 2 , 3 , 3 , 1 , 5 , 0 , 6 , 2 , 8) Tm,m = (4 , 4 , 7) T1,m peut se construire en trois étapes : interclasser les abcisses de T1,m−1 et de Tm,m pour obtenir le vecteur (1 , . , 2 , . , 3 , . , 4 , . , 5 , . , 6 , . , 7 , . , 8) Ensuite, remplir les valeurs des hauteurs : (1 , 1 , 2 , 3 , 3 , 1 , 4 , 4 , 5 , 4 , 6 , 4 , 7 , 2 , 8) Puis enlever la redondance (1 , 1 , 2 , 3 , 3 , 1 , 4 , 4 , 7 , 2 , 8) Question 9 : Quel est l’ordre maximal de complexité (en nombre de comparaisons) du calcul itératif de S1,m , à partir de I1 , . . . , Im , en fonction de m ? Un algorithme récursif. On suppose connaître le skyline T1,p des immeubles I1 à Ip d’une part, et le skyline Tp+1,m des immeubles Ip+1 à Im d’autre part. Question 10 : Montrer que l’on peut calculer le tableau T1,m à partir des tableaux T1,p et Tp+1,m avec une complexité en O(m).. 94 CHAPITRE 99. EXERCICES EN COURS DE RÉDACTION. Question 11 : Formuler le raisonnement par récurrence qui permet de construire T1,m . Donner un algorithme Diviser pour Régner utilisant une procédure récursive fondée sur ce raisonnement, que l’on formulera comme : SKY2(p, q, T ), avec 1 ≤ p ≤ q ≤ n. Cette procédure doit calculer le skyline correspondant aux seuls immeubles Ip à Iq et mettre le résultat dans T . Donner son appel depuis le programme principal. N.B. : Le mieux est d’utiliser trois tableaux auxiliaires de taille 3m : T1 , T2 et T . Question 12 : Quelle est son ordre de grandeur de complexité maximal en nombre de comparaisons ? La solution est page ?? Essais successifs. Programmation dynamique. Graphes Exercice 99.13 (♥ Algorithme de Dantzig.). Question 1 : Question 2 : Question 3 : Anonyme Exercice 99.14 (♥ Algorithme de Warshall.). Question 1 : Question 2 : Question 3 : Anonyme Recherche d’optimum. Exercice 99.15 (♥ Le meilleur intervalle (le retour)). Rappellons le problème, déjà présenté dans l’exercice 99.10, page 90. On dispose d’un tableau T [1 : n] de valeurs positives réelles. Il existe deux indices i etj, définissant l’intervalle [i, j], avec 1 ≤ i < j ≤ n, tels que la valeur T [j] − T [i] soit positive et maximale. On cherche cette valeur maximale (la valeur du meilleur intervalle). Si le tableau est monotone décroissant, cette valeur est négative ou nulle : on impose alors qu’elle soit nulle. Question 1 : Donner un algorithme en Θ(n) pour calculer la valeur du meilleur intervalle. Anonyme Exercice 99.16 (♥ Ensemble indépendant de poids maximal dans un arbre). Enoncé. Soit T un arbre dont chaque sommet (feuilles comprises) possède un poids positif. On note W(v) le poids du sommet v. Le poids W(S) d’un sous-ensemble S de sommets de T est par définition la somme des poids des sommets qui le composent X W(S) = W(u) u∈S On dit que deux sommets u et v sonts adjacents quand u est le père de v ou quand v est le père de u. Un ensemble de deux sommets ou plus est dit indépendant s’il ne contient pas de couple de 95 2 4 6 1 15 8 3 9 5 12 15 9 3 1 7 10 6 15 11 8 8 12 7 10 13 5 Fig. 99.1 – Un exemple d’arbre. Le numéro du sommet est encadré, le poids est écrit en gras sur le côté. L’ensemble indépendant {1, 4, 5, 10, 11, 12, 13} a pour poids 56. L’ensemble indépendant de poids maximal est {2, 3, 6, 7, 8, 12, 13}, dont le poids vaut 60. sommets adjacents. Le problème est de trouver S ∗ , un sous-ensemble indépendant de poids maximal des sommets de T ( la figure 99.1 donne un petit exemple de ce problème). Soit u un sommet de T . On suppose qu’il a des fils v1 , . . . , vc et des petits-fils w1 , . . . , wg . On note ∗ Su un ensemble indépendant de poids maximal pour le sous-arbre de racine u. Question 1 : Montrer que si u 6∈ S ∗ , alors Su∗ = Sv∗1 ∪. . .∪Sv∗c , où Sv∗i est un ensemble indépendant de poids maximal pour l’arbre de racine vi . ∗ ∗ ∗ Question 2 : Montrer que si u ∈ S ∗ , alors Su∗ = {u} ∪ Sw ∪ . . . ∪ Sw , où Sw est un ensemble g 1 i indépendant de poids maximal pour l’arbre de racine wi . Question 3 : En déduire une relation de récurrence et un algorithme de programmation dynamique pour calculer S ∗ et son poids. Quelle est sa complexité ? Question 4 : L’appliquer à l’arbre exemple. Exercice 99.17 (♥ Le sous-ensemble de somme nulle). Soit S = {x1 , x2 , . . . , xn } un ensemble de n entiers relatifs. On cherche s’il existe un sous-ensemble non vide de S de somme nulle. On note P la somme des éléments strictement positifs et N la somme des éléments strictement négatifs de S. On définit la fonction booléenne Q(i, s) comme la valeur de vérité de l’affirmation : « il existe un sous-ensemble de {x1 , x2 . . . xi } dont la somme des éléments vaut s ». On cherche donc à savoir si Q(n, 0) est VRAI ou FAUX. Question 1 : Montrer que Q(i, s) est FAUX quand s < N ou quand s > P. Question 2 : Démontrer une relation de récurrence qui relie Q(i, s), Q(i − 1, s) et Q(i − 1, s − xi ) pour i > 1. Quelle est son initialisation pour i = 1 ? Question 3 : En déduire un algorithme de programmation dynamique pour calculer Q(n, 0). Comment cet algorithme peut-il aussi calculer le (ou les) sous-ensemble(s) qui rendent cette valeur VRAI ? L’appliquer à l’exemple suivant : S = {8, −7, −2, −3, 5} Question 4 : Etudier sa complexité en fonction de n. 96 CHAPITRE 99. EXERCICES EN COURS DE RÉDACTION. Question 5 : Que devient ce problème si on cherche le sous-ensemble dont la somme est négative ou nulle, et la plus proche possible de 0 ? Remarque : En modifiant légèrement l’énoncé de cet exercice, on retrouve les exercices 4.8 et 4.9 du chapitre 4 : Partition de tableau (page 45) et Les élections présidentielles à l’américaine (page 46), qui sont, comme celui-ci, des variantes de problème du sac à dos 0/1 (voir le chapitre 8.1.1, page 75). Question 6 : Modifier ce problème pour résoudre le problème de partition de tableau (exercice 4.8, page 45). L’appliquer au tableau T = (6, 3, 5, 7). Sur Wikipedia : Subset sum problem. Exercice 99.18 (♥ Segmentation d’une image). On dispose d’une image binaire sous forme de matrice de pixels T [1 : m ; 1 : n], dont les éléments valent 0 (blanc) ou 1 (noir). On cherche à partager l’image en un ensemble de rectangles entièrement noirs ou entièrement blancs. La technique utilisée est celle de la guillotine, dont la règle est la suivante : étant donné un rectangle, on a le droit de le partager en deux rectangles plus petits soit par un trait horizontal, soit par un trait vertical. Le problème est de trouver le nombre minimal de coups de guillotine pour séparer complètement les pixels noirs des pixels blancs. Dans l’exemple ci-dessous, le nombre de coups de guillotine est 12. 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 Question 1 : Donner un ordre possible des coups de guillotine pour le problème donné en exemple. Question 2 : Donner une relation de récurrence pour calculer le nombre minimal de coups de guillotine nécessaires pour partager une image quelconque. Anonyme Exercice 99.19 (♥ Le plus grand carré noir). Soit une image carrée composée de pixels noirs et blancs. On cherche la plus grande sous-image carrée complètement noire. De manière plus formelle, étant donnée une matrice binaire T [1 : n , 1 : n] on cherche trois nombres entiers positifs l, m et k, avec k maximum, tels que : 1. 1 ≤ l ≤ n 2. 1 ≤ m ≤ n 97 3. k ≤ Min(n − l + 1, n − m + 1) 4. T [i, j] = 1 pour l ≤ i ≤ l + k − 1 et m ≤ j ≤ m + k − 1 Par exemple, dans le tableau ci-dessous (n = 6), la solution unique est pour les valeurs : l = 3, m = 2 et k = 3. 0 0 1 1 0 0 0 0 1 1 0 0 0 1 1 1 1 1 0 1 1 1 1 1 0 1 1 1 0 1 0 0 1 1 0 0 Question 1 : Donner un algorithme en Θ(n2 ) pour trouver l, m et k. L’opération élémentaire est la comparaison. Question 2 : Que dire de la complexité en espace ? La solution est page ?? Exercice 99.20 (♥ L’étagère). On veut ranger n livres B1 , . . . , Bn dans une étagère. Les contraintes sont les suivantes : – L’ordre dans lequel les livres sont disposés est fixé : B1 doit se trouver à gauche de l’étage du haut, B2 est serré à droite de B1 ou à gauche de l’étagère du dessous, etc. – L’étagère a une largeur fixe L, mais le nombre d’étages est réglable, et la hauteur de chaque étage est réglable. Pour simplifier, on dira que les planches qui forment les étages ont une épaisseur nulle. L’étage du haut est surmonté d’une planche qui définit la hauteur totale de l’étagère. – Chaque livre Bi est caractérisé par sa hauteur hi et son épaisseur ei . La figure ci-dessous donne deux façons différentes d’arranger une étagère de largeur 10 pour ranger cinq livres de dimensions : i ei hi 1 3 5 2 3 7 3 2 9 4 3 3 5 2 10 La hauteur totale de la première étagère est 7 + 10 = 17. Celle de la seconde est 9 + 10 = 19. Le problème est de trouver comment disposer les planches de l’étagère pour qu’elle ait une hauteur totale minimale. Question 1 : On note H(i) la hauteur minimale d’une étagère de largeur L qui range les livres Bi , . . . , Bn (avec 1 ≤ i ≤ n). 98 CHAPITRE 99. EXERCICES EN COURS DE RÉDACTION. On note t(i, j) la hauteur minimale d’une rangée de livres commençant par Bi et se terminant par Bj (avec j ≥ i). Pj On a : t(i, j) = Max hk si k=i ek ≤ L et t(i, j) = +∞ sinon. k=i,j Trouver une relation de récurrence sur les valeurs H(i) et t(i, j). Question 2 : En déduire un algorithme de programmation dynamique pour calculer H(1). Quelle est sa complexité temporelle et spatiale ? Anonyme Exercice 99.21 (♥ La plus courte sur-séquence commune). L(i, j est la longueur de la plus courte surséquence commune à u1 . . . ui et v1 . . . vj . Question 1 : Récurrence ? Question 2 : Exemple u = abcab et v = acda Anonyme Exercice 99.22 (♥ Approximation d’une fonction échantillonnée par une ligne brisée). Soit un ensemble S de n points du plan, avec n ≥ 1, dont les abcisses valent 1,2, . . . n et dont les ordonnées sont quelconques. On cherche la meilleure approximation de cet ensemble par une ligne brisée. Pour être plus précis : 1. Une ligne brisée est une suite de segments de droites dont les extrémités sont des points de S. Une ligne brisée peut se représenter par la suite des abcisses des points sur lesquels elle s’appuie. On impose que le premier nombre vale 1 et le dernier n, autrement dit que le premier point de S soit le départ du premier segment de droite et son dernier point l’arrivée du dernier segment de droite. Par exemple, dans les quatre figures ci-dessous, les lignes brisées sont successivement : (1, 4, 8), (1, 5, 8), (1, 4, 7, 8) et (1, 3, 4, 8). 2. La qualité de l’approximation de S par une ligne brisée se mesure pour partie en calculant la somme Σ des distances euclidiennes des points de S au segment de droite dont les extrémités les encadrent. Pour la première figure, Σ est la somme : – de la distance des points 1, 2, 3 et 4 au segment construit sur les points 1 et 4, soit environ (0 + 0.35 + 0.35 + 0) = 0.7, – et de la distance des points 4, 5, 6, 7 et 8 au segment construit sur les points 4 et 8, soit environ (0 + 1.6 + 0.4 + 1.2 + 0) = 3.2. 3. On ajoute à Σ un terme positif mC, proportionnel au nombre m de segments de droite de la ligne brisée. Dans l’exemple de la première figure, si l’on choisit la valeur de C à 2, ce terme vaut 4, puisque m = 2. 4. Pour finir, on dit que la qualité de l’approximation réalisée est d’autant meilleure que la somme O(n) = Σ + mC est petite. 5. Par convention, on pose O(1) = 0. Dans le premier exemple ci-dessous, cette valeur vaut donc à peu près 0.7 + 3.2 + 4 = 7.9. On calcule de la même manière, pour la figure 3, la valeur Σ = (0 + 0.3 + 0.3 + 0) + (0 + 0.8 + 0.2 + 0) + (0 + 0) = 1.6 et O(n) = 1.6 + 3 × 2 = 7.6. Elle est donc meilleure que la précédente. Le second exemple propose une approximation moins bonne que la première (à cause de Σ). De même, la quatrième approximation est moins bonne que la troisième. La meilleure des quatre est donc finalement la troisième. Pour C = 2, sous réserve d’un calcul exhaustif, il semble donc meilleur de prendre trois segments que deux. Le problème est finalement de trouver la ligne brisée optimale : étant donné S et C, celle qui minimise O(n) = Σ + mC. 99 5 4 3 2 1 0 bc bc c b bc bc bc bc c b 0 1 2 3 4 5 6 7 8 Exemple 1 : n = 8, m = 2, ligne brisée (1, 4, 8) 5 4 3 2 1 0 bc bc bc bc bc bc bc bc 0 1 2 3 4 5 6 7 8 Exemple 2 : n = 8, m = 2, ligne brisée (1, 5, 8) 100 5 4 3 2 1 0 CHAPITRE 99. EXERCICES EN COURS DE RÉDACTION. bc bc c b bc bc bc bc c b 0 1 2 3 4 5 6 7 8 Exemple 3 : n = 8, m = 3, ligne brisée (1, 4, 7, 8) 5 4 3 2 1 0 bc bc bc bc bc bc bc bc 0 1 2 3 4 5 6 7 8 Exemple 4 : n = 8, m = 3, ligne brisée (1, 3, 4, 8). Question 1 : Que peut-on dire quand C est très petit ? Quand C est très grand ? Supposons que la ligne brisée optimale de valeur O(n) corresponde à la suite croissante d’abcisses (a1 , a2 , . . . , am−1 , am ), avec a1 = 1 et am = n. Cette suite d’abcisses est alors aussi dite optimale. Notons plus généralement O(i) la valeur optimale que l’on peut obtenir pour approximer par une ligne brisée l’ensemble des points d’abcisses (1, . . . i). Maintenant, notons k = am−1 l’abcisse de départ du dernier segment de la ligne brisée optimale pour l’ensemble S des n points, et Σk,n la somme des distances des points d’abcisses (k, . . . , n) à la droite passant par les deux points de S ayant pour abcisses k et n. Question 2 : Montrer que : O(n) = O(k) + Σk,n + C. Comment calculer O(n) sans connaître k, mais en supposant connaître O(1) = 0, O(2), . . . O(n − 1) ? Question 3 : Démontrer une formule de récurrence permettant de calculer pour tout i, avec (1 ≤ i ≤ n), la valeur O(i) en fonction – des valeurs de O(j) pour 1 ≤ j ≤ i – des valeurs Σj,i – et de C. Question 4 : On suppose qu’il existe une procedure Distance(j, i, k), avec j ≤ k ≤ i, qui calcule la distance du point d’abcisse k à la droite construite sur les points j et i. Quels sont les calculs à faire et dans quel ordre faut-il les faire pour calculer O(n) ? Question 5 : En déduire le pseudo-code d’un algorithme qui calcule la valeur de l’approximation O(n) par une ligne brisée optimale d’un ensemble S quelconque de n points. Quel est son ordre de grandeur de complexité en nombre d’appel à Distance() ? Question 6 : Comment pourrait-on modifier cet algorithme pour qu’il produise aussi les abcisses des points de la ligne brisée optimale ? La solution est page ?? Exercice 99.23 (♥ Jeu à deux joueurs). Voir [GOO01], exercice C-9.22 et C-9.23. Deux joueurs. Sur la table, une ligne de 2n cartes avec un nombre écrit sur chaque carte. L’ensemble des cartes est à tout moment visible des deux joueurs. Chaque joueur, à tour de rôle, doit prendre une des deux cartes situées aux extrémités de la ligne. La carte disparaît alors du jeu et le gain du joueur augmente du nombre écrit sur la carte. 1. Quand l’adversaire veut perdre. On se pose d’abord la question de savoir quel est le gain maximal que pourrait récupérer le joueur qui joue le premier (ce qui revient à supposer que son adversaire collabore au maximum avec lui). Question 1 : Donner une récurrence qui permet de calculer le gain final maximal du joueur qui joue le premier, 101 Question 2 : En déduire un algorithme en O(n2 ) opérations élémentaires pour calculer cet optimum. Question 3 : Montrer sur un exemple que ce gain maximal n’est pas toujours égal à la somme des n plus grandes valeurs des cartes. 2. Quand l’adversaire veut gagner. On suppose maintenant que l’adversaire cherche également à gagner. Question 4 : Donner une récurrence qui permet de calculer le gain final maximal du joueur qui joue le premier, sans oublier que l’autre joueur cherche aussi à maximiser son propre gain. Question 5 : En déduire un algorithme en O(n2 ) opérations élémentaires pour calculer cet optimum. Question 6 : Faire tourner l’algorithme sur la ligne de cartes suivante : 6 7 10 8 La solution est page ?? Exercice 99.24 (♥ Le tas de briques). On dispose de briques de n types différents, et pour chaque type d’un nombre illimité d’exemplaires. Une brique de type i est un parallélépipède rectangle dont les côtés ont pour longueur c1i , c2i et c3i , avec c1i ≤ c2i ≤ c3i . On cherche à faire un tas de hauteur maximale. On commence par poser une brique sur une de ses faces, puis une seconde sur la première, les côtés parallèles, puis une troisième, etc. La contrainte est que l’on ne peut poser une brique sur le tas en construction que si la face que l’on pose est strictement incluse dans la face supérieure de la brique précédente. Autrement dit, à chaque nouvelle brique, le tas va en rétrécissant strictement. Question 1 : Montrer qu’il y a au plus six façons de poser une nouvelle brique sur une brique déjà posée. Question 2 : Montrer que parmi ces six façons, seulement trois sont à prendre en compte. Question 3 : Montrer que dans un tas il y a au plus deux briques d’un type donné. Question 4 : Reformuler le problème comme un problème d’empilement optimal, avec un choix non plus parmi un nombre illimité de briques, mais un choix parmi 3n objets différents. Question 5 : Donner la formule de récurrence permettant de construire le tas le plus haut. La solution est page ?? Exercice 99.25 (♥ Distribution de skis). On dispose de m paires de skis qu’il faut attribuer à n skieurs, avec m ≥ n. On doit attribuer à chaque skieur une paire de skis et une seule ; d’autre part, il s’agit de maximiser la satisfaction globale des skieurs. On dira qu’un skieur est d’autant plus satisfait que la longueur de la paire de skis qu’on lui attribue est proche de sa taille. Plus formellement, notons h1 , . . . , hn les tailles des skieurs et s1 , . . . , sm les longueurs des skis. Il s’agit de trouver une fonction injective f : {1 . . . , n} 7−→ {1, . . . , m} optimale, c’est à dire qui minimise la valeur : n X |sf(k) − hk | A(n, m) = k=1 Pour simplifier, on supposera tous les skis de longueur différente et tous les skieurs de taille différente. 102 CHAPITRE 99. EXERCICES EN COURS DE RÉDACTION. Analyse Question 1 : Montrer que pour n = 2 et m = 2, la solution optimale est obtenue en attribuant les skis les plus longs au skieur le plus grand et les skis les plus courts au skieur le plus petit. On fait désormais l’hypothèse que les suites h1 , . . . , hn et s1 , . . . , sm sont rangées par ordre croissant. Question 2 : Montrer que pour n = m, la solution optimale est obtenue par un algorithme glouton. Question 3 : Proposer un algorithme glouton pour le cas m > n et donner un cas sur lequel il ne fonctionne pas (par exemple celui de la question 6). Notons A(i, j) la solution optimale qui utilise les skis de rang 1 à j pour équiper les skieurs de rang 1 à i, avec j ≥ i. Il y a deux cas exclusifs : – soit la paire de skis de rang j a été attribuée à un skieur, – soit elle n’a pas été attribuée. Question 4 : En utilisant la question 2, montrer que dans le premier cas cité ci-dessus, c’est obligatoirement au skieur de rang i que la paire de skis de rang j a été attribuée. Question 5 : En déduire la formule de récurrence liant A(i, j), A(i, j − 1), A(i1 , j − 1) et |sj − hi |. Ne pas oublier son initialisation. Comment en déduire la fonction f ? Implémentation On s’intéresse maintenant à l’implémentation de cette récurrence sous la forme d’un algorithme. Question 6 : Prendre comme exemple m = 5 et n = 3, les valeurs de longueur des skis : s1 = 158, s2 = 179, s3 = 200, s4 = 203, s5 = 213 et les tailles des skieurs : h1 = 170, h2 = 190, h3 = 210. Résoudre le problème. Question 7 : Expliquer de manière générale dans quel ordre un tableau A[1 : n , 1 : m] doit être rempli pour que l’on puisse calculer A(n, m). Question 8 : Montrer que remplir complètement le tableau A n’est pas nécessaire. Quelle est la complexité minimale spatiale et temporelle de l’algorithme ? Vérifier que, pour n = m, on retrouve l’algorithme glouton de la question 2. La solution est page ?? Algorithmes gloutons. Exercice 99.26 (♥ Les relais pour téléphones portables). Une longue route droite de campagne, le long de laquelle sont dispersées des habitations. Chaque maison veut être reliée au réseau de téléphone portable, sur place et sur la route. Un émetteur du réseau permet l’usage du téléphone dans une zone à distance fixe autour de l’émetteur ; tous les émetteurs ont la même puissance. L’opérateur veut poser le moins d’émetteurs possibles pour « couvrir »toutes les maisons. Formellement : T [1 : n] est un tableau de nombres réels positifs croissants. On donne un nombre réel positif t. On cherche l’ensemble de valeurs réelles de cardinal minimum p, S = {s1 , . . . , sp } tel que pour toute valeur T [i], il existe une valeur sj telle que : | T [i] − sj | ≤ t. Question 1 : Donner informellement un algorithme glouton pour résoudre ce problème. Question 2 : Le donner en pseudo-code. Question 3 : Montrer qu’il est optimal. Anonyme Exercice 99.27 (♥ Blabla. Question 1 : Question 2 : Titre). La solution est page ?? Chapitre 99 Exercice en test. Exercice 99.1 (♥ Les guetteurs). On veut placer un nombre minimal de reines sur un échiquier n × n de sorte que : 1. elles ne sont pas en prise, 2. l’ensemble des cases de l’échiquier est sous leur contrôle. On rappelle qu’une reine contrôle les cases : 1. de la colonne sur laquelle elle se trouve, 2. de la ligne sur laquelle elle se trouve, et 3. des deux diagonales passant par la case où elle se trouve. Exemple. Voici une solution pour un échiquier 8 × 8. (5, 3) ❍ (4, 5) ❍ (3, 1) ❍ (2, 4) ❍ (1, 1) ❍ Remarque : on ne sait pas si elle est optimale (en fait elle l’est). Eléments d’analyse Il s’agit clairement d’un problème de recherche de solution optimale : ici le critère d’optimalité porte sur le nombre de reines posées. Dans la continuité de ce qui a été fait pour le problème des n reines, l’idée consiste à examiner successivement les lignes (ou les colonnes) puisque deux reines ne peuvent partager la même ligne (ou colonne) vu qu’elles ne doivent pas être en prise. On va utiliser les tableaux A[1 .. n], B[2 .. 2*n] et C[1 - n .. n - 1] vus précédemment pour gérer l’occupation des colonnes 103 104 CHAPITRE 99. EXERCICE EN TEST. et des diagonales. Cependant, vu qu’on peut ne pas poser de reine dans une ligne (ou colonne), il faut, outre les choix de colonne 1 à n associés à la ligne courante, ajouter le choix correspondant à ne pas poser de reine. Un point nouveau concerne le calcul des cases "en vue" de la solution courante avec le fait qu’une case peut être en vue de plusieurs reines et donc que la suppression d’un choix (opération défaire) doit être soigneusement étudié. Le principe va consister à calculer les cases en vue spécifiquement de la reine placée et ce sont celles-là qui seront enlevées lors de la remise en cause du choix courant (opération défaire). Pour ce qui est des aspects liés à l’optimalité de la solution cherchée, il faut gérer le nombre de reines utilisées. Il apparaît difficile de prédire un nombre de reines minimal à poser pour contrôler les cases non encore sous contrôle. On va donc se contenter de dire que tant qu’il reste au moins une case à contrôler, on devra encore poser au moins un reine (on inclura donc dans l’opération opt-encorepossible un test sur le nombre de reines de la solution courante + 1 d’une part et la valeur optimale actuelle du nombre de reines - celui associé à la solution optimale courante, d’autre part). Spécification. Vecteur Le vecteur X représentant la solution courante (en cours de construction) est de taille n. Le domaine Si de chaque coordonnée est constitué des valeurs 0 à n : la valeur 0 pour la coordonnée i indique que l’on ne pose pas de reine dans la ligne i. Une valeur j non nulle indique que l’on pose une reine en ligne i et colonne j. Satisfaisant Si xi = 0, Satisfaisant est VRAI. Si xi ∈ [1, n] Satisfaisant est VRAI quand la colonne xi est libre et les deux diagonales passant par (i, xi ) sont libres. Enregistrer Si xi = 0, Enregistrer se limite à : X[i] ← xi . Si xi ∈ [1, n], c’est un peu plus compliqué : X[i] ← xi ; nb-reines-cour ← nb-reines-cour +1 ; colonne xi occupée ; deux diagonales passant par xi occupées ; calcul des cases nouvellement prises (cases-nouv-prises) ; cases-à-contrôler ← cases-à-contrôler − cases-nouv-prises soltrouvé Si xi = 0 : faux Si xi ∈ [1, n] : vrai si l’ensemble des cases restant à contrôler est vide meilleur/optimal nb-reines-cour < nb-reines-opt (pas indispensable si on met le test d’espoir d’optimalité dans opt-encore-possible) opt-encore-possible (nb-reines-cour +1) < nb-reines-opt défaire Si xi = 0 : rien Si xi ∈ [1, n] : nb-reines-cour nb-reines-cour −1 ; colonne xi libre ; deux diagonales passant par xi libres cases-à-contrôler ← cases-à-contrôler cases-nouv-prises Déclarations globales type case c’est struct ent lig, col fstruct ; ens case nonatt init (1 :n)x(1 :n) ; var X[1 :n], SOPT[1 :n] ent ; tab A[1 :n] bool init vrai, B[2 :2*n] bool init vrai ; var C[-n+1 :n-1] bool init vrai ; ent nb-reinescour, nb-reines-opt, n, j ; Appel lire(n) ; nb-reines-opt n + 1 ; nb-reines-cour 0 ; guetteurs(1) ; pour j de 1 à n faire ecrire(SOPT[j]) fait ; Procédures proc guetteurs c’est fixe (ent i) ent j ; ens case nouvcasatt ; début X[i] 0 ; si i < n alors guetteurs(i+1) fsi ; co traitement du cas 0 à part fco ; pour j de 1 à n faire si A[j] et B[i+j] et C[i-j] alors X[i] j ; A[j] faux ; B[i+j] faux ; C[i-j] faux ; nouvcases(i,j)(nouvcasatt) ; nonatt nonatt - nouvcasatt ; nbreines nbreines + 1 ; si vide(nonatt) alors nb-reines-opt nb-reines-cour ; gardersol(i) fsi sinon si i < n et (nb-reines-cour + 1) < nb-reines-opt alors guetteurs(i+1) fsi fsi ; nb-reines-cour nb-reines-cour - 1 ; nonatt nonatt U nouvcasatt ; A[j] vrai ; B[i+j] vrai ; C[i-j] vrai fsi fait fin 105 proc gardersol c’est fixe (ent i) ent j ; début pour j de 1 à i faire SOPT[j] X[j] fait ; pour j de (i+1) à n faire SOPT[j] 0 fait fin proc nouvcases c’est fixe (ent i,j) mod (ens case E) case c ; début E ; pour tout c de (1 :n) (1 :n) faire si c.lig = i ou c.col = j ou c. lig+c.col = i+j ou c.lig-c.col = i-j alors E E c fsi fait E E nonatt fin Quelques résultats intéressants taille de l’échiquier "n reines" nbsol "guetteurs" nb-reines-opt - nbsol 1 1 1 1 2 0 1 4 3 0 1 1 4 2 3 16 5 10 3 16 6 4 4 120 7 40 4 8 8 92 5 728 Question 1 : Question 2 : La solution est page ?? 106 CHAPITRE 99. EXERCICE EN TEST. Chapitre 99 Exercice en test. Exercice 99.1 (♥ Distance entre séquences : algorithme de Wagner et Fisher.). Définitions On se donne un alphabet X, c’est-à-dire un ensemble fini de lettres dont la taille est notée |X| ; soit par exemple l’alphabet X = {a, b, c}. Une séquence, ou chaîne, ou mot ) sur X est une suite finie de lettres de X. La longueur |u| d’une séquence u est son nombre de lettres. Soit par exemple les séquences u = acbb et v = cab. On a |u| = 4. On appelle préfixe d’une séquence une séquence par laquelle elle commence. Par exemple, le préfixe de longueur 2 de u est ac. On note u[i] la lettre numéro i de u. Par exemple : u[3] = b On note ǫ la séquence de longueur nulle. Cette notion sera utile ici à deux choses : d’abord, le préfixe de longueur 0 de toute séquence est donc ǫ. Ensuite, étant donnée une séquence u = u1 u2 ...un , on peut y insérer autant de ǫ que l’on veut sans en changer la signification. On appellera superséquence de u la séquence u avec de telles insertions. Par exemple, si u = bcaa, une superséquence de u est : ǫbcǫǫaa. Par abus de langage, on dira que la longueur de cette surséquence est 7. Soit deux séquences u = u1 u2 ...un et v = v1 v2 ...vm et deux superséquences de u et de v de même longueur. On appelle « trace »entre u et v la mise en correspondance lettre à lettre des deux superséquences. On exige de plus qu’une trace n’associe pas deux ǫ. Par exemple, entre les mots u = bcaa et v = acbca, on peut créer la trace : ǫ | a ǫ | c b | b c | ǫ a | c a | a Une trace peut s’interpréter comme une suite d’insertions, de suppressions et de transformations de lettres de la première séquence pour former la seconde. Dans l’exemple précédent, la trace correspond à la suite suivante : 1. insertion de a 4. suppression de c 2. insertion de c 5. transformation de a en c 3. transformation de b en b 6. transformation de a en a Pour donner une valeurs aux coûts des insertions, des suppressions et des transformations, on définit une matrice δ de nombres réels positifs ou nuls de taille |X| + 1 × |X| + 1. C’est une matrice qui définit une distance : elle symétrique, de diagonale nulle et vérifiant l’inégalité triangulaire. La valeur d’un élément de cette matrice peut s’interpréter comme le coût de transformer une lettre en l’autre ou comme le coût de suppression et d’insertion pour chaque lettre. Par exemple, sur un alphabet de trois lettres, une telle matrice pourrait être : 107 108 CHAPITRE 99. EXERCICE EN TEST. δ ǫ a b c ǫ 0 1 1 1 a 1 0 1.5 1.2 b 1 1.5 0 1.7 c 1 1.2 1.7 0 Dans cet exemple, le coût de suppression de a est 1 (δ(a, ǫ) = 1), le coût d’insertion de b est 1 (δ(ǫ, b) = 1), le coût de transformation de a en c est 1.2 (δ(a, c) = 1.2). On appelle le coût d’une trace la somme des coûts élémentaires des opérations qui la constituent. Dans l’exemple précédent, le coût de la trace est donc : 1 + 1 + 0 + 1 + 1.2 + 0 = 5.2 Une autre trace entre les mots u = bcaa et v = acbca est par exemple, pour un coût de 1.5 + 0 + 1.5 + 1 + 0 = 4 : b | a c | c a | b ǫ | c a | a Le problème. Le problème est de trouver le coût de la trace optimale, c’est à dire la moins coûteuse, entre deux séquences u et v. On définit pour cela une matrice ∆(0 : n , 0 : m), de taille (n + 1) × (m + 1), telle que ∆(i, j) est le coût de la trace la moins coûteuse entre le préfixe de longueur i de u et le préfixe de longueur j de v. On cherche donc la valeur ∆(|u| + 1, |v| + 1) = ∆(n + 1, m + 1). Questions. Question 1 : Comment calculer ∆(0, j) pour j = 1, |v| et ∆(i, 0) pour i = 1, |u| ? Quelle valeur donner à ∆(0, 0) ? Question 2 : Cette question vise à établir une relation de récurrence qui calcule ∆(i, j) à partir de ∆(i, j − 1), ∆(i − 1, j), ∆(i − 1, j − 1), δ(u[i], ǫ), δ(ǫ, v[j]) et δ(u[i], v[j]). Pour cela, on remarque qu’une trace peut s’interpréter comme un chemin dans un graphe. Cette interprétation est illustrée ci-dessous sur l’exemple de trace : ǫ ǫ b c a a | | | | | | a c b ǫ c a Un chemin dans le graphe entre le nœud étiqueté (0/0) et le nœud étiqueté (4/5) représente une trace entre les séquences u = bcaa et v = acbca. La trace précédente est marquée en gras. a 4/0 4/1 4/2 4/3 4/4 4/5 a 3/0 3/1 3/2 3/3 3/4 3/5 c 2/0 2/1 2/2 2/3 2/4 2/5 b 1/0 1/1 1/2 1/3 1/4 1/5 0/0 0/1 0/2 0/3 0/4 0/5 a c b c a (a) Définir dans le cas général un tel graphe pour deux séquences quelconques u et v. (b) Montrer, en donnant des longueurs calculées à partir de δ aux arcs du graphe, que la trace optimale entre deux mots correspond au calcul d’un plus court chemin dans ce graphe. (c) Compte tenu de la forme particulière du graphe, donner une relation de récurrence pour calculer la longueur du plus court chemin entre le nœud (0/0) et tout autre nœud de ce graphe. 109 (d) En déduire la relation de récurrence du calcul du coût de la trace optimale en remplissant la matrice ∆(0 : n , 0 : m). Le coût de la trace optimale doit être la valeur de ∆(n, m). Question 3 : Combien y a-t’il de traces différentes entre deux mots u et v ? Question 4 : Donner la procédure de Wagner et Fisher WF qui calcule le coût de la trace la moins coûteuse (et permet de reconstituer cette trace) entre deux séquences quelconques sur un alphabet Σ, connaissant la matrice δ, avec une complexité en taille O(nm) (rappelons que n = |u| et m = |v|). Question 5 : Montrer que si l’on cherche simplement le coût de la trace la moins coûteuse, une complexité en taille O(Max(n, m)) est suffisante. Ecrire ce programme (appelé Wagner et Fisher « linéaire », ou WFL). Question 6 : On prend l’alphabet du français, avec : – pour toute lettre α : δ[α, ǫ] = δ[ǫ, α] = 2 ; – si α et β sont deux consonnes ou sont deux voyelles : δ[α, β] = δ[β, α2] = 1 ; – si α est une consonne et β une voyelle : δ[α, β] = δ[β, α] = 3 ; Donner la matrice ∆ pour les séquences malin et coquine. Question 7 : Comment reconstituer une trace optimale à partir de ∆ ? Dans l’exemple précédent, cette trace est unique. Est-ce cas en général (indication :essayer u = rien et v = est). Question 8 : Montrer que la trace optimale entre les phrases miroir u et v se déduit directement de celle entre u et v, si elle est unique. Et si elle ne l’est pas ? Question 9 : Montrer que, puisque δ définit une distance, alors ∆ définit aussi une distance (d’où le titre de cet exercice). La solution est page ?? Exercice 99.2 (♥ titre). Réponse 1 : La solution est page ?? 110 CHAPITRE 99. EXERCICE EN TEST. Bibliographie [Bou93] L. Bougé, C. Kenyon, J.-M. Muller et Y. Robert. Algorithmique, exercices corrigés. Ellipses, 1993. [BRA96] G. Brassard and P. Bratley. Fundamentals of algorithmics, Prentice-Hall, 1996. [COR90] T. Cormen, C. Leiserson and R. Rivest. Introduction to algorithms., MIT Press, 1990. [CRO01] M. Crochemore, C. Hancart and Thierry Lecroq. Algorithmique du texte., Vuibert, 2001. [FRO90] C. Froidevaux, M.-C. Gaudel, M. Soria. Types de données et algorithmes., McGraw-Hill, 1990. [GOO01] M. Goodrich and R. Tamassia. Algorithm design, Wiley, 2001. [GRI93] D. Gries and F. Schneider. A logical approach to discrete mathematics, Springer, 1993. [HOR78] E. Horowitz, S. Sahni and S. Rajasekaran Computer algorithms, Computer Science Press, 1998. [KLE06] J. Kleinberg and E. Tardos Algorithm Design, Addison Wesley, 2006. [Knu] D. Knuth, The Art of Computer Programming, Addison-Wesley. [JOH04] R. Johnsonbaugh and M. Schaeffer. Algorithms, Pearson, Prentice-Hall 2004. [MAN89] U. Manber Introduction to algorithms : a creative approach., Addison Wesley, 1989. [PAR95] I. Parberry. Problems on algorithms, Prentice-Hall, 1995. [PIN] S. Pinchinat et V. Schmitt. Algorithmique et complexité, Polycopié IFSIC, Université de Rennes 1. [Que00] M. Quercia, Nouveaux exercices d’algorithmique, Vuibert 2000. [RUS95] S. Russel and P. Norvig, Artificial intelligence, a modern approach, Prentice-Hall 1995, Second edition 2002. [Web1] http://www2.toki.or.id/book/AlgDesignManual/ [Web2] http://valis.cs.uiuc.edu/~sariel/teach/courses/473/ 111 Index suite, 58, 107 112