NSI Terminale • Programme français, lycées de Montréal
Exercices corrigés de NSI : diviser pour régner, programmation dynamique et graphes
Voici une série d'exercices corrigés de NSI pour la classe de Terminale, sur l'algorithmique avancée du programme français. Elle s'adresse aux élèves des lycées français de Montréal, le Lycée Marie de France et le Collège Stanislas.
Quinze exercices couvrent la méthode « diviser pour régner » avec la recherche dichotomique, le tri fusion et le tri rapide, la relation de récurrence du coût lue sur un arbre de récursion, la programmation dynamique avec le problème du rendu de monnaie et le problème du sac à dos, les parcours de graphe en profondeur et en largeur, puis, en partie C, la distance d'édition de Levenshtein et la pyramide de nombres. À la fin, vous savez tracer une dichotomie et en compter les comparaisons (exercice 1), dérouler un tri fusion et justifier son coût en nlog2n (exercice 2), lire le coût d'un algorithme sur sa récurrence (exercice 4), montrer qu'une méthode gloutonne de rendu de monnaie échoue puis remplir la table qui la corrige (exercice 5), remplir et remonter la table du sac à dos (exercices 6 et 10), choisir entre pile et file pour parcourir un graphe (exercice 7) et calculer la distance d'édition entre deux mots (exercice 11).
Le fil de la série : accélérer un algorithme, ce n'est jamais faire les mêmes opérations plus vite, c'est en SUPPRIMER. Diviser pour régner en supprime en coupant le problème en deux ; la programmation dynamique en supprime en cessant de recalculer ce qu'on sait déjà ; un bon parcours de graphe en supprime en ne visitant chaque sommet qu'une fois.
Trois pièges sont désignés nommément dans le corrigé : appliquer une dichotomie à un tableau non trié (exercices 1 et 8), croire qu'une méthode gloutonne est optimale sans le démontrer (exercices 5, 10 et 14), et comparer deux algorithmes par leur seul ordre de grandeur en oubliant les constantes (exercice 4).
Ce que le programme exige, et ce qu'il n'exige pas. Il demande d'écrire un algorithme « diviser pour régner », avec le tri fusion pour exemple, d'utiliser la programmation dynamique, de parcourir un graphe en profondeur et en largeur, d'y repérer un cycle et d'y chercher un chemin. Le coût du tri rapide selon son pivot, l'algorithme de Dijkstra, Karatsuba, la coloration d'un graphe et la borne inférieure des tris vont au-delà, et sont étiquetés comme approfondissements. Le dernier point d'algorithmique du programme, la recherche textuelle avec l'algorithme de Boyer-Moore, a sa propre série, liée en bas de page.
Série autocorrigéeTape tes réponses et vérifie-les question par question : la page te dit juste ou faux avant d'ouvrir la correction. Avec un compte, chaque bonne réponse du premier coup rapporte des points, et tes réponses justes restent remplies quand tu reviens.
•Tri rapide : nlog2n en moyenne, n2 au pire, notamment sur un tableau déjà trié avec pivot fixe.
•Méthode de l'arbre de récursion : ak sous-problèmes de taille n/bk au niveau k, logbn niveaux, on somme le travail par niveau.
•Une méthode gloutonne doit être DÉMONTRÉE. Contre-exemple : pièces 1, 3, 4 et somme 6.
•Programmation dynamique : principe d'optimalité, une table remplie de bas en haut, chaque valeur calculée une seule fois.
•Sac à dos : V(i,c)=max(V(i−1,c),vi+V(i−1,c−pi)) si l'objet rentre. Coût nC, pseudo-polynomial.
•Parcours de graphe : même squelette, pile pour la profondeur, file pour la largeur. Marquer AVANT d'ajouter.
•Coût d'un parcours : O(n+m) avec des listes d'adjacence, O(n2) avec une matrice.
•Dijkstra : sélectionner le plus proche NON TRAITÉ, jamais le voisin le plus proche du dernier. Poids positifs obligatoires.
Partie A : Les bases (/50)
Exercice 1 : Diviser pour régner : la recherche dichotomique
Diviser pour régner tient en trois mots : couper, résoudre, recombiner. La recherche dichotomique en est le cas le plus simple, celui où la recombinaison est gratuite.
def dicho(t, v):
g, d = 0, len(t) - 1
while g <= d:
m = (g + d) // 2
if t[m] == v:
return m
if t[m] < v:
g = m + 1
else:
d = m - 1
return -1
a) Quelle précondition la fonction exige-t-elle ? Que renvoie-t-elle si la valeur est absente ?
b) Faites la trace de dicho([2, 5, 8, 12, 16, 23, 38, 56, 72, 91], 23) : donnez à chaque tour les valeurs de g, d et m.
c) Même travail pour la valeur 40. Combien de tours dans chaque cas ?
d) Combien de comparaisons au maximum pour un tableau de 1 000 éléments ? de un million ? Justifiez par la formule et comparez à une recherche séquentielle.
e) Un élève écrit m = (g + d) / 2 au lieu de la division entière. Décrivez l'erreur. Un autre écrit g = m au lieu de m + 1 : décrivez la sienne.
Voir la correction
Réponses
a)Tableau trié ; −1 si absent
b)23 trouvé en 3 tours
c)40 absent : 4 tours
d)10 et 20 comparaisons au plus
e)Division flottante bruyante ; g = m boucle
a)
La précondition est que le tableau soit TRIÉ par ordre croissant. Sans elle, la comparaison t[m] < v ne permet plus de conclure dans quelle moitié chercher, et la fonction renvoie un résultat arbitraire sans jamais signaler d'erreur. Si la valeur est absente, la boucle se termine lorsque g dépasse d, et la fonction renvoie −1.
b)
Le tableau a 10 éléments, d'indices 0 à 9. Tour 1 : g=0, d=9, m=4, t[4]=16<23, donc g devient 5. Tour 2 : g=5, d=9, m=7, t[7]=56>23, donc d devient 6. Tour 3 : g=5, d=6, m=5, t[5]=23, trouvé : la fonction renvoie 5. Trois tours.
c)
Pour la valeur 40. Tour 1 : g=0, d=9, m=4, 16<40, g devient 5. Tour 2 : g=5, d=9, m=7, 56>40, d devient 6. Tour 3 : g=5, d=6, m=5, 23<40, g devient 6. Tour 4 : g=6, d=6, m=6, t[6]=38<40, g devient 7. La condition g≤d est alors fausse et la boucle s'arrête : la fonction renvoie −1. Quatre tours. On retient que l'échec coûte à peine plus cher que le succès, contrairement à la recherche séquentielle où l'échec coûte le maximum.
d)
Chaque tour divise par deux la taille de la zone restante, donc le nombre de tours est au plus ⌊log2n⌋+1. Pour n=1000 : log2(1000)≈9,97, donc au plus 10 comparaisons. Pour n=106 : log2(106)≈19,93, donc au plus 20 comparaisons. Une recherche séquentielle en demanderait respectivement 1 000 et 1 000 000. Le rapport vaut donc 100 dans le premier cas et 50 000 dans le second : c'est le propre d'un gain logarithmique, il grandit avec la taille du problème.
e)
Avec une division non entière, m devient un FLOTTANT, par exemple 4,5. L'accès t[4.5] lève un TypeError, car un indice de liste doit être entier. L'erreur est donc immédiate et bruyante, ce qui est une chance. Avec g = m au lieu de m + 1, la boucle peut ne plus PROGRESSER : quand il ne reste qu'un élément, g=d=m, la valeur n'est pas trouvée et g reste égal à m, si bien que le tour suivant recalcule exactement le même m. La boucle est infinie, et cette fois rien ne le signale : le programme se fige. Le remède est de vérifier, comme on l'a fait pour un variant, que la quantité d−g décroît STRICTEMENT à chaque tour, ce que garantit le passage à m+1 et m−1.
Coche ici les exercices faits ou à revoir : un compte gratuit, sans mot de passe, retient tes coches et tes réponses justes d'une visite à l'autre, te dit quel chapitre attaquer ensuite et te permet de demander l'exercice qui te manque. Crée ton espace, un courriel suffit.
Exercice 2 : Le tri fusion
La figure montre le découpage d'un tableau de quatre éléments. Le tri fusion coupe jusqu'à obtenir des tableaux d'un seul élément, qui sont triés par définition, puis fusionne deux à deux en remontant.
a) Donnez le contenu de chaque case de l'arbre à la remontée, c'est-à-dire après chaque fusion, jusqu'au tableau final trié.
b) Écrivez l'algorithme de fusion de deux tableaux déjà triés, en français, et donnez son coût pour deux tableaux de tailles p et q.
c) Combien de niveaux l'arbre compte-t-il pour un tableau de n éléments ? Quel travail est effectué à chaque niveau ? Déduisez-en le coût total.
d) Écrivez la relation de récurrence vérifiée par le coût C(n) et vérifiez que C(n)=nlog2n la satisfait pour n puissance de 2.
e) Le tri fusion est-il stable ? Combien de mémoire supplémentaire demande-t-il ? Comparez avec un tri par insertion sur ces deux critères et sur le coût.
Voir la correction
Réponses
a)Fusions : 27, 38 et 3, 43, puis 3, 27, 38, 43
b)Fusion linéaire en p+q
c)log2n niveaux de travail n
d)C(n)=2C(n/2)+n vérifiée
e)Stable, mémoire n
a)
Au niveau le plus bas, les quatre tableaux d'un élément sont 38, 27, 43 et 3. Première fusion : 38 et 27 donnent 27, 38 ; 43 et 3 donnent 3, 43. Seconde fusion : on fusionne 27, 38 avec 3, 43. On compare 27 et 3, on prend 3 ; on compare 27 et 43, on prend 27 ; on compare 38 et 43, on prend 38 ; il ne reste que 43, on le prend. Le tableau final est 3, 27, 38, 43.
b)
L'algorithme : on place un indice au début de chacun des deux tableaux et l'on crée un tableau résultat vide. Tant que les deux indices sont valides, on compare les deux éléments pointés, on ajoute le plus petit au résultat et on avance l'indice correspondant. Quand l'un des deux tableaux est épuisé, on recopie tout ce qui reste de l'autre. Le coût est de p+q opérations au plus, car chaque élément est examiné et recopié exactement une fois : la fusion est LINÉAIRE, et c'est cette propriété qui fait tout l'intérêt de la méthode.
c)
Chaque niveau divise par deux la taille des tableaux, donc l'arbre compte log2n niveaux de découpage, plus le niveau des feuilles, soit log2n+1 niveaux. À chaque niveau, les fusions portent au total sur les n éléments du tableau, quel que soit le nombre de tableaux à ce niveau : deux fusions de n/2 éléments coûtent autant qu'une fusion de n. Le travail par niveau vaut donc n, et le coût total vaut nlog2n. C'est le raisonnement le plus important du chapitre : on ne compte pas les appels, on compte le travail PAR NIVEAU et on multiplie par le nombre de niveaux.
d)
La récurrence : C(n)=2C(n/2)+n, avec C(1)=0, puisqu'un tableau d'un élément est trié sans travail. Vérification avec C(n)=nlog2n : le membre de droite vaut 2×(n/2)log2(n/2)+n=n(log2n−1)+n=nlog2n−n+n=nlog2n, qui est bien le membre de gauche. Et C(1)=1×log21=0, ce qui vérifie le cas de base.
e)
Le tri fusion est STABLE, à condition que la comparaison de la fusion prenne l'élément du tableau de GAUCHE en cas d'égalité : deux éléments égaux conservent alors leur ordre initial. Il demande une mémoire supplémentaire de l'ordre de n, pour le tableau résultat de chaque fusion, ce qui est son principal défaut. Comparaison avec le tri par insertion : celui-ci est également stable, ne demande AUCUNE mémoire supplémentaire, et coûte n2/4 en moyenne. Pour n=10000, l'insertion demande environ 2,5×107 opérations contre 1,3×105 pour la fusion, soit 188 fois plus. On garde donc l'insertion pour les très petits tableaux, où sa simplicité l'emporte, et la fusion au-delà : les bibliothèques réelles combinent d'ailleurs les deux, en basculant sur l'insertion sous une trentaine d'éléments.
Exercice 3 : Le tri rapide et le choix du pivot
La figure montre l'opération centrale du tri rapide : la partition. On choisit un pivot, ici la valeur 5, et l'on réorganise le tableau pour que tout ce qui lui est inférieur soit à sa gauche et tout ce qui lui est supérieur à sa droite.
Sur le statut de cet exercice : le programme de Terminale demande d'écrire un algorithme « diviser pour régner » et cite le tri fusion, en nlog2n dans le pire des cas. Le tri rapide en est un autre exemple, et les questions a) et b) sont au programme ; l'étude de son coût selon le choix du pivot, questions c) à e), est un approfondissement.
a) Décrivez ce que garantit la partition. Le pivot est-il à sa place définitive ? Les deux zones sont-elles triées ?
b) Écrivez l'algorithme complet du tri rapide en trois lignes, en supposant la partition donnée. Où se situe le travail par rapport au tri fusion ?
c) Le tableau est déjà trié et l'on choisit systématiquement le premier élément comme pivot. Décrivez le découpage obtenu et donnez le coût total.
d) Donnez la relation de récurrence dans le meilleur cas et dans le pire cas, et le coût correspondant. Pour n=10000, comparez les deux.
e) Citez deux stratégies de choix du pivot qui rendent le pire cas improbable, et expliquez pour chacune pourquoi elle fonctionne.
Voir la correction
Réponses
a)Pivot placé, zones non triées
b)Travail avant les appels
c)Tableau trié : coût quadratique
d)nlog2n contre n2/2
e)Pivot aléatoire ou médiane de trois
a)
La partition garantit que tous les éléments à gauche du pivot lui sont inférieurs ou égaux, et tous ceux à sa droite supérieurs. Le pivot EST à sa place définitive : dans le tableau trié, il occupera exactement cette position, puisque le nombre d'éléments qui lui sont inférieurs ne changera plus. Les deux zones, en revanche, ne sont PAS triées : sur la figure, la zone gauche vaut 3, 2, 1, 4 et la droite 8, 7, 6, ni l'une ni l'autre n'étant en ordre. C'est précisément ce qui reste à faire, et c'est l'objet des appels récursifs.
b)
L'algorithme : si le tableau a moins de deux éléments, il est trié, on s'arrête ; sinon on partitionne autour d'un pivot, ce qui donne sa position définitive ; puis on trie récursivement la zone de gauche et la zone de droite. Par rapport au tri fusion, le travail est situé AVANT les appels récursifs, dans la partition, alors que le tri fusion travaille APRÈS, dans la fusion. Le tri fusion découpe sans réfléchir et recombine intelligemment ; le tri rapide découpe intelligemment et recombine sans rien faire. Les deux stratégies sont symétriques, et c'est ce qui explique leurs profils d'erreur opposés.
c)
Sur un tableau déjà trié avec le premier élément comme pivot, la partition ne trouve AUCUN élément plus petit : la zone gauche est vide et la zone droite contient les n−1 éléments restants. Le découpage est donc totalement déséquilibré, un contre n−1 à chaque étape, et la profondeur de récursion vaut n au lieu de log2n. Le coût total vaut (n−1)+(n−2)+⋯+1=n(n−1)/2, c'est-à-dire QUADRATIQUE. Le paradoxe mérite d'être noté : l'entrée la plus favorable pour un humain est la pire pour cet algorithme, et c'est aussi l'entrée la plus fréquente en pratique, puisque les données arrivent souvent déjà classées.
d)
Meilleur cas, partition équilibrée : C(n)=2C(n/2)+n, d'où C(n)=nlog2n, exactement comme le tri fusion. Pire cas, partition dégénérée : C(n)=C(n−1)+n, d'où C(n)=n(n+1)/2, quadratique. Pour n=10000 : le meilleur cas donne environ 1,33×105 opérations, le pire environ 5,00×107. Le rapport vaut environ 376 : c'est la différence entre un tri instantané et un tri qu'on voit passer.
e)
Première stratégie, le PIVOT ALÉATOIRE : on tire au hasard l'indice du pivot. Elle fonctionne parce que le pire cas ne dépend plus des données mais du tirage : aucune entrée particulière ne peut le provoquer systématiquement, et la probabilité d'obtenir plusieurs partitions très déséquilibrées de suite décroît exponentiellement. On démontre que le coût moyen reste O(nlogn) quelle que soit l'entrée. Seconde stratégie, la MÉDIANE DE TROIS : on prend la médiane du premier, du dernier et de l'élément central. Elle fonctionne parce qu'elle traite exactement les cas problématiques réels, tableau trié ou trié à l'envers, pour lesquels elle choisit l'élément central, donc une partition parfaitement équilibrée. Elle ne protège pas contre une entrée construite exprès pour la mettre en échec, ce que le pivot aléatoire, lui, garantit.
Exercice 4 : Lire une récurrence de coût
Diviser pour régner produit toujours la même forme de récurrence : le coût d'un problème de taille n vaut le coût des sous-problèmes plus le travail de découpage et de recombinaison. Savoir la lire dispense de tout calcul.
a) Pour chacune des trois récurrences suivantes, identifiez l'algorithme et donnez le coût : C(n)=C(n/2)+1 ; C(n)=2C(n/2)+n ; C(n)=C(n−1)+n.
b) Expliquez la méthode générale : combien de sous-problèmes à chaque niveau, quelle taille, quel travail par niveau, combien de niveaux ?
c) Appliquez cette méthode à C(n)=2C(n/2)+1. Quel algorithme classique a ce profil ?
d) Appliquez-la à C(n)=4C(n/2)+n. Que vaut le travail au niveau k, et quel niveau domine ?
e) Deux algorithmes coûtent respectivement 100n et nlog2n. À partir de quelle taille le second devient-il plus coûteux ? Que faut-il en conclure sur l'usage des ordres de grandeur ?
Voir la correction
Réponses
a)Dichotomie, tri fusion, tri rapide au pire
b)Arbre de récursion : travail par niveau
c)2C(n/2)+1 : linéaire
d)4C(n/2)+n : quadratique
e)Seuil 2100 : les constantes comptent
a)
Première récurrence, C(n)=C(n/2)+1 : un seul sous-problème de taille moitié et un travail constant. C'est la recherche DICHOTOMIQUE, de coût log2n. Deuxième, C(n)=2C(n/2)+n : deux sous-problèmes de taille moitié et un travail linéaire. C'est le TRI FUSION, de coût nlog2n. Troisième, C(n)=C(n−1)+n : un sous-problème à peine plus petit et un travail linéaire. C'est le tri rapide dans son PIRE CAS, ou le tri par sélection, de coût n2/2.
b)
La méthode générale, dite de l'arbre de récursion. On se demande d'abord combien de sous-problèmes chaque appel engendre, appelons ce nombre a, et de quelle taille, disons n/b. Au niveau k, il y a donc ak sous-problèmes de taille n/bk. On se demande ensuite quel travail chacun effectue en dehors des appels récursifs, et l'on multiplie par ak pour obtenir le travail du niveau. Enfin, le nombre de niveaux est le nombre de divisions nécessaires pour atteindre la taille 1, soit logbn. Le coût total est la somme des travaux de tous les niveaux, et le résultat dépend de qui l'emporte : si tous les niveaux coûtent autant, on multiplie ; si le travail décroît, le premier niveau domine ; s'il croît, le dernier domine.
c)
Pour C(n)=2C(n/2)+1 : il y a 2k sous-problèmes au niveau k, chacun coûtant 1, donc le niveau k coûte 2k. Le travail CROÎT avec la profondeur, et le dernier niveau, celui des feuilles, en compte 2log2n=n. La somme 1+2+4+⋯+n vaut 2n−1 : le coût est LINÉAIRE, et le dernier niveau porte à lui seul la moitié du total. Le profil correspond au parcours complet d'un arbre binaire équilibré, ou au calcul du maximum d'un tableau par découpage en deux.
d)
Pour C(n)=4C(n/2)+n : au niveau k il y a 4k sous-problèmes de taille n/2k, chacun coûtant n/2k, donc le niveau coûte 4k×n/2k=n×2k. Le travail DOUBLE à chaque niveau : c'est donc le DERNIER qui domine. Le nombre de niveaux vaut log2n, donc le dernier niveau coûte n×2log2n=n×n=n2, et la somme géométrique vaut environ 2n2. Le coût est quadratique. C'est exactement le profil de la multiplication de matrices par blocs naïve, que l'algorithme de Strassen améliore en ramenant les 4 sous-problèmes à 7 sur des blocs deux fois plus petits.
e)
On cherche n tel que nlog2n>100n, c'est-à-dire log2n>100, donc n>2100≈1,27×1030. Autrement dit, JAMAIS en pratique : aucun jeu de données réel n'atteint cette taille. Il faut en conclure qu'un ordre de grandeur ne dit rien sur les tailles utiles s'il est pris seul. La constante multiplicative compte, et un algorithme asymptotiquement meilleur peut être plus lent sur toutes les entrées qu'on rencontrera. On compare donc les ordres de grandeur pour choisir une STRUCTURE d'algorithme, et l'on mesure pour trancher entre deux implémentations de même ordre.
Exercice 5 : Programmation dynamique : le rendu de monnaie
Rendre la monnaie avec le moins de pièces possible paraît trivial : on prend toujours la plus grosse pièce qui passe. Cette méthode gloutonne est fausse, et le contre-exemple tient en deux lignes.
a) Avec le système de pièces 1, 2, 5, 10, rendez 38 par la méthode gloutonne. Combien de pièces ?
b) Avec le système 1, 3, 4, rendez 6 par la méthode gloutonne, puis donnez la solution optimale. Quelle est la conclusion ?
c) On note m(s) le nombre minimal de pièces pour rendre la somme s. Écrivez la relation de récurrence vérifiée par m, avec son cas de base, pour un système de pièces donné.
d) Appliquez cette relation au système 1, 3, 4 pour s allant de 0 à 6. Présentez le tableau des valeurs.
e) Écrivez l'algorithme qui remplit ce tableau de bas en haut, donnez son coût pour une somme s et p pièces, et expliquez pourquoi il est préférable à une version récursive naïve.
Voir la correction
Réponses
a)38 : 6 pièces
b)Glouton 3 pièces, optimum 2
c)m(s)=1+minm(s−c)
d)Tableau 0, 1, 2, 1, 1, 2, 2
e)Remplissage en s×p
a)
Système 1, 2, 5, 10, somme 38. On prend trois pièces de 10, il reste 8 ; une pièce de 5, il reste 3 ; une pièce de 2, il reste 1 ; une pièce de 1. Total : 3+1+1+1=6 pièces. Ce résultat est bien optimal, et l'on démontre que la méthode gloutonne l'est toujours pour ce système, ce qui explique pourquoi personne ne s'en méfie au quotidien.
b)
Système 1, 3, 4, somme 6. La méthode gloutonne prend une pièce de 4, il reste 2 ; puis deux pièces de 1 : total 3 pièces. La solution optimale est deux pièces de 3, soit 2 pièces. La conclusion est que la méthode gloutonne N'EST PAS optimale en général : elle l'est pour certains systèmes de pièces, dits canoniques, et fausse pour d'autres. Une méthode gloutonne doit donc toujours être justifiée, jamais supposée ; et le contre-exemple, ici, tient en une ligne.
c)
La relation : m(0)=0, car rendre zéro ne demande aucune pièce ; et pour s>0, m(s)=1+min{m(s−c)}, le minimum étant pris sur toutes les pièces c du système telles que c≤s. La lecture est directe : quelle que soit la solution optimale, elle utilise au moins une pièce ; si l'on connaissait laquelle, le reste serait une solution optimale pour s−c. Comme on ne le sait pas, on essaie toutes les pièces et l'on garde la meilleure. C'est le principe d'optimalité, qui fonde toute la programmation dynamique.
d)
Système 1, 3, 4. m(0)=0. m(1)=1+m(0)=1, seule la pièce de 1 est utilisable. m(2)=1+m(1)=2. m(3)=1+min(m(2),m(0))=1+min(2,0)=1, en prenant la pièce de 3. m(4)=1+min(m(3),m(1),m(0))=1+min(1,1,0)=1, en prenant la pièce de 4. m(5)=1+min(m(4),m(2),m(1))=1+min(1,2,1)=2. m(6)=1+min(m(5),m(3),m(2))=1+min(2,1,2)=2, en prenant une pièce de 3 puis m(3)=1. Le tableau est donc 0, 1, 2, 1, 1, 2, 2 pour s de 0 à 6, ce qui confirme le résultat de la question b.
e)
L'algorithme : on crée un tableau de taille s+1, on met m[0] à 0 et toutes les autres cases à une valeur très grande ; puis, pour k allant de 1 à s, et pour chaque pièce c inférieure ou égale à k, on remplace m[k] par 1+m[k−c] si c'est plus petit ; on renvoie m[s]. Le coût est de s×p opérations, puisque chaque case examine chaque pièce : il est donc PSEUDO-POLYNOMIAL, linéaire en la somme et en le nombre de pièces. Il est préférable à la version récursive naïve pour la raison vue avec Fibonacci : celle-ci recalcule les mêmes sous-sommes un nombre exponentiel de fois, alors que le remplissage de bas en haut calcule chaque valeur exactement une fois et la conserve. On peut aussi mémoriser la pièce choisie dans un second tableau, ce qui permet de reconstituer la liste des pièces et pas seulement leur nombre.
Partie B : Problèmes et raisonnement (/50)
Exercice 6 : Programmation dynamique : le sac à dos
La table donne, pour chaque nombre d'objets considérés et chaque capacité, la valeur maximale atteignable. Les quatre objets ont pour couples poids et valeur : (2;3), (3;4), (4;5) et (5;6). La capacité du sac est 5.
a) Expliquez ce que représente la case de la ligne i et de la colonne c. Pourquoi la ligne 0 est-elle remplie de zéros ?
b) Écrivez la relation de récurrence qui remplit la table, en distinguant les deux cas.
c) Vérifiez par le calcul les cases de la ligne 2, colonnes 3, 4 et 5.
d) Lisez la réponse au problème et reconstituez le contenu du sac en remontant la table. Détaillez la remontée.
e) Donnez le coût en temps et en mémoire pour n objets et une capacité C. Pourquoi ne dit-on pas que ce problème est résolu en temps polynomial ? Comment réduire la mémoire ?
Voir la correction
Réponses
a)Case : meilleure valeur avec i objets et capacité c
b)Laisser ou prendre l'objet
c)Ligne 2 : 4, 4, 7
d)Valeur 7 : objets 1 et 2
e)Pseudo-polynomial, deux lignes suffisent
a)
La case de la ligne i et de la colonne c contient la valeur maximale que l'on peut emporter en n'ayant le droit d'utiliser que les i premiers objets et en ne dépassant pas la capacité c. La ligne 0 est remplie de zéros parce qu'avec zéro objet disponible, on n'emporte rien, quelle que soit la capacité offerte. C'est le cas de base, et il est indispensable : c'est sur lui que s'appuient toutes les autres cases.
b)
Notons pi et vi le poids et la valeur du i-ième objet, et V(i,c) la case cherchée. Premier cas, si pi>c : l'objet ne rentre pas, donc V(i,c)=V(i−1,c). Second cas, si pi≤c : on a le choix entre le laisser, ce qui donne V(i−1,c), et le prendre, ce qui donne vi+V(i−1,c−pi) ; on garde le maximum des deux. La subtilité est le c−pi : en prenant l'objet, on ne dispose plus que de la capacité restante pour les objets précédents.
c)
Ligne 2, l'objet 2 pesant 3 et valant 4. Colonne 3 : l'objet rentre, on compare V(1,3)=3 à 4+V(1,0)=4+0=4 ; le maximum est 4, ce qui correspond à la table. Colonne 4 : on compare V(1,4)=3 à 4+V(1,1)=4+0=4 ; le maximum est 4, conforme. Colonne 5 : on compare V(1,5)=3 à 4+V(1,2)=4+3=7 ; le maximum est 7, conforme. Cette dernière case est la seule où les deux premiers objets tiennent ensemble, 2+3=5, exactement la capacité.
d)
La réponse au problème est la case de la dernière ligne et de la dernière colonne, soit V(4,5)=7. Remontée. En (4,5), la valeur 7 est égale à V(3,5)=7 : l'objet 4 n'a donc pas été pris, on remonte en (3,5). En (3,5), la valeur 7 est égale à V(2,5)=7 : l'objet 3 n'a pas été pris, on remonte en (2,5). En (2,5), la valeur 7 diffère de V(1,5)=3 : l'objet 2 A été pris, on retire son poids et l'on va en (1,5−3)=(1,2). En (1,2), la valeur 3 diffère de V(0,2)=0 : l'objet 1 a été pris, on va en (0,0) et l'on s'arrête. Le sac contient donc les objets 1 et 2, de poids total 2+3=5 et de valeur 3+4=7. La règle de la remontée : si la valeur est identique à celle de la ligne du dessus, l'objet n'a pas été pris ; sinon il l'a été.
e)
Le coût en temps est de n×C opérations, une par case, chacune valant un maximum de deux termes. Le coût en mémoire est également de n×C cases. On ne dit pas que le problème est résolu en temps polynomial parce que la taille de l'ENTRÉE n'est pas C mais le nombre de chiffres nécessaires pour écrire C, soit environ log2C bits : le coût nC est donc exponentiel en la taille de l'écriture de la capacité. On parle de coût PSEUDO-POLYNOMIAL, et le problème du sac à dos reste effectivement dans la classe des problèmes réputés difficiles. Pour réduire la mémoire, on remarque que le calcul d'une ligne n'utilise que la précédente : il suffit donc de conserver DEUX lignes, ce qui ramène la mémoire à 2C cases. On peut même n'en garder qu'une seule en parcourant les capacités par valeurs DÉCROISSANTES, mais on perd alors la possibilité de reconstituer le contenu du sac, qui exige toute la table.
Exercice 7 : Parcourir un graphe : profondeur, largeur, et à quoi ça sert
Les deux parcours de graphe ont le même squelette et diffèrent d'un seul mot : l'un utilise une pile, l'autre une file. Cette différence décide de tout ce qu'ils permettent de calculer.
a) Écrivez le squelette commun des deux parcours, en français, en cinq lignes, et dites où intervient le choix de la structure.
b) Pourquoi faut-il marquer un sommet AVANT de l'ajouter à la structure, et non au moment où on le retire ? Décrivez ce qui se passe si l'on s'y prend mal.
c) Donnez le coût d'un parcours en fonction du nombre de sommets et d'arêtes, pour une représentation par listes d'adjacence puis par matrice d'adjacence.
d) Pour chacun des cinq problèmes suivants, dites quel parcours employer et pourquoi : détecter un cycle, trouver le plus court chemin en nombre d'arêtes, compter les composantes connexes, tester si un graphe est biparti, sortir d'un labyrinthe sans se soucier de la longueur.
e) Le parcours en profondeur s'écrit aussi récursivement. Quelle structure remplace alors la pile explicite, et quelle limite cela introduit-il sur un graphe de un million de sommets ?
Voir la correction
Réponses
a)Pile : profondeur ; file : largeur
b)Marquer avant d'ajouter
c)O(n+m) contre O(n2)
d)Largeur pour distances et biparti
e)Récursion : limite de la pile d'appels
a)
Le squelette : créer une structure et y placer le sommet de départ, en le marquant visité. Tant que la structure n'est pas vide : en retirer un sommet ; le traiter ; puis, pour chacun de ses voisins non encore visités, le marquer visité et l'ajouter à la structure. Le choix de la structure intervient à la troisième ligne, celle du retrait : avec une PILE, on retire le dernier ajouté et l'on obtient un parcours en profondeur ; avec une FILE, on retire le premier ajouté et l'on obtient un parcours en largeur. Tout le reste est identique, mot pour mot.
b)
Il faut marquer AVANT l'ajout parce qu'un même sommet peut être voisin de plusieurs sommets traités successivement. Si l'on ne marque qu'au retrait, il sera ajouté une fois par voisin qui le découvre, et se retrouvera plusieurs fois dans la structure. Les conséquences : la structure enfle jusqu'à contenir de l'ordre du nombre d'arêtes au lieu du nombre de sommets, chaque sommet est traité plusieurs fois, et surtout, pour un parcours en largeur, la distance calculée peut être fausse puisqu'un sommet risque d'être atteint par un chemin plus long après l'avoir été par un plus court. Le programme donne encore un résultat, ce qui rend l'erreur difficile à voir : c'est le coût qui explose d'abord.
c)
Avec des LISTES D'ADJACENCE : chaque sommet est retiré une fois de la structure et chacune de ses arêtes est examinée une fois depuis chaque extrémité, soit un coût de l'ordre de n+2m pour un graphe non orienté, c'est-à-dire O(n+m), linéaire. Avec une MATRICE D'ADJACENCE : pour chaque sommet retiré, il faut parcourir sa ligne entière pour trouver ses voisins, soit n cases, d'où un coût de O(n2) quel que soit le nombre d'arêtes. Sur un graphe creux, la différence est considérable : pour n=40000 et m=90000, on passe d'environ 2,2×105 opérations à 1,6×109, soit plus de sept mille fois plus.
d)
Détecter un CYCLE : parcours en profondeur, car un cycle se révèle par une arête qui ramène sur un sommet encore présent dans la descente en cours, information que la pile de récursion fournit naturellement. PLUS COURT CHEMIN en nombre d'arêtes : parcours en largeur, seul à découvrir les sommets par distance croissante. COMPOSANTES CONNEXES : l'un ou l'autre indifféremment, puisqu'on ne s'intéresse qu'à l'ensemble des sommets atteints ; on relance un parcours depuis chaque sommet non visité et l'on compte les relances. Tester si un graphe est BIPARTI : parcours en largeur, car il suffit de colorier chaque sommet selon la parité de sa distance à la source et de vérifier qu'aucune arête ne relie deux sommets de même couleur. Sortir d'un LABYRINTHE sans se soucier de la longueur : parcours en profondeur, qui trouve une sortie plus vite en moyenne et consomme beaucoup moins de mémoire, puisqu'il ne mémorise qu'un chemin au lieu d'un front d'onde entier.
e)
La pile explicite est remplacée par la PILE D'APPELS du langage : chaque appel récursif empile un cadre, exactement comme au chapitre de la récursivité. La limite introduite est la profondeur maximale de récursion, de l'ordre de mille en Python par défaut. Sur un graphe de un million de sommets, un chemin long suffit à la dépasser : un simple chemin de mille sommets provoque une RecursionError, alors que la version itérative avec une pile explicite traiterait un million de sommets sans difficulté, la pile étant alors une structure ordinaire logée dans le tas. C'est la raison pour laquelle tout parcours destiné à de grands graphes s'écrit itérativement, malgré l'élégance de la version récursive.
Exercice 8 : Cinq affirmations à corriger
Chacune des cinq affirmations suivantes est FAUSSE. Dites pourquoi et donnez l'énoncé correct.
1) « La recherche dichotomique fonctionne sur n'importe quel tableau. »
2) « Le tri rapide est plus rapide que le tri fusion, comme son nom l'indique. »
3) « La méthode gloutonne donne toujours le nombre minimal de pièces. »
4) « Le sac à dos se résout en temps polynomial par programmation dynamique. »
5) « Un algorithme en 100 n est toujours préférable à un algorithme en n log n, puisqu'il est d'un ordre de grandeur inférieur. »
Voir la correction
Réponses
1)Dichotomie sur tableau trié
2)Tri rapide sans garantie
3)Glouton non optimal
4)Sac à dos pseudo-polynomial
5)Constantes et tailles réelles
1)
FAUX. Elle exige que le tableau soit TRIÉ : c'est sur cette hypothèse que repose la conclusion « si l'élément du milieu est plus petit, la valeur cherchée est dans la moitié droite ». Sur un tableau non trié, la fonction renvoie un résultat arbitraire sans signaler d'erreur. Énoncé correct : la recherche dichotomique exige un tableau trié, et sa précondition doit être documentée, faute de quoi elle produit des faux négatifs silencieux.
2)
FAUX en général. Les deux coûtent nlog2n en moyenne, mais le tri rapide dégénère en n2 dans son pire cas, notamment sur un tableau déjà trié avec un pivot mal choisi, alors que le tri fusion garantit nlog2n dans TOUS les cas. Énoncé correct : le tri rapide est souvent plus rapide en pratique, grâce à de meilleures constantes et à un usage du cache favorable, mais il n'offre aucune garantie ; le tri fusion en offre une, au prix d'une mémoire supplémentaire linéaire.
3)
FAUX. Avec le système 1, 3, 4 et la somme 6, la méthode gloutonne donne 3 pièces, 4+1+1, alors que l'optimum en demande 2, 3+3. Énoncé correct : la méthode gloutonne n'est optimale que pour certains systèmes de pièces ; elle doit être démontrée pour le système considéré, et la programmation dynamique donne l'optimum quel que soit le système.
4)
FAUX, au sens strict. Le coût nC est linéaire en la CAPACITÉ, mais la capacité s'écrit avec environ log2C chiffres : rapporté à la taille de l'entrée, le coût est exponentiel. Énoncé correct : la programmation dynamique résout le sac à dos en temps PSEUDO-POLYNOMIAL ; le problème reste dans la classe des problèmes réputés difficiles, et l'algorithme devient impraticable dès que la capacité est un grand nombre.
5)
FAUX. On a nlog2n<100n tant que log2n<100, c'est-à-dire pour tout n inférieur à 2100, soit 1,27×1030 : aucun jeu de données réel n'atteint ce seuil, donc l'algorithme en nlog2n est le plus rapide sur toutes les entrées qu'on rencontrera jamais. Énoncé correct : un ordre de grandeur ignore les constantes, donc il ne permet de comparer deux algorithmes que sur les grandes tailles ; sur les tailles réelles, seules une analyse fine et une mesure tranchent.
Exercice 9 : L'algorithme de Dijkstra, pas à pas
Le graphe donne six routeurs et le coût de chaque liaison. On cherche les plus courts chemins depuis A vers tous les autres. Tous les coûts sont positifs, ce qui est l'hypothèse indispensable de la méthode.
Approfondissement, au-delà du programme : le programme de Terminale demande de parcourir un graphe, d'y repérer un cycle et d'y chercher un chemin, et relie le routage aux algorithmes de recherche de chemin ; il ne nomme pas l'algorithme de Dijkstra, qui n'est pas exigible. Il éclaire le protocole OSPF, dont le programme demande seulement de lire les tables et le coût des routes, voir la série sur l'architecture et les réseaux.
a) Décrivez l'algorithme en cinq lignes, en insistant sur le critère de sélection du prochain sommet à traiter.
b) Déroulez l'algorithme depuis A. Donnez, à chaque étape, le sommet sélectionné, sa distance définitive et les distances provisoires mises à jour.
c) Donnez le plus court chemin de A à F et son coût. Combien de chemins de A à F existe-t-il, et pourquoi l'algorithme n'en a-t-il examiné qu'une petite partie ?
d) Énoncez et justifiez la propriété qui garantit qu'une distance sortie de la file est DÉFINITIVE. Où intervient l'hypothèse des poids positifs ?
e) On autorise maintenant des liens de coût négatif. Montrez sur un graphe orienté de trois sommets que l'algorithme peut alors se tromper, et nommez la méthode à employer à la place.
Voir la correction
Réponses
a)Extraire la plus petite distance provisoire
b)Distances 0, 3, 2, 8, 10, 13
c)A, C, B, D, E, F : coût 13
d)Poids positifs : distance définitive
e)Arc négatif : Bellman-Ford
a)
L'algorithme. Un, attribuer la distance 0 à la source et l'infini à tous les autres sommets, et placer tous les sommets dans une file de priorité. Deux, tant que la file n'est pas vide, en extraire le sommet de distance provisoire MINIMALE. Trois, sa distance est alors définitive. Quatre, pour chacun de ses voisins encore dans la file, calculer la distance obtenue en passant par lui, et la remplacer si elle est meilleure que la distance provisoire connue : c'est le relâchement. Cinq, recommencer. Le critère de sélection est le point décisif : on choisit toujours le plus proche non encore traité, jamais le voisin le plus proche du dernier traité.
b)
Étape 1 : on sélectionne A, distance définitive 0. Relâchements : B passe à 4 par A, C passe à 2 par A. Étape 2 : le minimum provisoire est C à 2, on le sélectionne, distance définitive 2. Relâchements : B passe de 4 à 2+1=3 par C, D passe à 2+8=10, E passe à 2+10=12. Étape 3 : le minimum est B à 3, sélectionné, définitif. Relâchement : D passe de 10 à 3+5=8. Étape 4 : le minimum est D à 8, sélectionné, définitif. Relâchements : E passe de 12 à 8+2=10, F passe à 8+6=14. Étape 5 : le minimum est E à 10, sélectionné, définitif. Relâchement : F passe de 14 à 10+3=13. Étape 6 : F à 13, sélectionné, définitif. Distances finales : A 0, B 3, C 2, D 8, E 10, F 13.
c)
Le plus court chemin de A à F est A, C, B, D, E, F, de coût 2+1+5+2+3=13. On le reconstitue en remontant les prédécesseurs enregistrés à chaque relâchement retenu. Le nombre de chemins simples de A à F est bien plus grand : on en compte plusieurs dizaines dans ce petit graphe, et ce nombre croît de façon combinatoire avec la taille. L'algorithme n'en a examiné qu'une petite partie parce qu'il n'énumère aucun chemin : il ne fait que relâcher des arêtes, chacune au plus une fois, et la garantie de la question d lui permet de ne jamais revenir sur une décision.
d)
La propriété : au moment où un sommet est extrait avec la plus petite distance provisoire, cette distance est la distance définitive. Justification : supposons qu'il existe un chemin plus court vers ce sommet. Ce chemin devrait passer par au moins un sommet encore dans la file ; or tous ceux-là ont une distance provisoire supérieure ou égale à celle du sommet extrait, puisque c'est le minimum. Le chemin en question aurait donc déjà une longueur supérieure ou égale avant même d'atteindre sa destination, et il ne pourrait que s'allonger ensuite : il ne peut pas être plus court. C'est là qu'intervient l'hypothèse des POIDS POSITIFS, dans le mot « s'allonger » : si des arêtes pouvaient être négatives, la suite du chemin pourrait au contraire raccourcir le total, et le raisonnement s'effondre.
e)
Prenons trois sommets et trois arcs : A vers B de coût 2, A vers C de coût 1, B vers C de coût −3. L'algorithme sélectionne A à 0 et relâche B à 2 et C à 1. Il sélectionne ensuite C, le plus proche, et déclare sa distance 1 DÉFINITIVE. Il sélectionne enfin B à 2 et découvre que le chemin A, B, C coûte 2−3=−1, moins que 1 : mais C est déjà sorti de la file, et la distance fausse reste. L'erreur tient exactement à l'argument de la question d : la fin du chemin, par l'arc négatif, a raccourci le total au lieu de l'allonger. La méthode à employer en présence de poids négatifs est l'algorithme de Bellman-Ford, qui relâche toutes les arêtes n−1 fois au lieu de figer les sommets un par un, et qui détecte au passage les cycles de poids négatif, cas où la notion même de plus court chemin perd son sens.
Exercice 10 : Problème : organiser un plan de révision
Un élève dispose de 12 heures avant le baccalauréat. Six chapitres sont possibles, chacun avec un temps de révision et un gain de points estimé : A 2 h et 4 points, B 3 h et 5 points, C 4 h et 8 points, D 5 h et 9 points, E 1 h et 2 points, F 6 h et 11 points. Un chapitre se révise entièrement ou pas du tout.
a) Reconnaissez le problème. Quelle méthode gloutonne vient à l'esprit ? Appliquez-la et donnez le résultat.
b) La méthode gloutonne est-elle optimale ici ? Cherchez une meilleure combinaison et concluez.
c) Décrivez la table de programmation dynamique à construire : dimensions, signification d'une case, relation de récurrence.
d) Donnez le nombre de cases à remplir et le coût total. Comparez au nombre de combinaisons qu'une recherche exhaustive devrait examiner.
e) On change une hypothèse : un chapitre peut désormais être révisé partiellement, le gain étant proportionnel au temps passé. Le problème change de nature. Quelle méthode devient optimale, et pourquoi ? Donnez la solution.
Voir la correction
Réponses
a)Sac à dos : glouton à 23 points
b)Optimum 23, glouton chanceux
c)Table 7×13
d)91 cases contre 64 combinaisons
e)Fractionnaire : glouton optimal, 23,17
a)
C'est un problème de SAC À DOS : une capacité, ici 12 heures, des objets indivisibles avec un poids et une valeur, et une valeur totale à maximiser. La méthode gloutonne qui vient à l'esprit consiste à classer les chapitres par RENDEMENT, c'est-à-dire par points par heure, et à prendre dans cet ordre tant que le temps le permet. Rendements : A vaut 2, B environ 1,67, C vaut 2, D vaut 1,8, E vaut 2, F environ 1,83. Ordre décroissant : A, C et E à 2, puis F à 1,83, puis D à 1,8, puis B à 1,67. On prend A, 2 h et 4 points, il reste 10 h ; C, 4 h et 8 points, il reste 6 h ; E, 1 h et 2 points, il reste 5 h ; F demande 6 h, refusé ; D demande 5 h, accepté, 9 points, il reste 0 h. Total : 2+4+1+5=12 heures et 4+8+2+9=23 points.
b)
Ici la méthode gloutonne tombe juste, mais rien ne le garantissait. Vérifions qu'aucune combinaison ne fait mieux : le total des gains de tous les chapitres vaut 39 points pour 21 heures, et l'on ne peut en retenir que 12. En essayant les regroupements à 12 heures exactement, on trouve A, C, E, D à 23 points ; A, C, F à 23 points également ; B, C, E, A à 19 ; D, F à 20 ; C, F à 19 plus E, soit 21. Le maximum est donc 23, atteint par deux combinaisons différentes. La conclusion est que le glouton était optimal sur CETTE instance, ce qui ne prouve rien : il suffirait de remplacer F par un chapitre de 7 heures rapportant 15 points pour qu'il échoue : le glouton prend ce chapitre, de meilleur rendement, puis A et E, pour 21 points, alors que C, E et ce chapitre en rapportent 25 en 12 heures.
c)
La table a 7 lignes, de 0 à 6 chapitres considérés, et 13 colonnes, de 0 à 12 heures. La case de la ligne i et de la colonne t contient le gain maximal atteignable en n'utilisant que les i premiers chapitres et au plus t heures. La récurrence : si le temps du chapitre i dépasse t, la case vaut celle du dessus, V(i−1,t) ; sinon elle vaut le maximum entre V(i−1,t), on ne le révise pas, et gi+V(i−1,t−hi), on le révise. La ligne 0 est nulle partout, et la réponse se lit en bas à droite.
d)
Il y a 7×13=91 cases, chacune demandant une comparaison de deux termes, soit un coût de l'ordre de 91 opérations, disons quelques centaines en comptant tout. Une recherche exhaustive devrait examiner tous les sous-ensembles de 6 chapitres, soit 26=64 combinaisons, ce qui est ici du même ordre. L'écart n'apparaît qu'en grandissant : avec 40 chapitres et 200 heures, la table compte 41×201=8241 cases alors que la recherche exhaustive devrait examiner 240≈1,10×1012 combinaisons, soit environ 133 millions de fois plus. C'est exactement le passage de l'exponentiel au pseudo-polynomial.
e)
Si les chapitres deviennent divisibles, le problème devient le sac à dos FRACTIONNAIRE, et la méthode GLOUTONNE par rendement décroissant devient optimale. La raison est qu'on n'a plus jamais à refuser un objet faute de place : on en prend la fraction qui tient, si bien que la capacité est toujours remplie exactement, et qu'aucune heure n'est occupée par un chapitre de rendement inférieur à un autre encore disponible. Un échange local ne peut donc qu'aggraver le total, ce qui est la démonstration standard d'optimalité d'un glouton. Solution : on prend A, C et E, de rendement 2, soit 2+4+1=7 heures et 4+8+2=14 points ; il reste 5 heures. On prend ensuite F, de rendement 1,83, mais il demande 6 heures : on en prend les cinq sixièmes, ce qui donne 5 heures et 11×5/6≈9,17 points. Le total vaut environ 14+9,17=23,17 points, légèrement supérieur aux 23 du problème indivisible, ce qui est normal : relâcher une contrainte ne peut qu'améliorer l'optimum.
Partie C : les classiques (/50)
Exercice 11 : La distance d'édition
Un correcteur orthographique propose les mots du dictionnaire les plus proches du mot mal tapé. La distance d'édition entre deux mots est le nombre minimal d'opérations pour passer de l'un à l'autre, chaque opération coûtant 1 : insérer une lettre, supprimer une lettre, ou remplacer une lettre par une autre.
On note D(i,j) la distance entre les i premières lettres du premier mot et les j premières lettres du second. On compare CHAT et CHIEN.
a) Que valent D(0,j) et D(i,0) ? Justifiez.
b) Écrivez la relation de récurrence qui donne D(i,j) à partir de D(i−1,j), D(i,j−1) et D(i−1,j−1), en interprétant chacun des trois cas.
c) Remplissez le tableau des D(i,j) pour CHAT, en lignes, et CHIEN, en colonnes. Donnez la dernière ligne.
d) Quelle est la distance d'édition entre CHAT et CHIEN ? Donnez une suite d'opérations qui la réalise.
e) Quel est le coût du calcul pour deux mots de longueurs m et n ? Pourquoi une fonction récursive directe, sans mémoire, serait-elle inutilisable ? Un correcteur compare un mot à 300000 mots de longueur moyenne 8 : estimez le nombre de cases calculées.
Voir la correction
Réponses
a)D(0,j)=j, D(i,0)=i
b)Supprimer, insérer, remplacer
c)Dernière ligne 4, 3, 2, 2, 2, 3
d)Distance 3
e)O(mn) ; ≈2,4×107 cases
a)
D(0,j)=j : pour passer du mot vide aux j premières lettres du second, il faut exactement j insertions. De même D(i,0)=i : passer des i premières lettres au mot vide demande i suppressions. Ce sont les cas de base, la première ligne et la première colonne du tableau.
b)
D(i,j)=min(D(i−1,j)+1,D(i,j−1)+1,D(i−1,j−1)+δ), où δ vaut 0 si la i-ième lettre du premier mot est égale à la j-ième du second, et 1 sinon. Le premier terme correspond à SUPPRIMER la dernière lettre du premier mot, le deuxième à INSÉRER la dernière lettre du second, le troisième à faire correspondre les deux dernières lettres, gratuitement si elles sont égales, par un REMPLACEMENT sinon. Une solution optimale se termine forcément par l'un de ces trois cas, et l'on garde le meilleur : c'est le principe d'optimalité de l'exercice 5.
c)
Première ligne, mot vide : 0, 1, 2, 3, 4, 5. Ligne C : 1, 0, 1, 2, 3, 4. Ligne H : 2, 1, 0, 1, 2, 3. Ligne A : 3, 2, 1, 1, 2, 3. Ligne T : 4, 3, 2, 2, 2, 3. Par exemple, la case de la ligne A et de la colonne I vaut min(0+1,1+1,0+1)=1 : on remplace A par I après avoir fait correspondre CH et CH.
d)
La distance est la dernière case, D(4,5)=3. Une suite d'opérations : garder C et H ; remplacer A par I ; remplacer T par E ; insérer N. On obtient CHIEN en trois opérations, et le tableau prouve qu'on ne peut pas faire moins.
e)
Le tableau compte (m+1)(n+1) cases, chacune calculée en temps constant : coût O(mn). Une fonction récursive directe relancerait trois appels à chaque étape et recalculerait les mêmes sous-problèmes un nombre exponentiel de fois, exactement comme Fibonacci naïf. Pour le correcteur : environ 9×9=81 cases par comparaison, donc 300000×81≈2,4×107 cases, soit une fraction de seconde. Les vrais correcteurs réduisent encore ce nombre en ne comparant que des mots de longueur voisine.
Exercice 12 : La multiplication de Karatsuba
Multiplier deux nombres de n chiffres à la main demande de multiplier chaque chiffre de l'un par chaque chiffre de l'autre. En 1960, Karatsuba a trouvé mieux. On écrit x=10x1+x0 et y=10y1+y0, puis on calcule a=x1y1, d=x0y0 et e=(x1+x0)(y1+y0). On a alors xy=100a+10(e−a−d)+d.
Approfondissement, au-delà du programme : la multiplication de Karatsuba est un exemple de « diviser pour régner » que le programme ne cite pas, et l'exposant log23 de la question e) sort de l'usage du logarithme que le programme autorise, « simple outil de comptage ». Les questions a) à c) se traitent avec les outils du programme.
a) Combien de multiplications de deux chiffres la méthode de l'école fait-elle pour deux nombres de 2 chiffres ? de n chiffres ?
b) Calculez 47×83 par la méthode de Karatsuba : donnez a, d, e, puis e−a−d et le produit.
c) Démontrez la formule xy=100a+10(e−a−d)+d.
d) Pour des nombres de n chiffres, on coupe chaque nombre en deux moitiés de n/2 chiffres et l'on applique la même idée récursivement. Écrivez la récurrence du nombre M(n) de multiplications élémentaires et résolvez-la pour n=2k. Calculez M(1024) et comparez à la méthode de l'école.
e) Montrez que M(n)=nlog23 et donnez l'exposant au millième. Pourquoi les bibliothèques de calcul utilisent-elles tout de même la méthode de l'école pour les petits nombres ?
Voir la correction
Réponses
a)École : n2 multiplications
b)47×83=3901 en 3 produits
c)e−a−d : terme du milieu
d)M(1024)=310=59049
e)nlog23≈n1,585
a)
Chacun des 2 chiffres du premier nombre est multiplié par chacun des 2 chiffres du second : 4 multiplications. Pour n chiffres : n2 multiplications, un coût quadratique.
b)
x1=4, x0=7, y1=8, y0=3. a=4×8=32, d=7×3=21, e=(4+7)(8+3)=11×11=121. Puis e−a−d=121−32−21=68. Le produit vaut 100×32+10×68+21=3200+680+21=3901, et l'on vérifie que 47×83=3901. Trois multiplications au lieu de quatre.
c)
xy=(10x1+x0)(10y1+y0)=100x1y1+10(x1y0+x0y1)+x0y0. Or e=(x1+x0)(y1+y0)=x1y1+x1y0+x0y1+x0y0=a+(x1y0+x0y1)+d, donc x1y0+x0y1=e−a−d. En remplaçant, xy=100a+10(e−a−d)+d. Toute l'astuce est là : le terme du milieu, qui semblait exiger deux produits, s'obtient à partir d'un seul produit supplémentaire et de soustractions, beaucoup moins coûteuses.
d)
Chaque multiplication de nombres de n chiffres se ramène à 3 multiplications de nombres de n/2 chiffres : M(n)=3M(n/2), avec M(1)=1. Pour n=2k, on déroule : M(2k)=3M(2k−1)=⋯=3k. Pour n=1024=210 : M=310=59049, contre 10242=1048576 pour la méthode de l'école, soit environ 18 fois moins. On a négligé les additions, dont le coût est linéaire à chaque niveau.
e)
3k=(2log23)k=(2k)log23=nlog23, avec log23≈1,585. Le coût passe donc de n2 à environ n1,585, un gain qui grandit sans limite avec n. Pour les petits nombres, la méthode récursive paie des additions, des soustractions et des découpages supplémentaires, dont le coût constant dépasse le gain : les bibliothèques basculent donc sur la méthode de l'école en dessous d'un seuil de quelques dizaines de chiffres, comme le tri fusion bascule sur le tri par insertion pour les petits tableaux.
Exercice 13 : Colorier un graphe pour un calendrier d'examens
Un lycée doit placer six examens, mathématiques M, physique P, SVT S, histoire H, anglais A et philosophie F, sur des demi-journées. Deux examens qui ont des candidats communs ne peuvent pas avoir lieu en même temps. Les paires en conflit sont : M et P, M et S, P et S, P et A, S et H, H et A, A et F, H et F.
On modélise par un graphe dont les sommets sont les examens et les arêtes les conflits. Donner un créneau à chaque examen revient à COLORIER les sommets de sorte que deux sommets voisins aient des couleurs différentes. L'algorithme glouton traite les sommets dans un ordre donné et attribue à chacun la plus petite couleur, 1, 2, 3 et ainsi de suite, non utilisée par ses voisins déjà coloriés.
Approfondissement, au-delà du programme : la coloration d'un graphe n'est pas au programme de Terminale, dont les algorithmes sur les graphes sont les parcours, la détection d'un cycle et la recherche d'un chemin ; les algorithmes gloutons du programme sont ceux de Première, sac à dos et rendu de monnaie. L'exercice reste un bon entraînement à la modélisation par un graphe, qui, elle, est au programme.
a) Combien le graphe a-t-il d'arêtes ? Donnez le degré de chaque sommet.
b) Appliquez l'algorithme glouton dans l'ordre M, P, S, H, A, F. Donnez la couleur de chaque examen et le nombre de créneaux utilisés.
c) Montrez qu'il est impossible de faire mieux sur ce graphe.
d) Appliquez l'algorithme glouton dans l'ordre M, P, S, A, F, H. Combien de créneaux obtient-on ? Que conclure sur l'algorithme ?
e) Montrez que l'algorithme glouton n'utilise jamais plus de Δ+1 couleurs, où Δ est le degré maximal. Que vaut cette borne ici ? Que sait-on de la recherche du nombre minimal de couleurs dans un graphe quelconque ?
Voir la correction
Réponses
a)8 arêtes, degrés 2 à 3
b)Ordre M, P, S, H, A, F : 3 créneaux
c)Triangle : 3 couleurs nécessaires
d)Autre ordre : 4 créneaux
e)Au plus Δ+1=4 couleurs
a)
Le graphe a 8 arêtes, une par paire en conflit. Degrés : M 2, P 3, S 3, H 3, A 3, F 2. On vérifie que la somme des degrés, 16, vaut bien deux fois le nombre d'arêtes.
b)
M reçoit 1. P, voisin de M, reçoit 2. S, voisin de M et P, reçoit 3. H, dont seul le voisin S est déjà colorié, reçoit 1. A, voisin de P de couleur 2 et de H de couleur 1, reçoit 3. F, voisin de A de couleur 3 et de H de couleur 1, reçoit 2. On utilise 3 créneaux : M et H ensemble, P et F ensemble, S et A ensemble.
c)
M, P et S sont deux à deux en conflit : ils forment un TRIANGLE, et leurs trois couleurs doivent être différentes. Il faut donc au moins 3 couleurs, et la coloration de la question b en utilise 3 : elle est optimale.
d)
M reçoit 1, P reçoit 2, S reçoit 3. A, dont seul P est colorié parmi ses voisins, reçoit 1. F, voisin de A, reçoit 2. H est voisin de S de couleur 3, de A de couleur 1 et de F de couleur 2 : il reçoit 4. On obtient 4 créneaux. L'algorithme glouton n'est donc PAS optimal : son résultat dépend de l'ordre de traitement des sommets, et un mauvais ordre peut coûter une couleur de plus sur le même graphe.
e)
Quand l'algorithme traite un sommet, celui-ci a au plus Δ voisins, donc au plus Δ couleurs sont interdites : parmi les couleurs 1 à Δ+1, l'une au moins est libre. Ici Δ=3, donc au plus 4 couleurs, ce qui est exactement ce qu'a produit le mauvais ordre. Déterminer le nombre MINIMAL de couleurs d'un graphe quelconque est un problème difficile : on ne connaît aucun algorithme efficace dans tous les cas, et l'on se contente en pratique d'heuristiques gloutonnes, par exemple traiter d'abord les sommets de plus fort degré.
Exercice 14 : La pyramide de nombres
On descend une pyramide de nombres depuis le sommet jusqu'à la base, en passant à chaque étage à l'un des deux nombres situés juste en dessous, à gauche ou à droite. On veut maximiser la somme des nombres traversés.
La pyramide est rangée dans la liste t = [[4], [8, 5], [1, 3, 9], [6, 2, 7, 8]] : t[i][j] est le j-ième nombre de l'étage i, et depuis t[i][j] on peut descendre en t[i+1][j] ou en t[i+1][j+1].
a) Combien de chemins différents mènent du sommet à la base de cette pyramide de 4 étages ? D'une pyramide de n étages ? Estimez ce nombre pour 100 étages.
b) La méthode gloutonne descend à chaque étage vers le plus grand des deux nombres. Quel chemin suit-elle, et quelle somme obtient-elle ?
c) On note S(i,j) la meilleure somme obtenue en partant de t[i][j]. Écrivez la relation de récurrence et le cas de base, puis calculez S en remontant depuis la base, étage par étage.
d) Quelle est la somme maximale ? Reconstituez le chemin optimal et comparez au résultat glouton.
e) Combien de cases la méthode de la question c calcule-t-elle pour 100 étages ? Comparez à l'exploration de tous les chemins.
Voir la correction
Réponses
a)2n−1 chemins
b)Glouton : 22
c)S(i,j)=t[i][j]+max
d)Optimum 26 par 4, 5, 9, 8
e)5050 cases contre 6,3×1029 chemins
a)
À chacun des 3 passages d'un étage au suivant, on a 2 choix : 23=8 chemins. Pour n étages, 2n−1 chemins. Pour 100 étages : 299≈6,3×1029 chemins, bien au-delà de ce qu'aucun ordinateur pourra jamais parcourir.
b)
Depuis 4, on choisit 8 plutôt que 5 ; depuis 8, on choisit 3 plutôt que 1 ; depuis 3, on choisit 7 plutôt que 2. Chemin 4, 8, 3, 7, de somme 22.
c)
Cas de base, la dernière ligne : S(3,j)=t[3][j]. Relation : S(i,j)=t[i][j]+max(S(i+1,j),S(i+1,j+1)). Étage 3 : 6, 2, 7, 8. Étage 2 : 1+max(6,2)=7 ; 3+max(2,7)=10 ; 9+max(7,8)=17. Étage 1 : 8+max(7,10)=18 ; 5+max(10,17)=22. Étage 0 : 4+max(18,22)=26.
d)
La somme maximale est S(0,0)=26. On reconstitue le chemin en suivant à chaque étage le fils qui a réalisé le maximum : de 4, on va vers 5, dont la valeur S vaut 22 contre 18 ; de 5, vers 9, avec 17 contre 10 ; de 9, vers 8, avec 8 contre 7. Chemin 4, 5, 9, 8, de somme 26, contre 22 pour le glouton. Le glouton s'est laissé attirer par le 8 du deuxième étage, qui fermait l'accès au 9 et au 8 ; la programmation dynamique, en partant du bas, voit d'abord ce que chaque choix rend possible.
e)
Chaque case de la pyramide est calculée une fois : 1+2+⋯+100=2100×101=5050 cases, chacune par un maximum de deux termes. À comparer aux 6,3×1029 chemins : la programmation dynamique exploite le fait que les chemins partagent leurs fins, et qu'il suffit de connaître la meilleure fin depuis chaque case, au lieu de toutes.
Exercice 15 : Peut-on trier plus vite que n log n ?
Un tri par comparaison n'obtient d'information sur les données qu'en comparant deux éléments. On peut représenter ses exécutions par un ARBRE DE DÉCISION : chaque noeud est une comparaison, chaque branche une réponse, et chaque feuille un ordre final. Pour n éléments distincts, l'arbre doit avoir au moins une feuille par ordre possible, soit n! feuilles.
Approfondissement, au-delà du programme : le programme demande de montrer l'intérêt du passage d'un coût en n2 à un coût en nlog2n, pas de démontrer qu'on ne peut pas faire mieux. La borne inférieure par l'arbre de décision et le tri par dénombrement sont donc des approfondissements.
a) Un arbre binaire de hauteur h a au plus 2h feuilles. Déduisez-en le nombre minimal de comparaisons, dans le pire cas, d'un tri par comparaison de n éléments. Calculez ce minimum pour n=3 et pour n=10.
b) Montrez qu'un tri par insertion trie toujours 3 éléments en au plus 3 comparaisons. Que conclure pour n=3 ?
c) Pour n=106, on admet que log2(n!)≈1,85×107. Comparez à nlog2n. Que conclure sur le tri fusion ?
d) Un lycée doit trier 1000000 de copies par note entière de 0 à 20. Le tri par dénombrement compte, dans un tableau de 21 cases, combien de copies ont chaque note, puis réécrit les notes dans l'ordre. Donnez son coût, et expliquez pourquoi il ne contredit pas la question a.
e) Appliquez le tri par dénombrement à la liste [3, 1, 4, 1, 5, 2, 3, 1], de valeurs entre 0 et 5 : donnez le tableau des effectifs et la liste triée. Dans quel cas ce tri devient-il inutilisable ?
Voir la correction
Réponses
a)h≥log2(n!) : 3 et 22
b)Insertion optimale pour 3 éléments
c)Tri fusion optimal
d)Dénombrement linéaire, sans comparaison
e)Effectifs 0, 3, 1, 2, 1, 1
a)
Un tri qui fait au plus h comparaisons correspond à un arbre de hauteur au plus h, donc à au plus 2h feuilles. Il en faut au moins n!, donc 2h≥n!, soit h≥log2(n!). Pour n=3 : 3!=6 et log26≈2,58, donc au moins 3 comparaisons dans le pire cas. Pour n=10 : 10!=3628800 et log2(10!)≈21,8, donc au moins 22 comparaisons. Aucun algorithme, aussi astucieux soit-il, ne peut descendre en dessous.
b)
Le tri par insertion place le deuxième élément en 1 comparaison, puis le troisième en au plus 2 comparaisons, face au plus grand puis au plus petit des deux premiers : au plus 1+2=3 comparaisons. Il atteint la borne de la question a : pour 3 éléments, il est OPTIMAL dans le pire cas.
c)
nlog2n=106×log2(106)≈106×19,93≈1,99×107, du même ordre que la borne 1,85×107. Plus généralement, log2(n!) est de l'ordre de nlog2n : aucun tri par comparaison ne fait mieux qu'un nombre de comparaisons de cet ordre, et le tri fusion, qui l'atteint dans tous les cas, est optimal à un facteur constant près.
d)
Un premier parcours des copies remplit les 21 compteurs, un second passage réécrit les notes : environ 1000000+21 opérations, un coût LINÉAIRE, vingt fois moins que nlog2n≈2×107. Il ne contredit pas la borne, parce qu'il ne COMPARE jamais deux copies : il utilise la valeur de la note comme indice de tableau, ce qui suppose que les valeurs sont des entiers dans un petit intervalle connu. La borne ne concerne que les tris qui n'apprennent rien d'autre que le résultat de comparaisons.
e)
Effectifs des valeurs 0 à 5 : 0, 3, 1, 2, 1, 1. On réécrit trois fois 1, une fois 2, deux fois 3, une fois 4 et une fois 5 : [1, 1, 1, 2, 3, 3, 4, 5]. Le tri devient inutilisable quand l'intervalle des valeurs est immense devant le nombre d'éléments : trier mille nombres compris entre 0 et un milliard exigerait un tableau d'un milliard de compteurs, pour mille valeurs seulement.
Il manque quelque chose dans cette série ?Un type d'exercice que ton prof donne, un énoncé qui te bloque, une erreur repérée : dis-le-moi, je lis chaque demande.
L'envoi passe par ton espace gratuit : c'est ce qui me permet de te répondre et de te prévenir quand l'exercice est ajouté. Un courriel suffit, pas de mot de passe. Crée ton espace.
Pas envie de créer un compte ? Écris-moi directement.
Contactez-moi pour une première séance. C'est le chapitre le plus rentable de la Terminale NSI : les mêmes quatre schémas de raisonnement reviennent d'un sujet à l'autre, et une fois qu'on les reconnaît, les questions se ressemblent toutes.