Notes
Notes
données
notes de cours
Michael Blondin
2 décembre 2019
Ce document
Si vous trouvez des coquilles ou des erreurs dans le document, veuillez s.v.p.
me les indiquer par courriel à [Link]@[Link].
Légende
Observation.
Remarque.
Les passages compris dans une région comme celle-ci correspondent à des
remarques jugées intéressantes mais qui dérogent légèrement du contenu
principal.
Les passages compris dans une région rectangulaire colorée sans bordure
comme celle-ci correspondent à du contenu qui ne sera pas présenté en classe,
mais qui peut aider à une compréhension plus approfondie. La plupart de ces
passages contiennent des preuves ou des propositions techniques.
Les exercices marqués par « ⋆ » sont considérés plus avancés que les autres.
Table des matières
I Fondements 1
i
TABLE DES MATIÈRES ii
1.9 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2 Tri 34
2.1 Approche générique . . . . . . . . . . . . . . . . . . . . . . . . 34
2.2 Tri par insertion . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.3 Tri par monceau . . . . . . . . . . . . . . . . . . . . . . . . . . 39
2.4 Tri par fusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
2.5 Tri rapide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
2.6 Propriétés intéressantes . . . . . . . . . . . . . . . . . . . . . . 43
2.7 Tri sans comparaison . . . . . . . . . . . . . . . . . . . . . . . . 43
2.8 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3 Graphes 46
3.1 Graphes non dirigés . . . . . . . . . . . . . . . . . . . . . . . . 46
3.2 Graphes dirigés . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.3 Chemins et cycles . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.4 Sous-graphes et connexité . . . . . . . . . . . . . . . . . . . . . 48
3.5 Représentation . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
3.5.1 Matrice d’adjacence . . . . . . . . . . . . . . . . . . . . 49
3.5.2 Liste d’adjacence . . . . . . . . . . . . . . . . . . . . . . 49
3.5.3 Complexité des représentations . . . . . . . . . . . . . . 50
3.6 Accessibilité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.6.1 Parcours en profondeur . . . . . . . . . . . . . . . . . . 51
3.6.2 Parcours en largeur . . . . . . . . . . . . . . . . . . . . . 51
3.7 Calcul de plus court chemin . . . . . . . . . . . . . . . . . . . . 52
3.8 Ordre topologique et détection de cycle . . . . . . . . . . . . . 54
3.9 Arbres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.10 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
II Paradigmes 58
4 Algorithmes gloutons 59
4.1 Arbres couvrants minimaux . . . . . . . . . . . . . . . . . . . . 59
4.1.1 Algorithme de Prim–Jarník . . . . . . . . . . . . . . . . 59
4.1.2 Algorithme de Kruskal . . . . . . . . . . . . . . . . . . . 61
4.2 Approche générique . . . . . . . . . . . . . . . . . . . . . . . . 65
4.3 Problème du sac à dos . . . . . . . . . . . . . . . . . . . . . . . 66
4.3.1 Approche gloutonne . . . . . . . . . . . . . . . . . . . . 67
4.3.2 Variante fractionnelle . . . . . . . . . . . . . . . . . . . 68
4.3.3 Approximation . . . . . . . . . . . . . . . . . . . . . . . 69
4.4 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
5 Algorithmes récursifs et
approche diviser-pour-régner 73
5.1 Tours de Hanoï . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
TABLE DES MATIÈRES iii
6 Force brute 94
6.1 Problème des n dames . . . . . . . . . . . . . . . . . . . . . . . 94
6.2 Problème du sac à dos . . . . . . . . . . . . . . . . . . . . . . . 97
6.3 Problème du retour de monnaie . . . . . . . . . . . . . . . . . . 98
6.4 Satisfaction de formules de logique propositionnelle . . . . . . . 100
6.5 Programmation linéaire entière . . . . . . . . . . . . . . . . . . 101
6.6 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Bibliographie 157
Index 158
Fondements
1
0
Rappel de notions mathématiques
0.1.1 Ensembles
Rappelons qu’un ensemble est une collection (finie ou infinie) d’éléments non
ordonnés et sans répétitions. Par exemple, {2, 3, 7, 11, 13} est l’ensemble des
cinq premiers nombres premiers, ∅ est l’ensemble vide, {aa, ab, ba, bb} est l’en-
semble des mots de taille deux formés des lettres a et b, {0, 2, 4, 6, 8, . . .} est
l’ensemble des nombres pairs non négatifs.
Nous utiliserons souvent des définitions en compréhension. Par exemple,
l’ensemble des nombres pairs non négatifs peut être écrit de la façon suivante:
{2n : n ∈ N}.
La taille d’un ensemble fini X est dénoté par |X|, par ex. |{a, b, c}| = 3 et
|∅| = 0. Nous écrivons x ∈ X et x ̸∈ X afin de dénoter que x appartient et
n’appartient pas à X, respectivement. Nous écrivons X ⊆ Y afin de dénoter
que tous les éléments de X appartiennent à Y , et nous écrivons X ⊂ Y lorsque
X ⊆ Y et X ̸= Y . Lorsque X ⊆ E où E est considéré comme un univers, nous
écrivons X afin de dénoter le complément
déf
X = {e ∈ E : e ̸∈ X}.
2
CHAPITRE 0. RAPPEL DE NOTIONS MATHÉMATIQUES 3
le produit cartésien:
déf
X ∪ Y = {x : x ∈ X ∨ x ∈ Y },
déf
X ∩ Y = {x : x ∈ X ∧ x ∈ Y },
déf
X \ Y = {x : x ∈ X ∧ y ̸∈ Y },
déf
X × Y = {(x, y) : x ∈ X, y ∈ Y }.
Par exemple, P({a, b}) = {∅, {a}, {b}, {a, b}} et P(∅) = {∅}.
0.1.2 Nombres
Ensembles de nombres. Nous utiliserons certains ensembles standards de
nombres dont les nombres naturels, entiers, rationnels et réels:
N = {0, 1, 2, . . .},
Z = N ∪ {−n : n ∈ N},
Q = {a/b : a, b ∈ Z, b ̸= 0},
√
R = Q ∪ {π, 2, . . .}.
Nous restreindrons parfois ces ensembles, par ex. R>0 dénote l’ensemble des
déf
nombres réels positifs. L’intervalle des entiers de a à b est dénoté [a, b] = {a,
déf
a + 1, . . . , b}. Le cas particulier où a = 1 s’écrit [b] = [1, b].
logb (xy) = logb (x) + logb (y), logb (x/y) = logb (x) − logb (y),
logb (x)
logb (xy ) = y · logb (x), loga (x) = ,
logb (a)
logb (1) = 0, x < y =⇒ logb (x) < logb (y).
bx
bx · by = bx+y , = bx−y , (bx )y = bx·y .
by
CHAPITRE 0. RAPPEL DE NOTIONS MATHÉMATIQUES 4
0.1.3 Séquences
Une séquence est une suite d’éléments. Contrairement aux ensembles, les élé-
ments d’une séquence sont ordonnés (par un indice) et peuvent se répéter.
Nous décrivons les séquences à l’aide de crochets (plutôt que d’accolades pour
les ensembles). Par exemple, s = [k, a, y, a, k] représente la chaîne de caractères
« kayak » et f = [1, 1, 2, 3, 5, 8, . . .] est la séquence de Fibonacci. Nous dénotons
le ième élément d’une séquence t par t[i] (en débutant par 1), par ex. s[1] = k
et f [3] = 2. La taille d’une séquence finie t est dénotée par |t|, par ex. |s| = 5.
Nous écrivons s[i : j] afin de dénoter la sous-séquence:
déf
s[i : j] = [s[i], s[i + 1], . . . , s[j]].
déf
Par convention, s[i : j] = [ ] lorsque i > j. Nous écrivons s + t afin de dénoter
la séquence obtenue en ajoutant t à la suite de s.
0.1.4 Relations
Une relation (binaire) de deux ensembles X et Y est un ensemble R ⊆ X × Y .
Chaque paire (x, y) ∈ R indique que x est en relation avec y. Afin d’alléger
la notation, nous écrivons R(x, y) afin de dénoter que (x, y) ∈ R. Nous disons
qu’une relation R ⊆ X × Y est:
— surjective si ∀y ∈ Y ∃x ∈ X R(x, y);
— injective si ∀x, x′ ∈ X ∀y ∈ Y [R(x, y) ∧ R(x′ , y)] → (x = x′ );
— bijective si elle est surjective et injective.
Nous disons qu’une relation R ⊆ X × X est:
— réflexive si ∀x ∈ X R(x, x);
— symmétrique si ∀x, y ∈ X R(x, y) → R(y, x);
— transitive si ∀x, y, z ∈ X [R(x, y) ∧ R(y, z)] → R(x, z);
— une relation d’équivalence si elle est réflexive, symmétrique et transitive.
Une fonction est une relation f ⊆ X × Y qui met chaque élément de X en
relation avec exactement un élément de Y ; autrement dit, qui satisfait ∀x ∈
X ∃y ∈ Y f (x, y) et ∀x ∈ X ∀y, y ′ ∈ Y [f (x, y) ∧ f (x, y ′ )] → (y = y ′ ). Afin
d’alléger la notation, nous écrivons f (x) = y afin de dénoter que f (x, y).
0.1.5 Combinatoire
déf déf
La factorielle d’un entier n ∈ N est définie par n! = 1 · 2 · · · n avec 0! = 1
par convention. Par exemple, 5! = 120 et 6! = 720. Il y a n! séquences façons
CHAPITRE 0. RAPPEL DE NOTIONS MATHÉMATIQUES 5
[a, b, c], [a, c, b], [b, a, c], [b, c, a], [c, a, b], [c, b, a].
{ , }, { , }, { , }, { , }, { , }, { , }.
1 ∑∑
n n
= s[j]
n i=1 j=1
1 ∑n
= ·n· s[j] (car aucun terme ne dépend de i)
n j=1
∑
n
= s[j].
j=1
1 = 1 = 12 ,
1+3 = 4 = 22 ,
1+3+5 = 9 = 32 ,
1 + 3 + 5 + 7 = 16 = 42 .
Cherchons à démontrer que cela est vrai pour tout n ∈ N≥1 . Considérons le cas
où n = 4. Nous pouvons visualiser graphiquement que 1 + 3 + 5 + 7 = 42 :
n+1
n
n n+1
∑
n+1
sn+1 = (2i − 1) (par définition de sn+1 )
i=1
∑n
= (2i − 1) + (2(n + 1) − 1)
i=1
∑n
= (2i − 1) + (2n + 1)
i=1
= sn + (2n + 1) (par définition de sn )
2
= n + (2n + 1) (par hypothèse d’induction)
= (n + 1)(n + 1)
= (n + 1)2 .
Observation.
La proposition précédente donne lieu à un algorithme qui calcule le carré
d’un entier naturel.
Dans chaque cas, toutes les cases sont pavées à l’exception d’une seule case (co-
lorée en noire). Cela porte à croire qu’il est toujours possible de paver la totalité
d’une grille 2n × 2n à l’exception d’une seule case de notre choix. Démontrons
cette conjecture:
Proposition 4. Pour tout n ∈ N et pour tous i, j ∈ [2n ], il est possible de
paver la totalité d’une grille 2n × 2n à l’exception de la case (i, j).
Démonstration. Nous prouvons la proposition par induction sur n.
Cas de base (n = 0). La grille possède une seule case qui est forcément la case
(i, j). Le pavage ne nécessite donc aucune tuile.
Étape d’induction. Soit n ≥ 0. Considérons une grille de taille 2n+1 × 2n+1 et
supposons que toute grille de taille 2n × 2n soit pavable à l’exception d’une
case arbitraire. Découpons notre grille en quatre sous-grilles. Remarquons que
chacune d’elles est de taille 2n × 2n . La case (i, j) se trouve dans l’une des
quatre sous-grilles. Par exemple, la case (i, j) se trouve ici (colorée en noire)
dans la sous-grille supérieure gauche:
Nous retranchons une case à chacune des trois autres sous-grilles de façon à
former un « L ». Par exemple, dans le cas précédent, nous retranchons ces trois
cases hachurées en rouge:
CHAPITRE 0. RAPPEL DE NOTIONS MATHÉMATIQUES 10
Nous pavons ces trois cases retranchées par une tuile. Remarquons qu’exacte-
ment une case a été retranchée à chaque sous-grille. De plus, chaque sous-grille
est de taille 2n × 2n . Ainsi, par hypothèse d’induction, chaque sous-grille peut
être pavée, ce qui donne un pavage de la grille entière.
Observation.
La preuve précédente est constructive: elle ne montre pas seulement
l’existence d’un pavage, elle explique également comment en obtenir
un. En effet, l’étape d’induction effectue essentiellement quatre appels
récursifs d’un algorithme, dont les paramètres sont (n, i, j), jusqu’à son
cas de base qui est n = 0. Par exemple, une implémentation simple en
Python pave la grille suivante de taille 64 × 64, avec (i, j) = (2, 2), en
quelques millisecondes:
Observation.
Proposition 5. Bob possède une stratégie gagnante au jeu de Nim pour tout
n ∈ N≥1 , où n désigne le nombre d’allumettes initialement dans chaque pile.
Démonstration. Nous montrons que Bob gagne s’il retire toujours la même
quantité d’allumettes qu’Alice.
Cas de base (n = 1). Le seul coup possible pour Alice consiste à retirer une
allumette d’une pile. Bob retire donc la dernière allumette et gagne.
Étape d’induction. Soit n ≥ 1. Supposons que la stratégie fonctionne pour toute
valeur initiale m ∈ [1, n]. Considérons l’instance du jeu où il y a initialement
n + 1 allumettes dans chaque pile. Si Alice retire n + 1 allumettes d’une pile,
alors Bob retire les n + 1 allumettes de l’autre pile et gagne. Sinon, si elle retire
k ∈ [1, n] allumettes d’une pile, alors Bob retire k allumettes de l’autre pile. Il
déf
reste donc m = n+1−k allumettes dans chaque pile. Observons que m ∈ [1, n].
Ainsi, par hypothèse d’induction, Bob possède peut gagner le jeu.
CHAPITRE 0. RAPPEL DE NOTIONS MATHÉMATIQUES 12
0.3 Exercices
0.1) Quelle est la taille de l’ensemble {{a, b}, {∅, {1, 2}}, {∅, {∅, ∅}}}?
déf déf
0.2) Soient les ensembles X = {1, 5, 6, a, 9, 23, c} et Y = {−23, 3, 5, 9, a}.
Donnez le contenu des ensembles X ∪ Y , X ∩ Y , X \ Y et Y \ X.
0.4) Montrez que la somme d’un nombre pair et d’un nombre impair est for-
cément impaire.
0.5) Montrez que si une séquence de nombre réels contient au moins deux
éléments distincts, alors elle contient au moins un élément strictement
inférieur à sa moyenne.
√
0.6) ⋆ Montrez que 2 ̸∈ Q par contradiction.
0.12) Montrez que la somme des n premiers nombres pairs non négatifs donne
n2 −n. Tentez de le prouver d’au moins deux manières différentes: (1) par
induction sur n; (2) directement en utilisant la proposition 3.
∑n n(n+1)
0.13) Montrez que i=1 i= 2 pour tout n ∈ N>0 .
0.16) Montrez que 1$ et 3$ sont les seuls montants entiers qui ne peuvent pas
être payés à l’aide de pièces de 2$ et de billets de 5$.
1
Analyse des algorithmes
Autrement dit, t(n) indique le plus grand nombre d’opérations exécutées parmi
toutes les entrées de taille n. Nous considérerons parfois également le temps
d’exécution dans le meilleur cas, défini par:
déf
tmin (n) = min{f (x) : entrée x de taille n}.
Par défaut, le « temps d’exécution » fera référence au pire cas lorsque nous ne
spécifions pas de quel cas il s’agit.
13
CHAPITRE 1. ANALYSE DES ALGORITHMES 14
1.2.1 Notation O
Nous formalisons les notions de la section précédente en introduisant une no-
tation qui permet de comparer des fonctions asymptotiquement, c’est-à-dire
lorsque leur entrée tend vers l’infini. Nous nous concentrons sur les fonctions à
un seul paramètre. Plus précisément, nous considérons les fonctions de N vers
R qui sont éventuellement positives, c’est-à-dire:
déf
F = {f : N → R : ∃m ∈ N ∀n ≥ m f (n) > 0} .
Exemples.
déf
Considérons d’autres exemples de fonctions. Posons f (n) = 5n2 +400n+
9. La fonction f est supérieure à n2 et peut sembler croître beaucoup
plus rapidement que n2 :
5n2 + 400n + 9
4 000 000
2 000 000
n2
n
500 1 000
n2 = (n − 1)n + n
≤ n! + n (car n ≥ 2 et n! = 1 · · · (n − 1)n)
≤ n! + n!
= 2n!.
′ ′′
max(n0 , n0 ) et comme seuil. Pour tout n ≥ n0 , nous avons:
f (n) ≤ c′ · g(n) (car n ≥ n0 ≥ n′0 )
′ ′′
≤ c · c · h(n) (car n ≥ n0 ≥ n′′0 )
= c · h(n) (par définition de c).
Rappelons que nous avons vu que 5n2 +400n+9 ∈ O(n2 ). Cela est plutôt in-
tuitif puisque 5n2 est le terme dominant et puisque les constantes « n’importent
pas » asymptotiquement. Nous formalisons ce raisonnement. Pour toutes fonc-
tions f, g ∈ F et tout coefficient c ∈ R>0 , les fonctions f + g, c · f et max(f, g)
sont définies par:
déf
(f + g)(n) = f (n) + g(n),
déf
(c · f )(n) = c · f (n),
déf
(max(f, g))(n) = max(f (n), g(n)).
Les deux propositions suivantes montrent que les constantes apparaissant
dans une somme de fonctions n’importent pas asymptotiquement, et qu’une
somme de fonctions se comporte asymptotiquement comme leur maximum.
Proposition 7. f1 + . . . + fk ∈ O(c1 · f1 + . . . + ck · fk ) pour toutes fonctions
f1 , . . . , fk ∈ F et tous coefficients c1 , . . . , ck ∈ R>0 .
Exemples.
Exemples.
1.2.2 Notation Ω
La notation O nous permet de borner des fonctions supérieurement. Afin d’ana-
lyser des algorithmes, il sera également utile de borner des fonctions inférieu-
rement. Dans ce but, nous introduisons la notation Ω.
Définition 2. Soit g ∈ F . L’ensemble Ω(g) est défini par:
déf
Ω(g) = {f ∈ F : ∃c ∈ R>0 ∃n0 ∈ N ∀n ≥ n0 f (n) ≥ c · g(n)} .
Exemple.
déf
Voyons un exemple. Soit f (n) = n2 − 400n + 5 la fonction suivante:
1 500 000 n2
n2 − 400n + 5
1 000 000
500 000
n
500 1 000
1. Cela découle du fait que tout polynôme possède un nombre fini de zéros.
CHAPITRE 1. ANALYSE DES ALGORITHMES 20
f (n) = n2 − 400n + 5
≥ n2 − 400n
n
≥ n2 − · n pour tout n ≥ 800
2
1 2
= ·n .
2
Ainsi, en prenant 1/2 comme constante multiplicative et 800 comme
seuil, nous concluons que f ∈ Ω(n2 ).
1.2.3 Notation Θ
Nous introduisons une troisième et dernière notation asymptotique qui permet
de borner une fonction à la fois inférieurement et supérieurement. Pour toute
fonction g ∈ F , nous définissons:
déf
Θ(g) = O(g) ∩ Ω(g).
Autrement dit, f ∈ Θ(g) si f ∈ O(g) et f ∈ Ω(g). Intuitivement, Θ(g) décrit
donc l’ensemble des fonctions qui croissent aussi rapidement que g.
Exemples.
déf
Par exemple, considérons la fonction f (n) = 42n + 5 suivante:
42n + 5
200 000
100 000
n
n
2 000 4 000
déf
Voyons un autre exemple. Soit la fonction g(n) = 9000n. Nous avons
g ∈ O(n2 ) puisque g est un polynôme de degré 1 alors que n2 est un
polynôme de degré 2. Cependant, nous avons g ̸∈ Ω(n2 ) puisque g croît
moins rapidement que n2 . Cet argument demeure intuitif et ne démontre
pas cette affirmation. Prouvons-la par contradiction. Supposons que g ∈
Ω(n2 ). Il existe donc c ∈ R>0 et n0 ∈ N tels que 9000n ≥ c · n2 pour
tout n ≥ n0 . En supposant que n ≥ 1, nous pouvons diviser des deux
côtés par c et n afin d’obtenir:
9000
≥n pour tout n ≥ max(n0 , 1).
c
Nous avons donc une constante du côté gauche qui borne supérieurement
une valeur arbitrairement grande du côté droit. Il y a donc contradiction,
ce qui démontre g ̸∈ Ω(n2 ). Nous en concluons donc que g ̸∈ Θ(n2 ).
n · (1 + 3n + 2) ≤ t(n) ≤ n · (1 + 5n + 3).
CHAPITRE 1. ANALYSE DES ALGORITHMES 22
∑
n
= (5 + 5(n − i + 1) + 2)
i=1
∑n
= (5n − 5i + 12)
i=1
∑
n
= 5n + 12n − 5
2
i
i=1
= 5n2 + 12n − 5n(n + 1)/2
5 19
= · n2 + · n.
2 2
Ainsi, t est borné supérieurement par un polyôme de degré 2, ce qui implique
que t ∈ O(n2 ).
Cherchons maintenant à borner t inférieurement. Considérons le cas où T
contient des éléments qui sont tous distincts. Une telle séquence ne contient pas
d’élément majoritaire, et ainsi la condition « c > n ÷ 2 » n’est jamais satisfaite.
De plus, la condition « T [i] ̸= ⊥ » est toujours satisfaite car tous les éléments
sont distincts. Les deux boucles sont donc exécutées un nombre maximal de
fois. Ainsi:
n ∑
∑ n
t(n) ≥ 1
i=1 j=i
∑n
= (n − i + 1)
i=1
∑
n
= n2 + n − i
i=1
= n2 + n − n(n + 1)/2
n2 n
= + .
2 2
Nous obtenons donc t ∈ Ω(n2 ) et par conséquent t ∈ Θ(n2 ). Les deux algo-
rithmes ont donc un temps d’exécution quadratique (dans le pire cas). Ainsi,
bien que notre deuxième algorithme puisse être légèrement plus efficace, l’amé-
lioration du temps d’exécution est négligeable.
CHAPITRE 1. ANALYSE DES ALGORITHMES 24
Nous verrons qu’il existe des algorithmes plus efficaces pour ce problème:
Θ(n log n) (laissé en exercice) et Θ(n) (présenté à la section 1.8).
1 si T [j] = x alors
2 c←c+1
3 T [j] ← ⊥
≤ m + k1 · ℓ · max(c1 , . . . , cℓ )
≤ ℓ · max(c1 , . . . , cℓ ) · (m + k1 )
≤ ℓ · max(c1 , . . . , cℓ ) · (m + c1 · k1 )
= ℓ · max(c1 , . . . , cℓ ) · f ′ (x).
Afin d’illustrer cette règle, considérons un exemple tiré de [BB96, p. 84]. Posons
déf déf √
f (n) = log n et g(n) = n. Nous avons:
f (n) f ′ (n)
lim = lim ′ (par la règle de L’Hôpital)
n→+∞ g(n) n→+∞ g (n)
1/(loge 2 · n)
= lim √
n→+∞ 1/(2 · n)
√
2· n
= lim
n→+∞ loge 2 · n
√
2 n
= · lim
loge 2 n→+∞ n
2 1
= · lim √
loge 2 n→+∞ n
= 0.
Par la proposition
√ 13,√ nous obtenons donc f ∈ O(g) et g ̸∈ O(f ), ou autrement
dit log n ∈ O( n) et n ̸∈ O(log n).
déf
O(g) = {f ∈ F2 : ∃c ∈ R>0 ∃m0 , n0 ∈ N ∀m ≥ m0 ∀n ≥ n0
f (m, n) ≤ c · g(m, n)} .
1.7.1 Correction
Nous nous sommes intéressés au temps d’exécution d’algorithmes, en prenant
pour acquis que ceux-ci fonctionnent. Cependant, il n’est pas toujours simple de
se convaincre qu’un algorithme fonctionne correctement. Formalisons le concept
de correction.
Un algorithme possède un domaine d’entrée D, par ex. l’ensemble des en-
tiers, des séquences, des matrices, des arbres binaire, des paires d’entiers, etc.
Sur toute entrée x ∈ D, on s’attend à ce que l’algorithme retourne une sortie
y telle qu’une propriété φ(x, y) soit satisfaite. Par exemple, reconsidérons l’al-
gorithme 2 qui cherche à calculer la valeur maximale apparaissant dans une
séquence non vide. Dans le cas de cet algorithme, nous avons:
D = {x : x est une séquence de n ∈ N>0 éléments},
φ(x, y) = (y = max{x[i] : 1 ≤ i ≤ n}).
Définition 4. Nous disons qu’un algorithme est correct si pour toute entrée
x ∈ D, la sortie y de l’algorithme sur entrée x est telle que φ(x, y) soit vraie.
L’appartenance d’une entrée à D s’appelle la pré-condition, et la propriété
φ s’appelle la post-condition. En mots, un algorithme est donc correct si sur
chaque entrée qui satisfait la pré-condition, la sortie satisfait la post-condition.
Afin de démontrer qu’un algorithme est correct, nous avons souvent re-
cours à un invariant, c’est-à-dire une propriété qui demeure vraie à chaque fois
qu’une ou certaines lignes de code sont atteintes. Par exemple, dans le cas de
l’algorithme 2, il est possible d’établir l’invariant suivant par induction sur i:
Proposition 14. À chaque fois que l’algorithme 2 atteint le tant que, l’égalité
suivante est satisfaite: max = max{s[j] : 1 ≤ j ≤ i − 1}.
CHAPITRE 1. ANALYSE DES ALGORITHMES 28
1.7.2 Terminaison
Pour la plupart des algorithmes que nous considérerons, il sera évident que
ceux-ci terminent, c’est-à-dire qu’une instruction retourner est exécutée sur
toute entrée. Toutefois, cela n’est pas toujours le cas. Par exemple, à ce jour
personne ne sait si l’algorithme 7 termine sur toute entrée.
Remarques.
qui recherche une valeur majoritaire en temps Θ(n). La correction de cet al-
gorithme est loin d’être évidente. Ainsi, nous expliquons le fonctionnement de
l’algorithme et démontrons qu’il est correct.
avec x = c et c = 1:
i 1 2 3 4 5
′
T [i] a b a b c
x − a a a a c
c 0 1 0 1 0 1
La deuxième phase vérifie si x apparaît au moins 3 fois, ce qui n’est pas le cas,
et conclut donc que T ′ ne possède pas de valeur majoritaire.
Afin de démontrer que l’algorithme 8 est correct, nous établissons un inva-
riant satisfait par sa première boucle. Intuitivement, celui-ci affirme qu’après
la ième itération de la première boucle, x est l’unique valeur possiblement ma-
joritaire dans la sous-séquence T [1 : i] = [T [1], T [2], . . . , T [i]].
Proposition 15. Après l’exécution du corps de la première boucle de l’algo-
rithme 8, l’invariant suivant est satisfait:
(a) c ≥ 0;
(b) x apparaît au plus i+c
2 fois dans T [1 : i];
(c) y apparaît au plus i−c
2 fois dans T [1 : i], pour tout y ̸= x.
Démonstration. Nous procédons par induction sur i ≥ 1.
Cas de base (i = 1). Puisque x = T [1] et c = 1, x apparaît au plus i+c2 = 1 fois
dans T [1 : 1], et tout y ̸= x apparaît au plus 2 = 0 fois dans T [1 : 1].
i−c
Étape d’induction. Soit i > 1. Supposons que l’invariant soit satisfait après
l’exécution pour i − 1. Soient c′ et x′ la valeur des variables c et x au début
du corps de la boucle (et ainsi à la fin du corps de l’itération précédente). Par
hypothèse d’induction, nous avons:
(a’) c ≥ 0;
i−1+c′
(b’) x′ apparaît au plus 2 fois dans T [1 : i − 1];
i−1−c′
(c’) y apparaît au plus 2 tout y ̸= x′ .
fois dans T [1 : i − 1], pour
Observons qu’exactement une des lignes 3, 4 et 5 est exécutée lors de l’exécution
du corps de la boucle. Nous considérons ces trois cas séparément.
Ligne 3. Nous avons c′ = 0, c = 1 et x = T [i]. Clairement, c ≥ 0. Puisque
c′ = 0, (b’) et (c’) affirment que chaque valeur apparaît au plus i−1 2 fois dans
T [1 : i−1]. Puisque T [i] = x, la valeur x apparaît donc au plus i−1
2 +1 = i+1
2 =
i+c
2 fois dans T [1 : i]. Soit y ̸
= x. Puisque T [i] ̸
= y, le nombre d’occurrences
de y n’a pas changé. Ainsi, y apparaît au plus i−1 i−c
2 = 2 fois dans T [1 : i].
Ligne 4. Nous avons c = c′ + 1 et T [i] = x′ = x. Par (a’), nous avons
′ ′
c > c′ ≥ 0. Par (b’), x apparaît au plus i−1+c2 + 1 = i+c2 +1 = i+c
2 fois dans
T [1 : i]. Soit y ̸= x. Le nombre d’occurrences de y n’a pas changé. Par (c’), y
′
apparaît donc au plus i−1−c 2 = i−c
2 fois dans T [1 : i].
Ligne 5. Nous avons c = c′ − 1 et T [i] ̸= x′ = x. Par (a’), nous avons c′ ≥ 0.
Puisque la ligne 5 est exécutée, nous avons c′ ̸= 0 et donc c′ > 0. Ainsi,
CHAPITRE 1. ANALYSE DES ALGORITHMES 31
′
c ≥ 0. Par (b’) et puisque T [i] ̸= x, x apparaît au plus i−1+c 2 = i+c
2 fois
dans T [1 : i]. Soit y ̸= x. Le nombre d’occurrences de y a augmenté d’au plus
′ ′
1. Ainsi, par (c’), y apparaît au plus i−1−c2 + 1 = i−c2 +1 = i−c
2 fois dans
T [1 : i].
Observation.
La première phase de l’algorithme de Boyer et Moore considère chaque
élément de T une et une seule fois, et les consomme du début vers la fin
de la séquence. De plus, la seconde phase n’est pas nécessaire si nous
savons à coup sûr que l’entrée possède une valeur majoritaire. L’algo-
rithme peut donc s’avérer particulièrement efficace avec les entrées sous
forme de flux, c.-à-d. où l’algorithme reçoit son entrée progressivement
sans que sa taille ne soit connue à priori.
CHAPITRE 1. ANALYSE DES ALGORITHMES 32
1.9 Exercices
1.1) Montrez que si f ∈ O(g), alors O(f ) ⊆ O(g). Déduisez-en l’équivalence
suivante: O(f ) = O(g) ⇐⇒ f ∈ O(g) et g ∈ O(f ). Est-ce aussi le cas si
l’on remplace O par Ω? Et par Θ?
1.14) ⋆ Soient f (n) = n et g(n) = 2⌊log n⌋ . Montrez que f ∈ Θ(g), mais que la
déf déf
1.17) Nous avons vu qu’il est possible de déterminer si une séquence possède
une valeur majoritaire en temps Θ(n2 ) et Θ(n). Donnez un algorithme
intermédiaire qui résout ce problème en temps O(n log n), en supposant
que vous ayez accès à une primitive qui permet de trier une séquence en
temps O(n log n).
CHAPITRE 1. ANALYSE DES ALGORITHMES 33
Entrées : n ∈ N>0
Sorties : vrai
tant que n est pair
n ← n + (n ÷ 2)
retourner vrai
2
Tri
1. Sauf pour quelques exceptions, tels les algorithmes loufoques comme « bogosort ».
34
CHAPITRE 2. TRI 35
Exemple.
Considérons la séquence s = [30, 50, 10, 40, 20]. Ses inversions sont
inv(s) = {(1, 3), (1, 5), (2, 3), (2, 4), (2, 5), (4, 5)}.
En corrigeant l’inversion (1, 3), nous obtenons la séquence s′ = [10, 50, 30,
40, 20] dont les inversions sont:
inv(s′ ) = {(2, 3), (2, 4), (2, 5), (3, 5), (4, 5)}.
Remarquons que bien que des inversions aient disparues, une nouvelle
inversion est aussi apparue. Toutefois, le nombre d’inversions a diminué.
En corrigeant l’inversion (2, 5) de s′ , nous obtenons la séquence s′′ =
[10, 20, 30, 40, 50] qui ne possède aucune inversion, c.-à-d. inv(s′′ ) = ∅.
Observons que s′′ est à la fois triée et une permutation de s. Nous avons
donc bel et bien trié s.
déf
Posons X = [n]\{i, j}. Puisque le contenu de s aux positions X n’a pas changé,
nous avons:
Centre. Soit x ∈ C. Observons d’abord qu’il est impossible que s[i] ≤ s[x] ≤
s[j] puisque s[i] > s[j]. Il y a donc trois ordonancemments possibles:
Avant Après
Ord. dans s f (i, x) + f (x, j) Ord. dans s′ f ′ (i, x) + f ′ (x, j)
′
s[i] > s[x] ≤ s[j] 1 s [i] ≥ s[x] < s[j] 1 ou 0
s[i] ≤ s[x] > s[j] 1 s[i] < s[x] ≥ s[j] 1 ou 0
s[i] > s[x] > s[j] 2 s[i] < s[x] < s[j] 0
déf
Fin de la preuve. Posons P = {(x, y) ∈ [n]2 : |X ∩ {x, y}| = 1}. Autrement
dit, P contient les paires de positions où précisément une position appartient
CHAPITRE 2. TRI 37
= |inv(s′ )|.
On peut démontrer que le tri par insertion est correct en observant que
l’invariant « s[1 : i − 1] est triée » est satisfait chaque fois que l’on atteint la
boucle principale, ce qui résulte en « s[1 : (n + 1) − 1] = s[1 : n] = s est triée »
à la sortie de la boucle principale.
Analysons le temps de calcul de l’algorithme. Si la condition de la boucle
interne n’est jamais satisfaite, alors le temps d’exécution appartient à Ω(n).
Cela se produit lorsque s est déjà triée. Ainsi, le temps d’exécution dans le
meilleur cas appartient à Θ(n). Si la condition de la boucle interne est satisfaite
un nombre maximal de fois, c.-à-d. i − 1 fois, alors l’algorithme fonctionne en
temps:
( n ) (n−1 )
∑ ∑
O (i − 1) = O i = O(n(n − 1)/2) = O(n2 /2 − n/2) = O(n2 ).
i=1 i=1
Puisqu’il n’y a aucune inversion dans une séquence triée, et n(n − 1)/2 in-
versions dans une séquence dont les éléments apparaissent en ordre décroissant,
la proposition 17 donne bien Θ(n) et Θ(n2 ) pour ces deux cas.
La proposition 17 montre notamment que le tri par insertion est un bon
choix pour les séquences quasi-triées. De plus, le tri par insertion performe
généralement bien en pratique sur les séquences de petite taille.
La correction du tri par monceau est immédiate (en supposant que le mon-
ceau est lui-même bien implémenté). Observons que la boucle est itérée préci-
sément n fois. Ainsi, le temps d’exécution dans le pire cas appartient à
Cet algorithme s’avère donc plus rapide que le tri par insertion sur les séquences
de grande taille.
CHAPITRE 2. TRI 40
Remarque.
k+1
=2 (ck + 1 + c)
k+1
=2 (c(k + 1) + 1).
Ainsi, lorsque n est de la forme n = 2k , nous avons t(n) ≤ 2k (ck + 1) =
n(c log n + 1) = c · n log n + n. Informellement, nous concluons donc que:
t ∈ O(n log n : n est une puissance de 2).
Nous verrons dans un chapitre subséquent que t ∈ O(n log n) sans se restreindre
aux puissances de 2.
trier'(1, |s|)
retourner s
Remarque.
s′ = [|{z}
010 , 111, 011, 101, 001].
| {z }
lo hi
2.8 Exercices
2.1) Donnez un algorithme qui détermine si une séquence s possède au moins
une inversion, et en retourne une le cas échéant. Votre algorithme doit
fonctionner en temps O(|s|).
2.2) Une séquence binaire est une séquence dont chaque élément vaut 0 ou 1,
par ex. s = [0, 1, 0, 0, 1, 0, 1]. Donnez un algorithme qui trie des séquences
binaires en temps linéaire avec une quantité constante de mémoire auxi-
liaire. Autrement dit, donnez un algorithme sur place. Votre algorithme
est-il stable?
2.4) Donnez un algorithme qui regroupe les éléments égaux d’une séquence s
de façon contigüe. Par exemple, [a, a, c, b, b] et [c, a, a, b, b] sont des re-
groupements valides de s = [b, a, a, c, b]. Votre algorithme doit fonction-
ner en temps O(|s|2 ). L’algorithme peut utiliser les comparaisons {=, ̸=},
mais pas {<, ≤, ≥, >}.
2.6) Considérez une pile de n crêpes de diamètres différents, où les crêpes sont
numérotées de 1 à n du haut vers le bas. Vous pouvez réorganiser la pile
à l’aide d’une spatule en l’insérant sous une crêpe k, puis en renversant
l’ordre des crêpes 1 à k. Donnez un algorithme qui trie les crêpes avec
O(n) renversements. Vous avez accès au diamètre de chaque crêpe.
2.8) Décrivez une version améliorée du tri par insertion qui utilise la recherche
dichotomique afin d’accélérer l’insertion. Le temps d’exécution dans le
pire cas appartient-il toujours à Θ(n2 )?
2.10) ⋆ Implémentez le tri par fusion de façon purement itérative; donc sans
récursion et sans émulation de la récursion à l’aide d’une pile.
b e
a c d
Exemple.
46
CHAPITRE 3. GRAPHES 47
Exemple.
G = ({a, b, c, d, e}, {(a, b), (b, c), (c, a), (c, d), (d, b), (d, c), (d, e)})
deg− (a) = deg− (d) = deg− (e) = 1, deg+ (a) = deg+ (b) = 1,
deg− (b) = deg− (c) = 2, deg+ (c) = 2,
deg+ (d) = 3,
deg+ (e) = 0.
a b
c d e
Remarque.
C = [v0 , v1 , . . . , vk ]
Exemple.
1. Cette deuxième contrainte est redondante pour les graphes dirigés, mais elle empêche
de considérer u −
→v−→ u comme étant un cycle simple pour les graphes non dirigés.
CHAPITRE 3. GRAPHES 49
Exemple.
3.5 Représentation
Exemple.
a b c d e a b c d e
a 0 1 1 0 0 a 0 1 0 0 0
b
1 0 0 0 1 b
0 0 1 0 0
c
1 0 0 1 1 et c
1 0 0 1 0.
d0 0 1 0 1 d0 1 1 0 1
e 0 1 1 1 0 e 0 0 0 0 0
Exemple.
3.6 Accessibilité
Nous considérons deux façons de parcourir un graphe. Plus précisément, étant
donné un sommet de départ u, nous présentons deux approches afin de calculer
∗
l’ensemble des sommets accessibles par u, c.-à-d. l’ensemble {v : u −
→ v}. Cela
permet notamment de déterminer si un sommet cible est accessible à partir
d’un sommet de départ.
2. Nous supposons que l’accès à adj[u] se fait en temps constant dans le pire cas, mais en
pratique ce n’est pas nécessairement le cas lorsque V ̸= {1, 2, . . . , n}. En effet, si les sommets
proviennent d’un domaine plus complexe, alors adj risque d’être implémenté par une table
de hachage qui offre généralement un temps constant amorti.
CHAPITRE 3. GRAPHES 51
3.9 Arbres
Nous disons qu’un graphe non dirigé est acyclique s’il ne possède pas de cycle
simple. Une forêt est un graphe non dirigé acyclique et un arbre est une forêt
connexe. Les arbres peuvent être caractérisés de plusieurs façons équivalentes:
Proposition 20. Soit G = (V, E) un graphe non dirigé. Les propriétés sui-
vantes sont toutes équivalentes:
— G est connexe et acyclique;
— G est connexe et |E| = |V | − 1;
— G est acyclique et |E| = |V | − 1.
Nous disons qu’un sommet v d’une forêt est une feuille si deg(v) = 1, et un
sommet interne sinon. Nous disons qu’un arbre est une arborescence s’il possède
un sommet spécial r appelé sa racine. Dans ce cas, nous ne considérons pas r
comme étant une feuille, même lorsque deg(r) = 1.
Un arbre couvrant d’un graphe non dirigé G est un sous-graphe de G qui
contient tous ses sommets et qui est un arbre. Un tel arbre correspond à
une façon de relier tous les sommets de G avec le moins d’arêtes possibles.
Par exemple, la figure 3.3 identifie un arbre couvrant. Tout graphe non dirigé
CHAPITRE 3. GRAPHES 56
b d
c e
Figure 3.3 – Exemple d’arbre couvrant identifié par un trait gras de couleur.
CHAPITRE 3. GRAPHES 57
3.10 Exercices
3.1) Nous avons vu comment détecter la présence de cycle dans un graphe
dirigé à l’aide du tri topologique. Donnez un autre algorithme de détection
de cycle basé sur le parcours en profondeur.
3.4) Un graphe non dirigé G = (V, E) est dit biparti s’il existe X, Y ⊆ V tels
que X ∩ Y = ∅, X ∪ Y = V , et {u, v} ∈ E =⇒ (u ∈ X ⇐⇒ v ∈ Y ).
Autrement dit, G est biparti si on peut partitionner ses sommets en deux
ensembles X et Y de telle sorte que toute arête possède un sommet dans
X et un sommet dans Y . Donnez un algorithme qui détermine effica-
cement si un graphe est biparti. Analysez sa complexité. Adaptez votre
algorithme afin qu’il calcule une partition X, Y si le graphe est biparti.
Paradigmes
58
4
Algorithmes gloutons
2 5
b d f
2
1
a 4 3 2
3
c e g
1 4
Figure 4.1 – Arbre couvrant minimal identifié par un trait gras de couleur.
59
CHAPITRE 4. ALGORITHMES GLOUTONS 60
1 1 1
a 4 3 2 a 4 3 2 a 4 3 2
3 3 3
c e g c e g c e g
1 4 1 4 1 4
2 5 2 5 2 5
b d f b d f b d f
2 2 2
1 1 1
a 4 3 2 a 4 3 2 a 4 3 2
3 3 3
c e g c e g c e g
1 4 1 4 1 4
2 5
b d f
2
1
a 4 3 2
3
c e g
1 4
Puisque log |E| ≤ log(|V |2 ) = 2 log |V | ∈ O(log |V |), nous concluons donc que
l’algorithme fonctionne en temps O(|E| log |V |).
— on ajoute e à E ′ ;
— on répète jusqu’à ce qu’il ne reste qu’un seul arbre.
Autrement dit, l’algorithme débute par une forêt de |V | arbres et réduit pro-
gressivement le nombre d’arbres en choisissant toujours la plus petite arête
disponible.
2 5 2 5 2 5
b d f b d f b d f
2 2 2
1 1 1
a 4 3 2 a 4 3 2 a 4 3 2
3 3 3
c e g c e g c e g
1 4 1 4 1 4
2 5 2 5 2 5
b d f b d f b d f
2 2 2
1 1 1
a 4 3 2 a 4 3 2 a 4 3 2
3 3 3
c e g c e g c e g
1 4 1 4 1 4
2 5
b d f
2
1
a 4 3 2
3
c e g
1 4
À tout moment, ces ensembles sont disjoints puisqu’un sommet ne peut pas
appartenir simultanément à deux arbres. Nous décrivons donc une structure
de données qui permet de manipuler une collection d’ensembles disjoints. Nous
représentons chaque ensemble par une arborescence. Par exemple, la figure 4.4
illustre une représentation possible de la partition {a}, {b, c, e, d}, {f, g}.
a b f
c d e g
O(1 + |V | + |E| log |E| + |E| log |V |) = O(|E| log |E|) = O(|E| log |V |).
1. On peut démontrer, par induction généralisée sur k, qu’une arborescence de k ∈ N≥1
sommets possède une hauteur d’au plus ⌊log k⌋.
CHAPITRE 4. ALGORITHMES GLOUTONS 65
Exemple.
Exemple.
de chaque objet est choisie. Remarquons que la seule différence entre les deux
problèmes est le passage de l’intervalle discret {0, 1} à l’intervalle continu [0, 1].
Dans le contexte des objets d’un sac, cette variante fait du sens si on peut les
découper, par ex. comme la pomme des exemples précédents.
Exemple.
4.3.3 Approximation
Bien que l’approche gloutonne ne fonctionne pas pour le problème du sac à dos
(discret), il est possible d’approximer une solution optimale en s’inspirant de
la variante fractionnelle du problème:
— on calcule une solution x selon l’approche gloutonne;
— on choisit la meilleure solution entre x et la solution y constituée unique-
ment du prochain objet qui n’entre pas dans le sac (s’il en reste un).
Exemple.
Remarque.
Pour tout ε ∈ (0, 1], il existe un algorithme qui approxime une solution
maximale du problème du sac à dos à un facteur d’au moins 1 − ε.
Autrement dit, il est possible d’approximer le problème du sac à dos de
façon arbitrairement précise. De plus, cette approximation se calcule en
temps polynomial.
CHAPITRE 4. ALGORITHMES GLOUTONS 72
4.4 Exercices
4.1) Expliquez comment calculer un arbre couvrant de poids maximal.
4.2) Supposons le poids d’un graphe soit défini par le produit de ses poids plu-
tôt que la somme. Expliquez comment calculer un arbre de poids minimal
sous cette nouvelle définition.
4.3) Lorsqu’un graphe non dirigé G n’est pas connexe, il est impossible d’obte-
nir un arbre couvrant de G. Toutefois, nous pouvons relaxer cette notion
et considérer une forêt couvrante, c’est-à-dire un sous-graphe de G qui
est une forêt telle que chacun de ses arbres est un arbre couvrant d’une
composante connexe de G. Une forêt couvrante correspond à un arbre
couvrant standard pour les graphes connexes. Les algorithmes de Prim–
Jarník et de Kruskal permettent-ils de calculer une forêt couvrante de
poids minimal? Si ce n’est pas le cas, est-il possible de les adapter?
4.4) Donnez un algorithme simple qui calcule les composantes connexes d’un
graphe non dirigé grâce à une structure d’ensembles disjoints.
73
CHAPITRE 5. ALGO. RÉCURSIFS ET DIVISER-POUR-RÉGNER 74
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
t(k) = 2 · t(k − 1) + 1
= 2 · (2 · t(k − 2) + 1) + 1 (par la relation de récurrence)
= 4 · t(k − 2) + 3
= 4 · (2 · t(k − 3) + 1) + 3 (par la relation de récurrence)
= 8 · t(k − 3) + 7
..
.
= 2i · t(k − i) + (2i − 1) (en répétant i fois)
..
.
= 2k · t(0) + (2k − 1) (en répétant k fois)
=2 −1
k
(car t(0) = 0).
Cette approche suggère donc que t(k) = 2k − 1. Afin de s’en convaincre formel-
lement, on pourrait prouver par induction que c’est bien le cas (ce l’est!)
Comme la taille de la solution est une borne inférieure sur le temps d’exé-
cution de l’algorithme et que celui-ci débute par un appel à hanoi' avec k = n,
nous en concluons qu’il fonctionne en temps Ω(2n ). Une analyse plus fine mon-
trerait que son temps d’exécution appartient à Θ(2n ). En fait, il est impossible
de résoudre le problème en moins de 2n − 1 déplacements, donc aucun algo-
rithme ne peut faire mieux.
Remarque.
L’algorithme 30 décrit une procédure récursive qui calcule tous les pavages en
observant que tout pavage débute forcément par l’un de ces trois motifs:
disons que cette récurrence est linéaire, car son côté gauche est une combinaison
linéaire, et homogène, car son côté droit vaut 0. Il existe une méthode afin de
résoudre les récurrences linéaires homogènes. Nous la décrivons en trois étapes
en l’illustrant sur notre problème de pavage.
a0 · xd + a1 · xd−1 + . . . + ad · x0 .
t(n) = c1 · 2n + c2 · (−1)n .
t(1) = c1 · 21 + c2 · (−1)1 ,
t(2) = c1 · 22 + c2 · (−1)2 .
1 = 2c1 − c2 ,
3 = 4c1 + c2 .
1. Le cas où certaines racines apparaissent plusieurs fois est légèrement plus compliqué.
CHAPITRE 5. ALGO. RÉCURSIFS ET DIVISER-POUR-RÉGNER 78
Exemple.
Nous avons:
0 si n = 0,
Fn = 1 si n = 1,
Fn−1 + Fn−2 sinon.
En déplaçant tous les termes à gauche de l’égalité, la récurrence se
réécrit Fn − Fn−1 − Fn−2 = 0. Son polynôme caractéristique est donc:
p(x) = x2 − x − 1 = (x − λ1 )(x − λ2 ),
déf
√ déf
√
où λ1 = (1 + 5)/2 ≈ 1,618 et λ2 = (1 − 5)/2 = 1 − λ1 ≈ −0,618.
Ainsi, Fn = c1 · λn1 + c2 · λn2 pour certaines constantes c1 et c2 .
Afin d’identifier la valeur des constantes, on utilise t(0) = 0 et t(1) =
1 afin de construire ce système d’équations:
0= c1 + c2 ,
1 = λ1 · c1 + λ2 · c2 .
√ √
En résolvant le système, nous obtenons c1 = 1/ 5 et c2 = −1/ 5.
Ainsi:
1 1
Fn = √ · λn1 − √ · λn2
5 5
1
= √ · (λn1 − λn2 )
5
≈ 0,447 · (1,618n − (−0,618)n ).
Remarque.
√
Le nombre λ1 = 1+2 5 ≈ 1,618, souvent dénoté φ, est le célèbre
nombre d’or qui se manifeste par ex. dans la nature et en art.
CHAPITRE 5. ALGO. RÉCURSIFS ET DIVISER-POUR-RÉGNER 79
À partiri d’ici, la méthode demeure la même. Nous savons que r(k) peut s’écrire
sous la forme
r(k) = c1 · 2k + c2 · 1k .
Afin d’identifier les valeurs de c1 et c2 , nous évaluons r(0) = 0 et r(1) = 2, et
construisons le système d’équations:
0= c1 + c2 ,
2 = 2 · c1 + c2 .
Cette procédure est décrite à l’algorithme 32. Sa correction découle du fait que
l’ordre des multiplications n’a aucune importance; ou en termes plus techniques,
par la commutativité et l’associativité de la multiplication.
Analysons le nombre de multiplications t(n) de exp-rapide par rapport à
n. Comme la procédure effectue deux multiplications et un appel récursif avec
l’exposant n ÷ 2, nous obtenons la récurrence:
{
0 si n = 0,
t(n) =
t(n ÷ 2) + 2 sinon.
Comme cette récurrence n’est pas linéaire, nous ne pouvons pas la résoudre
avec la méthode décrite précédemment. Cependant, comme on divise n par deux
à répétition, on peut imaginer que le nombre d’appels récursifs est d’au plus
log(n) + 1. De plus, à chaque appel, on effectue précisément 2 multiplications.
Nous pouvons donc naturellement conjecturer que t(n) ≤ 2 · log n + 2 pour tout
n ≥ 1. Cela se vérifie par induction généralisée sur n. Pour n = 1, nous avons
CHAPITRE 5. ALGO. RÉCURSIFS ET DIVISER-POUR-RÉGNER 81
x · y = (10k · a + b)(10k · c + d)
= 102k · ac + 10k · (ad + bc) + bd.
ad + bc = ac + bd − (a − b)(c − d).
CHAPITRE 5. ALGO. RÉCURSIFS ET DIVISER-POUR-RÉGNER 82
Coût
n c·n
Remarque.
La fonction t appartient à:
— O(nd ) si c < bd ,
— O(nd · log n) si c = bd ,
— O(nlogb c ) si c > bd .
Tri par fusion. L’algorithme de tri par fusion découpe une séquence en deux,
fait un appel récursif sur chacune des deux sous-séquences, puis effectue une
fusion en temps linéaire. Nous obtenons donc une récurrence:
(g, h) (d, h)
(g, 0) (d, 0)
[(1, 2, 3), (2, 1, 5), (3, 4, 8), (9, 3, 10), (11, 2, 12), (11, 1, 15), (13, 2, 17)]
correspond graphiquement à:
4
3
2
1
0
1 2 3 5 8 9 10 11 12 13 15 17
4
3
2
1
0
1 3 8 9 10 11 12 13 17
a b
Si droite(a) > gauche(b), cela signifie que les deux blocs se chevauchent. On
découpe donc a et b de cette façon:
Cas Avant Après
droite(a) ≤ droite(b)
hauteur(a) ≤ hauteur(b)
droite(a) ≤ droite(b)
hauteur(a) > hauteur(b)
Lorsque la fusion de x et y est complétée, l’un des deux peut encore contenir
des blocs (comme dans le tri par fusion); on les ajoute simplement à z. L’al-
gorithme 34 décrit cette approche sous forme de pseudocode. Notons qu’afin
d’éviter des cas limites embêtants, il faudrait remplacer chaque instruction de
la forme « empiler c sur w » par
d0 · n0 · λn + d1 · n1 · λn + . . . + dk−1 · nk−1 · λn .
t(n) = c1 · 3n + c2 · 2n + c3 · n · 2n .
Maintenant que nous savons traiter les racines multiples, nous pouvons
donner un aperçu du raisonnement derrière le théorème maître. Considérons
la récurrence suivante qui capture essentiellement le temps d’exécution du tri
par fusion et de l’algorithme du problème de la ligne d’horizon:
{
1 si n = 0,
t(n) =
2 · t(n ÷ 2) + n sinon.
Cette récurrence n’est pas linéaire, on ne peut donc pas appliquer directement
notre méthode. Cependant, nous pouvons analyser t pour les valeurs de n qui
sont des puissances de deux en effectuant un changement de domaine. Posons
s(k) = t(2k ). Remarquons que s(0) = t(1) = 3 et que s(k) = 2 · t(2k−1 ) + 2k
CHAPITRE 5. ALGO. RÉCURSIFS ET DIVISER-POUR-RÉGNER 88
obtenu de 2k
z }| {
(x − 2) · (x − 2)
| {z }
poly. car.
s(k) = c1 · 2k + c2 · k · 2k .
En résolvant le système:
s(0) = 3 = c1
s(1) = 8 = 2c1 + 2c2
s(k) = 3 · 2k + k · 2k .
5.8 Exercices
5.1) Identifiez une forme close pour la récurrence suivante:
1 si n = 0,
t(n) = 2 si n = 1,
t(n − 1) + 6 · t(n − 2) sinon.
5.5) Soient c, d ∈ R≥1 . Identifiez une forme close pour la récurrence suivante:
{
0 si n = 0,
t(n) =
d · t(n − 1) + c sinon.
5.6) Nous avons vu que la récurrence définie par t(0) = 0 et t(k) = 2·t(k−1)+1
possède la forme close t(k) = 2k − 1. Montrez que c’est bien le cas par
induction sur k.
5.8) Donnez un algorithme qui calcule tous les pavages d’une grile 2 × n à
l’aide de tuiles de cette forme:
5.12) Donnez un algorithme qui reçoit une séquence binaire non décroissante
et retourne le nombre de bits égaux à 1 en temps O(log n). Par exemple,
votre algorithme doit retourner 5 sur entrée [0, 0, 0, 1, 1, 1, 1, 1].
A[i − 1, j]
>
A[i, j − 1] < A[i, j] > A[i, j + 1]
<
A[i + 1, j]
5.14) Nous disons qu’une séquence s de n ∈ N≥1 éléments comparables est triée
circulairement s’il existe i ∈ [n] tel que
5.15) Donnez un algorithme qui reçoit une séquence s de n entiers et qui re-
tourne la plus grande somme contigüe en temps O(n log n), c’est-à-dire:
Remarque.
Ce chapitre traite de la force brute, une approche simple qui consiste à explo-
rer exhaustivement un ensemble de solutions candidates jusqu’à l’identification
d’une véritable solution. Par exemple, afin de trier une séquence s de n élé-
ments comparables, on pourrait naïvement énumérer toutes les permutations
de s jusqu’à l’identification de sa forme triée. Cela nécessiterait n! itérations
dans le pire cas, ce qui est impraticable. En général, on nomme « explosion com-
binatoire » le phénomène où l’espace de recherche croît rapidement par rapport
à la taille des entrées. Malgré cette limitation générale, la force brute possède
certains avantages. Premièrement, elle permet d’établir rapidement un algo-
rithme de référence contre lequel on peut tester nos algorithmes plus efficaces.
Deuxièmement, pour plusieurs problèmes, on ne connaît simplement aucune
autre approche algorithmique, par ex. plusieurs problèmes dits NP-complets.
Nous présentons quelques-uns de ces problèmes.
94
CHAPITRE 6. FORCE BRUTE 95
Ces matrices sont éparses, c-à-d. qu’il y a peu d’occurrences de 1 par rap-
port aux occurrences de 0. Nous pouvons donc simplifier leur représentation.
Remarquons qu’une solution assigne nécessairement une dame à chaque ligne
et à chaque colonne. Ainsi, nous pouvons décrire une solution par une séquence
sol de taille n où sol[i] indique la colonne de la dame sur la ligne i. Par exemple,
pour n = 8, la solution [1, 5, 8, 6, 3, 7, 2, 4] correspond graphiquement à:
1 2 3 4 5 6 7 8
1 X
2 X
3 X
4 X
5 X
6 X
7 X
8 X
( 2)
Peu des nn possibilités forment une solution. Par exemple, il existe 92
solutions pour le cas n = 8. Nous avons donc intérêt à ne pas explorer toutes
les possibilités.
1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8 9 1 0 -1 -2 -3 -4 -5 -6 -7
2 3 4 5 6 7 8 9 10 2 1 0 -1 -2 -3 -4 -5 -6
3 4 5 6 7 8 9 10 11 3 2 1 0 -1 -2 -3 -4 -5
4 5 6 7 8 9 10 11 12 4 3 2 1 0 -1 -2 -3 -4
5 6 7 8 9 10 11 12 13 5 4 3 2 1 0 -1 -2 -3
6 7 8 9 10 11 12 13 14 6 5 4 3 2 1 0 -1 -2
7 8 9 10 11 12 13 14 15 7 6 5 4 3 2 1 0 -1
8 9 10 11 12 13 14 15 16 8 7 6 5 4 3 2 1 0
— il ne peut y avoir deux dames (ou plus) sur une même ligne:
∧ ∧ ∧
(¬xi,j ∨ ¬xi,k )
1≤i≤n 1≤j≤n j<k≤n
— il ne peut y avoir deux dames (ou plus) sur une même colonne:
∧ ∧ ∧
(¬xj,i ∨ ¬xk,i )
1≤i≤n 1≤j≤n j<k≤n
— il ne peut y avoir deux dames (ou plus) sur une même diagonale:
∧ ∧
(¬xi,j ∨ ¬xi′ ,j ′ ) ∧ (¬xi,j ∨ ¬xi′ ,j ′ )
i,i′ ,j,j ′ ∈[n] i,i′ ,j,j ′ ∈[n]
(i,j)̸=(i′ ,j ′ ) (i,j)̸=(i′ ,j ′ )
i+j=i′ +j ′ i−j=i′ −j ′
Un solveur SAT de pointe risque d’identifier une solution d’une telle formule
bien plus rapidement qu’un algorithme par force brute « maison » (cela dépend
des problèmes, des formules et de leur taille). Par exemple, voici la formulation
du problème pour n = 4 avec le solveur z3 de Microsoft Research.
L’instance v = [50, 5, 65, 10, 12, 20], p = [700, 320, 845, 70, 420, 180], c = 900 du
problème du sac à dos s’exprime quant à elle par:
maximiser 50 x1 + 5 x2 + 65 x3 + 10 x4 + 12 x5 + 20 x6
sujet à 700 x1 + 320 x2 + 845 x3 + 70 x4 + 420 x5 + 180 x6 ≤ 900.
Ces programmes peuvent être résolus efficacement en pratique par des solveurs
exploitant la force brute combinée à des heuristiques.
CHAPITRE 6. FORCE BRUTE 102
6.6 Exercices
6.1) La distance de Levenshtein entre deux chaînes de caractères u et v, dé-
notée dist(u, v), est définie comme étant la plus petite quantité d’ajouts,
de retraits et de modifications de lettres qui transforment u en v. Nous
écrivons ε afin de dénoter la chaîne vide. Par exemple: dist(ab, ac) = 1,
dist(abc, ba) = 2 et dist(ε, ab) = 2. Donnez un algorithme de force brute
qui calcule la distance entre deux chaînes données. Pensez d’abord à une
borne supérieure sur dist(u, v).
6.2) Donnez un algorithme qui identifie la plus plus longue sous-chaîne conti-
guë commune entre deux chaînes de caractères u et v. Par exemple, si
u = abcaba et v = abaccab, alors votre algorithme devrait retourner cab.
Analysez sa complexité.
6.4) Un chemin hamiltonien est un chemin qui passe par chaque sommet d’un
graphe exactement une fois. Donnez un algorithme qui détermine s’il
existe un chemin hamiltonien entre deux sommets s et t d’un graphe G.
Par exemple, l’arbre des appels récursifs de fib(4) montre que les valeurs de
fib(2), fib(1) et fib(0) sont recalculées plusieurs fois:
103
CHAPITRE 7. PROGRAMMATION DYNAMIQUE 104
fib(4)
fib(3) fib(2)
fib(1) fib(0)
T [i, 0] = 0, si 0 ≤ i ≤ n,
T [0, j] = ∞ si 1 ≤ j ≤ n,
T [i, j] = min(T [i − 1, j], T [i, j − s[i]] + 1) sinon,
v = [5, 3, 4] (valeurs),
p = [4, 2, 3] (poids),
c =8 (capacité).
0 1 2 3 4 5 6 7 8
0 (sans objet) 0 0 0 0 0 0 0 0 0
1 (avec objet 1) 0 0 0 0 5 5 5 5 5
2 (avec objet 2) 0 0 3 3 5 5 8 8 8
3 (avec objet 3) 0 0 3 4 5 7 8 9 9
Par exemple, nous avons T [ 2, 5 ] = 5 puisqu’on ne peut pas prendre les deux
premiers objets simultanément et puisque le premier objet possède la valeur
maximale entre ces deux objets. L’entrée T [ 3, 5 ] = 7 raffine cette solution car
on peut prendre les objets 2 et 3 pour une valeur combinée de 7. La solution
au problème de départ apparaît dans le coin inférieur droit: T [ 3, 8 ] = 9.
T [i, 0] = 0, si 0 ≤ i ≤ n,
T [0, j] = 0 si 0 ≤ j ≤ n,
T [i, j] = max(T [i − 1, j], T [i − 1, j − p[i]] + v[i]) sinon,
2
b d
3 3
3
a 5 1 f
1 7
c e
7
∞ ∞ 3 ∞
2 2
b d b d
3 3 3 3
0 ∞ 0 ∞
a 3 a 3
5 1 f 5 1 f
1 7 1 7
c e c e
7 7
∞ ∞ 1 ∞
3 ∞ 3 5
2 2
b d b d
3 3 3 3
0 ∞ 0 ∞
a 3 a 3
5 1 f 5 1 f
1 7 1 7
c e c e
7 7
1 8 1 6
3 5 3 5
2 2
b d b d
3 3 3 3
0 8 0 8
a 3 a 3
5 1 f 5 1 f
1 7 1 7
c e c e
7 7
1 6 3 5 1 6
2
b d
3 3
0 8
a 3
5 1 f
1 7
c e
7
1 6
Figure 7.2 – Exemple d’exécution de l’algorithme de Dijkstra.
u∈V
u∈V
Poids négatifs. L’algorithme de Dijkstra suppose que le poids des arêtes sont
non négatifs, autrement l’algorithme peut échouer. Par exemple, considérons
le graphe de la figure 7.3 à partir du sommet a.
L’algorithme marque les sommets dans l’ordre [a, e, b, d, c] et retourne no-
tamment la distance d[e] = 2, alors que le plus court chemin de a vers e est de
longueur 3 + 2 − 2 − 2 = 1.
3
a b
2
2 1 c
−2
e d
−2
Figure 7.3 – Graphe avec poids négatifs sur lequel l’algo. de Dijkstra échoue.
et ce sans restriction de non négativité sur les poids. Cet algorithme exploite
l’observation suivante: si on découpe un plus court chemin de vi vers vj , alors
on obtient un plus court chemin de vi vers un sommet intermédiaire vk , ainsi
qu’un plus court chemin de vk vers vj .
v1 2 v2
4 5 3
1
v4 2 v3
— v1 v2 v3 v4
v1 0 2 ∞ 5
v2 ∞ 0 ∞ 1
v3 ∞ 3 0 ∞
v4 4 ∞ 2 0
k=1 v1 v2 v3 v4 k=2 v1 v2 v3 v4
v1 0 2 ∞ 5 v1 0 2 ∞ 3
v2 ∞ 0 ∞ 1 v2 ∞ 0 ∞ 1
v3 ∞ 3 0 ∞ v3 ∞ 3 0 4
v4 4 6 2 0 v4 4 6 2 0
k=3 v1 v2 v3 v4 k=4 v1 v2 v3 v4
v1 0 2 ∞ 3 v1 0 2 5 3
v2 ∞ 0 ∞ 1 v2 5 0 3 1
v3 ∞ 3 0 4 v3 8 3 0 4
v4 4 5 2 0 v4 4 5 2 0
Θ(|V |2 + |V | + |E| + |V |3 ).
En effet, imaginons par ex. qu’une entrée d[vi , vk ] soit modifiée, alors cela si-
gnifierait que d[vi , vk ] > d[vi , vk ] + d[vk , vk ] et ainsi que d[vk , vk ] < 0. Cela est
impossible car G ne possède aucun cycle négatif.
Soient i, j ∈ [n] \ {k} et soit C un plus court chemin de vi vers vj dont
les sommets intermédiaires appartiennent à {v1 , v2 , . . . , vk }. Si C n’utilise pas
le sommet vk , alors δk (vi , vj ) = δk−1 (vi , vj ). Sinon, nous avons δk (vi , vj ) =
δk−1 (vi , vk ) + δk−1 (vk , vj ). Par conséquent:
Remarque.
0 ∞ 0 3
a 3 a 3
b 2 ∞ b 2 5
2 1 c 2 1 c [inchangé ensuite]
e d −2 e d −2
−2 −2
∞ ∞ 1 3
Correction. Soit δi (v) la longueur d’un plus court chemin de s vers v utili-
sant au plus i arêtes. On peut montrer que l’algorithme de Bellman-Ford est
correct en démontrant la proposition suivante par induction:
Proposition 27. Après i exécutions de la boucle principale de l’algorithme
de Bellman-Ford, pour tout sommet v ∈ V ,
— si d[v] ̸= ∞, alors d[v] correspond à la longueur d’un chemin de s vers
v,
— d[v] ≤ δi (v).
En effet, si d[u] + p[u, v] < d[v], alors une itération supplémentaire aurait
diminué d[v]. Or, cela est impossible en l’absence d’un cycle négatif.
7.3.4 Sommaire
Voici un sommaire des trois algorithmes de plus courts chemins introduits:
7.4 Exercices
7.1) La quantité de mémoire utilisée par l’algorithme 43 appartient à O(m·n).
Adaptez l’algorithme afin de la réduire à O(m).
7.6) Identifiez une famille de graphes pour laquelle il existe un nombre expo-
nentiel de plus courts chemins entre deux des sommets.
7.7) Montrez que si un graphe ne possède pas de cycle négatif et qu’il existe
un plus court chemin entre deux sommets, alors il en existe un simple.
7.11) Si nous voulons seulement identifier un plus court chemin d’un sommet
s vers un sommet t, à quel moment pouvons-nous arrêter l’exécution de
l’algorithme de Dijkstra?
7.13) L’algorithme de Dijkstra ne fonctionne pas sur les graphes avec des poids
négatifs. Peut-on le faire fonctionner avec le prétraitement suivant?
Prétraitement: on remplace le poids p[e] de chaque arête e par le nouveau
poids p[e] + |d|, où d est le plus petit poids négatif du graphe?
7.14) Deux personnes situées dans des villes distinctes veulent se rencontrer.
Elles désirent le faire le plus rapidement possible et décident donc de se
rejoindre dans une ville intermédiaire. Expliquez comment identifier cette
ville algorithmiquement. Considérez un graphe pondéré G = (V, E) où V
est l’ensemble des villes, et où chaque arête u −
→ v de poids d représente
une route directe de u vers v dont le temps (idéalisé) pour la franchir est
de d minutes.
(tiré de [Eri19, chap. 8, ex. 14])
Dans l’ensemble des chapitres précédents, nous avons étudié les algorithmes dits
déterministes: les algorithmes dont la valeur de retour et le temps d’exécution
ne diffèrent jamais sur une même entrée. Dans ce chapitre, nous considérons
les algorithmes probabilistes ayant accès à une source d’aléa (idéalisée).
119
CHAPITRE 8. ALGORITHMES ET ANALYSE PROBABILISTES 120
déf
est de p = 6/8 = 3/4. Ainsi, avec probabilité p, on effectue un seul tour de
boucle, et avec probabilité (1 − p), on effectue un tour de boucle, plus E[X]
tours supplémentaires. Nous avons donc:
E[X] = p · 1 + (1 − p) · (1 + E[X])
= p + 1 + E[X] − p − p · E[X]
= (1 − p) · E[X] + 1.
Ainsi, p · E[X] = 1 et par conséquent E[X] = 1/p. Nous concluons donc que
E[X] = 4/3 et ainsi que le nombre espéré de tirs à pile ou face est de 3·(4/3) = 4.
Observation.
Nous aurions pu obtenir l’espérance en observant que X suit une loi
géométrique de paramètre p = 3/4 et ainsi que E[X] = 1/p = 4/3.
déf
de quitter la boucle pour une itération donnée est de p = c/2k . Ainsi:
Remarque.
Intuitivement, tout algorithme doit itérer sur au moins la moitié des éléments de
s afin de retourner le maximum. Autrement dit, un algorithme résout forcément
CHAPITRE 8. ALGORITHMES ET ANALYSE PROBABILISTES 122
ce problème en temps Ω(n) dans le pire cas. Nous présentons deux algorithmes
probabilistes qui surmontent cette barrière.
b d
a e
c c
b d d
a e ab e
cd
ab e abcd e
Proposition 28. Une coupe minimum possède une taille d’au plus 2|E|/|V |.
possède une taille égale à deg(v). Donc, k ≤ deg(v) pour tout v ∈ V , et ainsi:
|V | fois
z }| {
|V | · k = k + k + . . . + k
∑
≤ deg(v)
v∈V
= 2|E|.
En divisant les deux côtés par |V |, nous obtenons k ≤ 2|E|/|V |.
Théorème 4. La probabilité d’erreur de l’algorithme de Karger est inférieure
ou égale à 1 − 1/|V |2 .
Démonstration. Fixons une coupe minimum C = (X, Y ). Observons d’abord
que la probabilité de choisir une arête qui traverse C est d’au plus 2/|V |. En
effet, il y a |E| choix d’arêtes et au plus 2|E|/|V | arêtes qui traversent C par
la proposition 28. Si l’algorithme contracte une arête qui ne traverse pas C à
chaque itération, alors la valeur de retour est correcte. Soit p la probabilité de
contracter une arête qui ne traverse pas C à chaque itération. Nous avons:
( )( )( ) ( ) ( )
2 2 2 2 2
p≥ 1− 1− 1− ··· 1 − · 1−
|V | |V | − 1 |V | − 2 4 3
|V | − 2 |V | − 3 |V | − 4 2 1
= · · ··· ·
|V | |V | − 1 |V | − 2 4 3
2
=
|V | · (|V | − 1)
1
≥ .
|V |2
Remarquons que C n’est pas nécessairement l’unique coupe minimum. La pro-
babilité de succès est donc d’au moins p. Ainsi, la probabilité d’erreur est d’au
plus 1 − p ≤ 1 − 1/|V |2 .
= 2−k/|V | .
2
CHAPITRE 8. ALGORITHMES ET ANALYSE PROBABILISTES 127
déf
Ainsi, en prenant k = ε · |V |2 , la probabilité d’erreur est réduite à au plus 1/2ε .
En général, nous pouvons réduire la probabilité d’erreur d’un algorithme de
Monte Carlo (et ainsi amplifier sa probabilité de succès) en le répétant k fois,
puis en retournant la meilleure valeur ou la valeur majoritaire selon le type de
problème.
Ainsi, l’analyse en temps moyen correspond à faire l’hypothèse que les en-
trées d’un algorithme sont distribuées uniformément, et à faire la moyenne sur
toutes les entrées d’une même taille. La validité de cette hypothèse dépend
donc grandement de l’application.
À titre d’exemple, nous analysons le temps moyen du tri par insertion:
Proposition 29. Le temps moyen du tri par insertion appartient à Θ(n2 ).
∑
n
f (n) = [(n − 1)! · (i − 1) + f (n − 1)]
i=1
∑
n
= n · f (n − 1) + (n − 1)! · (i − 1)
i=1
= n · f (n − 1) + (n − 1)! · n(n − 1)/2
= n · f (n − 1) + n! · (n − 1)/2.
CHAPITRE 8. ALGORITHMES ET ANALYSE PROBABILISTES 128
f (n) = n · f (n − 1) + n! · (n − 1)/2
= n · [(n − 1) · f (n − 2) + (n − 1)! · (n − 2)/2] + n! · (n − 1)/2
= n · (n − 1) · f (n − 2) + n!/2 · [(n − 2) + (n − 1)]
..
.
∑
n−1
= n! · f (0) + n!/2 · i
i=0
∑
n−1
= n!/2 · i
i=1
= n!/2 · n(n − 1)/2
= n! · n(n − 1)/4.
8.6 Exercices
8.1) Montrez que la distribution des nombres générés par l’algorithme 49 est
bien uniforme.
8.4) Supposez que vous ayez accès à une pièce de monnaie biaisée: elle retourne
déf
pile avec probabilité 0 < p < 1 et face avec probabilité q = 1 − p. Vous
ne connaissez pas la valeur de p. Donnez un algorithme qui simule une
pièce non biaisée.
Entrées : A, B, C ∈ Qn×n
Résultat : A · B = C?
générer un vecteur aléatoire v ∈ {0, 1}n de façon uniforme
retourner A · (B · v) = C · v
déf
Indice: pensez à la probabilité que D · v = 0, où D = A · B − C.
Cette section présente des solutions à certains des exercices du document. Dans
certains cas, il ne s’agit que d’ébauches de solutions.
131
ANNEXE A. SOLUTIONS DES EXERCICES 132
Chapitre 0
0.4) Soient m, n ∈ N tels que m est pair et n est impair. Démontrons que m+n
est impair. Par définition, il existe a, b ∈ N tels que m = 2a et n = 2b + 1
Nous avons donc m + n = 2a + 2b + 1 = 2(a + b) + 1. Ainsi m + n = 2k + 1
déf
où k = a + b. Nous concluons donc que m + n est impair.
0.8) Montrons que n! > 2n pour tout n ∈ N≥4 par induction sur n.
Cas de base (n = 4). Nous avons 4! = 1 · 2 · 3 · 4 = 24 > 16 = 24 .
(n + 1)! = 1 · 2 · · · n · (n + 1)
> 2n · (n + 1) (par hypothèse d’induction)
>2 ·2n
(car n + 1 ≥ 4 + 1 > 2)
n+1
=2 .
0.9) Soient n, k ∈ N. Si n < k, alors les deux côtés de l’équation sont triviale-
ment égaux à 0. Si n ≥ k, nous avons:
( ) ( )
n n n! n!
+ = +
k k+1 k! · (n − k)! (k + 1)! · (n − k − 1)!
( )
1 1
= n! · +
k! · (n − k)! (k + 1)! · (n − k − 1)!
( )
(k + 1) (n − k)
= n! · +
(k + 1)! · (n − k)! (k + 1)! · (n − k)!
(k + 1) + (n − k)
= n! ·
(k + 1)! · (n − k)!
n+1
= n! ·
(k + 1)! · (n − k)!
(n + 1)!
=
(k + 1)! · (n − k)!
( )
n+1
= .
k+1
(0) (0)
0.10) Observons que (n) 0 = 1 et k = 0 pour k > 0. Ainsi, la formule de Pascal
montre que k se calcule récursivement comme une grande somme de 1
et 0, ce qui en fait forcément un entier naturel. Nous pourrions démontrer
ce fait de façon un peu plus formelle en procédant par induction sur n.
ANNEXE A. SOLUTIONS DES EXERCICES 133
0.16) Observons d’abord qu’il est impossible de payer 1$ puisque toute combi-
naison de 2$ et 5$ excède forcément ce montant. De plus, il est impossible
de payer 3$ puisqu’il faut nécessairement prendre une pièce de 2$ et qu’il
est impossible de payer le 1$ restant (par l’argument précédent). Claire-
ment, il est possible de payer 2$. Montrons maintenant qu’il est possible
de payer tout montant n ≥ 4 par induction généralisée.
Cas de base (n ∈ {4, 5}). Pour n = 4, il suffit de prendre deux pièces de
2$, et pour n = 5 il suffit de prendre un billet de 5$.
Étape d’induction. Soit n ≥ 5. Supposons qu’il soit possible de payer
tout montant compris dans [4, n] et cherchons à montrer qu’il est possible
de payer (n + 1)$. Nous prenons une pièce de 2$, puis il reste à payer
(n − 1)$. Observons que n − 1 ∈ [4, n]. Ainsi, par hypothèse d’induction,
il est possible de payer le reste du montant.
ANNEXE A. SOLUTIONS DES EXERCICES 134
Chapitre 1
1.1) Soient f, g ∈ F tels que f ∈ O(g). Soit h ∈ O(f ). Puisque h ∈ O(f ) et
f ∈ O(g), nous avons h ∈ O(g) par transitivité. Ainsi, O(f ) ⊆ O(g).
1.4)
f (n) f ′ (n)
lim = lim ′ (par la règle de L’Hôpital)
n→∞ g(n) n→∞ g (n)
2n
= lim
n→∞ loge (2) · 2n
2 n
= · lim
loge (2) n→∞ 2n
2 n′
= · lim (par la règle de L’Hôpital)
loge (2) n→∞ (2n )′
2 1
= · lim
loge (2) n→∞ loge (2) · 2n
2 1
= · lim
loge (2)2 n→∞ 2n
= 0.
f (n) = 1d + 2d + . . . + nd
≤ nd + nd + . . . + nd (pour tout n ≥ 1)
=n·n d
= nd+1 .
∑
n
f (n) = id
i=1
∑
n
≥ id (on garde les termes ≥ à la médiane)
i=⌈(n+1)/2⌉
∑
n
≥ ⌈(n + 1)/2⌉d (car chaque i ≥ ⌈(n + 1)/2⌉)
i=⌈(n+1)/2⌉
k(n − k) ≥ n ⇐⇒ kn − k 2 ≥ n
⇐⇒ kn − n ≥ k 2
⇐⇒ n(k − 1) ≥ k 2
k2
⇐⇒ n ≥ (car k − 1 ≥ 2 − 1 > 0).
k−1
nd = n · nd−1
∏
d
d2
≤n· k(n − k) pour tout n ≥
d−1
k=2
= n · 2 · (n − 2) · · · d · (n − d)
= 2 · 3 · · · d · (n − d) · · · (n − 3) · (n − 2) · n
≤ n! pour tout n ≥ 2d + 1.
√
1.12) Soit d ∈ N>0 . Montrons d’abord que log n ∈ O ( d n). Pour tout n ∈ N≥1 :
( √ )
log n = log ( d n)d
√
= d · log d n
√
≤d· dn (car log x ≤ x pour tout x ∈ R>0 ).
Chapitre 2
2.2) Algorithme sur place non stable:
2.6) On insère la spatule sous la plus grande crêpe, puis on renverse. Ensuite,
on insère la spatule sous la pile et on l’inverse au complet. La plus grande
crêpe est maintenant à la dernière position. En répétant ce processus n
fois, on obtient une pile de crêpes triée. Puisque chaque itération nécessite
deux renversements, on obtient O(n) renversements au total. Une analyse
légèrement plus détaillée montre que cette procédure effectue 2n − 3 ren-
versements. Notons qu’il est possible d’obtenir une meilleure constante
multiplicative.
ANNEXE A. SOLUTIONS DES EXERCICES 138
Chapitre 3
3.2)
Entrées : matrice A ∈ {0, 1}n×n
Résultat : indice de l’intrus
i←1
j←1
tant que j ≤ n
si i = j alors
j ←j+1
sinon si A[i, j] = 0 alors // i n'est pas l'intrus
i←i+1
sinon // j n'est pas l'intrus
j ←j+1
retourner i
Chapitre 4
4.1) Les deux algorithmes présentés fonctionnent également sur les poids né-
gatifs. Il suffit donc de multiplier chaque poids par −1 et de rechercher
un arbre couvrant minimal.
Si l’on ne croît pas que ces algorithmes fonctionnent avec des poids néga-
tifs (à défaut d’avoir vu une preuve!), on peut argumenter qu’ils peuvent
être adaptés. Soit G = (V, E) un graphe non dirigé pondéré par p et soit
c son plus petit poids, c.-à-d. c = min{p[e] : e ∈ E}. On remplace p par
p′ tel que p′ [e] = p[e] + |c| + 1. Un arbre couvrant de G est minimal sous
déf
4.2) Minimiser p[1] · p[2] · · · p[n] est équivalent à minimiser son logarithme:
log(p[1] · p[2] · · · p[n]) = log p[1] + log p[2] + . . . + log p[n]. Il suffit donc
de remplacer chaque poids par son logarithme et de rechercher un arbre
couvrant minimal sous la définition standard.
Chapitre 5
5.1) A) Polynôme caractéristique: x2 − x − 6 = (x − 3)(x + 2).
B) Forme close: t(n) = c1 · 3n + c2 · (−2)n .
C) Identification des constantes:
1 = c1 + c2
2 = 3c1 − 2c2
0 = c1 + c2 + c3
1 = 2c1 − c2 + c3
4 = 4c1 + c2 + c3
1 = c1 + c2
0 = c1 − c2
1 + (−1)n
t(n) = (1/2) · 1n + (1/2) · (−1)n = ∈ Θ(1).
2
ANNEXE A. SOLUTIONS DES EXERCICES 141
5.7) On adapte le tri par fusion afin d’identifier les inversions en triant:
Entrées : séquence s d’éléments comparables
Sorties : séquence s triée et nombre d’inversions
trier(s):
fusion(x, y ):
i ← 1; j ← 1; z ← [ ]
c←0 // nombre d'inversions entre x et y
tant que i ≤ |x| ∧ j ≤ |y|
si x[i] ≤ y[j] alors
ajouter x[i] à z
i←i+1
sinon
ajouter y[j] à z
j ←j+1
c ← c + (|x| − i + 1)
retourner (z + x[i : |x|] + y[j : |y|], c)
si |s| ≤ 1 alors retourner (s, 0)
sinon
m ← |s| ÷ 2
x, a ← trier(s[1 : m])
y, b ← trier(s[m + 1 : |s|])
z, c ← fusion(x, y)
retourner (z, a + b + c)
rence de 1:
Entrées : séquence de bits s ordonnée de façon croissante
Sorties : nombre d’occurrences de 1 dans s
compter(s):
premier-un(lo, hi):
si lo = hi alors
si s[lo] = 1 alors retourner lo
sinon retourner aucune
sinon
mid ← (lo + hi) ÷ 2
si s[mid] = 0 alors
retourner premier-un(mid + 1, hi)
sinon
retourner premier-un(lo, mid)
pos ← premier-un(1, |s|)
si pos ̸= aucune alors retourner |s| − pos + 1
sinon retourner 0
Chapitre 6
6.2) Cet algorithme essaie toutes les sous-chaînes contiguës en temps O(mn ·
min(m, n)) où m = |u| et n = |v|:
Entrées : chaînes u et v
Résultat : plus longue sous-chaîne contiguë commune à u et v
sous-chaine-contiguë(u, v ):
préfixe(i, j ):
p ← []
tant que i ≤ |u| ∧ j ≤ |v| ∧ u[i] = v[j]
ajouter u[i] à p
i←i+1
j← j + 1
retourner p
s ← []
pour i ← 1, . . . , |u|
pour j ← 1, . . . , |v|
s′ ← préfixe(i, j )
si |s′ | > |s| alors s ← s′
retourner s
Chapitre 7
7.1) On mémorise seulement la dernière ligne de T :
7.5) On calcule la taille d’un plus long suffixe commun de u[1 : i] et v[1 : j]
en temps O(|u| · |v|), puis on retourne la plus grande taille identifiée:
Entrées : chaînes u et v
Résultat : plus longue sous-chaîne commune à u et v
sous-chaine-contiguë-dyn(u, v ):
initialiser T [0 . . . |u|, 0 . . . |v|] avec 0
i′ , j ′ ← 0, 0
pour i ← 1, . . . , |u|
pour j ← 1, . . . , |v|
si u[i] = v[j] alors
T [i, j] ← T [i − 1, j − 1] + 1
sinon
T [i, j] ← 0
// Nouvelle solution meilleure que l'ancienne?
si T [i, j] > T [i′ , j ′ ] alors
i′ , j ′ ← i, j
retourner T [i′ , j ′ ]
7.6)
s t
6 −2
s t
1 1 1
7.14) Soient s et t les sommets qui correspondent aux deux villes. On cherche
à identifier
min{max(d[v], d′ [v]) : v ∈ V },
où d[v] et d′ [v] dénotent respectivement la distance minimale de s vers
v, et t vers v. Ainsi, on calcule d et d′ avec l’algorithme de Dijkstra
à partir de s et t respectivement. Ensuite, on identifie itérativement le
sommet v qui minimise max(d[v], d′ [v]). L’algorithme fonctionne en temps
O(|V | log |V | + |E| + |V |) = O(|V | log |V | + |E|).
ANNEXE A. SOLUTIONS DES EXERCICES 147
Chapitre 8
8.1) Considérons la dernière itération de l’algorithme. Soit A l’événement
« y2 = y1 = y0 ». Nous avons:
Pr[A] = Pr[y2 = y1 = y0 = 0 ∨ y2 = y1 = y0 = 1]
= Pr[y2 = y1 = y0 = 0] + Pr[y2 = y1 = y0 = 1] (évén. disjoints)
∏
2 ∏
2
= Pr[yi = 0] + Pr[yi = 1] (par indép.)
i=0 i=0
∏
2 ∏
2
= (1/2) + (1/2)
i=0 i=0
= 1/8 + 1/8
= 1/4.
Ainsi,
Pr[A] = 1 − Pr[A] = 1 − (1/4) = 3/4.
Soit X la variable aléatoire qui dénote la valeur retournée et soit k ∈ [1, 6].
Nous avons:
déf déf
8.3) Avec a = 1 et b = 6, la probabilité de générer 2, par exemple, est de 1/4
plutôt que 1/6. On peut s’en convaincre en dessinant l’arbre de récursion.
Pr[retourner pile]
= Pr[X = 0 | X ̸= Y ]
= Pr[X = 0 ∧ X ̸= Y ] / Pr[X ̸= Y ]
= Pr[X = 0 ∧ Y = 1] / Pr[X ̸= Y ]
= (Pr[X = 0] · Pr[Y = 1]) / Pr[X ̸= Y ] (par indép.)
= pq/ Pr[X ̸= Y ]
= pq/ Pr[(X = 0 ∧ Y = 1) ∨ (X = 1 ∧ Y = 0)]
= pq/(Pr[X = 0 ∧ Y = 1] + Pr[X = 1 ∧ Y = 0]) (évén. disjoints)
= pq/(Pr[X = 0] · Pr[Y = 1] + Pr[X = 1] · Pr[Y = 0]) (par indép.)
= pq/[pq + qp]
= pq/2pq
= 1/2.
déf déf ∑n
Posons a = D[i, j] et b = k=1 D[i, k] · v(k). Nous avons
k̸=j
Pr[x = 0] ≤ Pr[x(i) = 0]
= Pr[x(i) = 0 | b = 0] · Pr[b = 0] +
Pr[x(i) = 0 | b ̸= 0] · Pr[b ̸= 0]
≤ Pr[v(j) = 0] · Pr[b = 0] +
Pr[v(j) = 1] · Pr[b ̸= 0]
= (1/2) · Pr[b = 0] +
(1/2) · Pr[b ̸= 0]
= 1/2,
où (A.1) découle du fait que
x(i) = 0 ⇐⇒ [v(j) = b = 0 ∨ (v(j) = 1 ∧ b = −a)].
∑
n/2
( )
≤ i · pi · (1/2)i−1 (par (A.3))
i=1
∑
n/2
( )
≤ i · (1/2)i−1 (par (A.2))
i=1
∞
∑ ( )
≤ i · (1/2)i−1 (car chaque terme est non négatif)
i=1
1
= (série géométrique dérivée)
(1 − 1/2)2
= 4.
⋆⋆ Voici une preuve de Gabriel McCarthy (A2019) qui montre que
E[X] < 2, et par conséquent que le nombre espéré d’itérations n’excède
pas celui de l’algorithme 51. Observons d’abord que:
∏
i−1
(n/2 − 1)(n/2 − 2) · · · (n/2 − i + 1)
qj =
j=1
(n − 1)(n − 2) · · · (n − i + 1)
n/2 ( ( ) ( ))
∑ n−i n−1
= i · pi · / (par (A.4))
i=1
n/2 n/2
n/2 ( ( ) ( ))
∑ n/2 n−i n−1
= i· · /
i=1
n−i n/2 n/2
∑
n−1 ( ( ) ( ))
n−j j n−1
= n/2 · · /
j n/2 n/2
j=n/2
n/2 ∑ ( n − j ( j ))
n−1
= (n−1) · ·
n/2
j n/2
j=n/2
( ) ∑ ( j )
n/2 ∑ n
n−1 n−1
j
= (n−1) · · −
n/2
j n/2 n/2
j=n/2 j=n/2
n/2 n ∑ ( j−1 )
n−1 ∑ ( j )
n−1
= (n−1) · · − (par (A.5))
n/2
n/2 n/2 − 1 n/2
j=n/2 j=n/2
[ ) ( ( )]
n/2 n n−1 n
= (n−1) · · − (par (A.5))
n/2
n/2 n/2 n/2 + 1
( n )
n/2+1
= n − (n/2) · (n−1)
n/2
(n−1)
n n/2
= n − (n/2) · ·( ) (par (A.5))
n/2 + 1 n−1n/2
n
= n − (n/2) ·
n/2 + 1
n2
=n−
n+2
n2 + 2n n2
= −
n+2 n+2
2n
=
n+2
2(n + 2) − 4
=
n+2
4
=2−
n+2
< 2.
B
Fiches récapitulatives
Les fiches des pages suivantes résument le contenu de chacun des chapitres.
Elles peuvent être imprimées recto-verso, ou bien au recto seulement afin d’être
découpées et pliées en deux. À l’ordinateur, il est possible de cliquer sur la
plupart des puces « ▶ » pour accéder à la section du contenu correspondant.
153
1. Analyse des algorithmes
2. Tri
Sommaire
Approche générique Complexité (par cas)
Algorithme Sur place Stable
▶ Inversion: indices (i, j) t.q. i < j et s[i] > s[j] meilleur moyen pire
insertion Θ(n) Θ(n2 ) Θ(n2 ) 3 3
▶ Progrès: corriger une inversion en diminue la quantité monceau Θ(n) Θ(n log n) Θ(n log n) 3 7
▶ Procédure: sélectionner et corriger une inversion, jusqu’à ce fusion Θ(n log n) Θ(n log n) Θ(n log n) 7 3
qu’il n’en reste plus rapide Θ(n) Θ(n log n) Θ(n2 ) 3 7
Usage
Algorithmes (par comparaison) ▶ Petite taille: tri par insertion
▶ Insertion: considérer s[1 : i−1] triée et insérer s[i] dans s[1 : i] ▶ Grande taille: tri par monceau ou tri rapide
▶ Monceau: transformer s en monceau et retirer ses éléments ▶ Grande taille + stabilité: tri par fusion
▶ Fusion: découper s en deux, trier chaque côté et fusionner
Tri sans compraison
▶ Rapide: réordonner autour d’un pivot et trier chaque côté
▶ Par comparaison: barrière théorique de Ω(n log n)
Propriétés ▶ Sans comparaison: possible de faire mieux pour certains cas
▶ Sur place: n’utilise pas de séquence auxiliaire ▶ Représentation binaire: trier en ordonnant du bit de poids
faible vers le bit de poids fort
▶ Stable: l’ordre relatif des éléments égaux est préservé
▶ Complexité: Θ(mn) où m = nombre de bits et n = |s|
3. Graphes
Graphes Représentation Mat. Liste (non dirigé) Liste (dir.)
u− → v? Θ(1) O(min(deg(u), deg(v))) O(deg+ (u))
▶ Graphe: G = (V , E) où V = sommets et E = arêtes
a b c
[a 7→ [b,c], {v : u −
→ v} Θ(|V |) O(deg(u)) O(deg+ (u))
a 0 1 1
0 b → 7 {u : u −
→ v} Θ(|V |) O(deg(v)) O(|V | + |E|)
▶ Dirigé vs. non dirigé: {u, v} ∈ E vs. (u, v) ∈ E b 1 0 [a],
Modif. u −
→v O(deg(u) + deg(v)) O(deg+ (u))
c 0 1 0 c → 7 [b]] Θ(1)
▶ Degré (cas non dirigé): deg(u) = # de voisins Mémoire Θ(|V |2 ) Θ(|V | + |E|)
▶ Degré (cas dirigé): deg− (u) = # préd., deg+ (u) = # succ. Propriétés et algorithmes
▶ Taille: |E| ∈ Θ(somme des degrés) et |E| ∈ O(|V |2 ) ▶ Plus court chemin: parcours en largeur + stocker préd.
▶ Ordre topologique: u1 ⪯ · · · ⪯ un où i < j =⇒ (uj , ui ) ̸∈ E
▶ Chemin: séq. u0 −
→ ··· −
→ uk (taille = k, simple si sans rép.)
▶ Tri topologique: mettre sommets de degré 0 en file, retirer en
▶ Cycle: chemin de u vers u (simple si sans rép. sauf début/fin) mettant les degrés à jour, répéter tant que possible
▶ Sous-graphe: obtenu en retirant sommets et/ou arêtes ▶ Détec. de cycle: tri topo. + vérifier si contient tous sommets
▶ Composante: sous-graphe max. où sommets access. entre eux ▶ Temps d’exécution: tous linéaires
Parcours Arbres
▶ Profondeur: explorer le plus loin possible, puis retour (pile) ▶ Arbre: graphe connexe et acyclique (ou prop. équivalentes)
▶ Largeur: explorer successeurs, puis leurs succ., etc. (file) ▶ Forêt: graphe constitué de plusieurs arbres
▶ Temps d’exécution: O(|V | + |E|) ▶ Arbre couv.: arbre qui contient tous les sommets d’un graphe
4. Algorithmes gloutons
Arbres couvrants minimaux Ensembles disjoints
▶ Graphe pondéré: G = (V, E) où p[e] est le poids de l’arête e ▶ But: manipuler une partition d’un ensemble V
∑
▶ Poids d’un graphe: p(G) = e∈E p[e] ▶ Représentation: chaque ensemble sous une arborescence
▶ Arbre couv. min.: arbre couvrant de G de poids minimal {a} {b, c, d, e} {f, g} init(V ) Θ(|V |)
Algorithmes a b f trouver (v) O(log |V |)
union(u, v) O(log |V |)
▶ Prim–Jarník: faire grandir un arbre en prenant l’arête min. c d e g
▶ Complexité: O(|E| log |V |) avec monceau
Algorithme glouton
▶ Kruskal: connecter forêt avec l’arête min. jusqu’à un arbre
1) Choisir un candidat c itérativement (sans reconsidérer)
▶ Complexité: O(|E| log |V |) avec ensembles disjoints
2) Ajouter c à solution partielle S si admissible
Prim–Jarník Kruskal 3) Retourner S si solution (complète), « impossible » sinon
2 5 2 5 2 5 2 5 2 5 2 5
b d f b d f b d f b d f b d f b d f
2 2 2 2 2 2
3 3 3 3 3 3
c e g c e g c e g c e g c e g c e g
2 5 2 5 2 5 2 5 2 5 2 5
b d f b d f b d f b d f b d f b d f
2 2 2 2 2 2
3 3 3 3 3 3
c e g c e g c e g c e g c e g c e g
1 4 1 4 1 4 1 4 1 4 1 4
2
b
2
d
5
f
2
b
2
d
5
f
▶ Fonctionne seulement si on peut découper objets
1 1
a 4 3 2 a 4 3 2
3
c
1
e
4
g
3
c
1
e
4
g ▶ Approxime solution discrète à facteur 1/2
6. Force brute
Approche
▶ Exhaustif : essayer toutes les sol. ou candidats récursivement Problème des n dames
▶ But: placer n dames sur échiquier sans attaques
▶ Explosion combinatoire: souvent # solutions ≥ bn , n!, nn
▶ Algo.: placer une dame par ligne en essayant colonnes dispo.
▶ Avantage: simple, algo. de test, parfois seule option
▶ Désavantage: généralement très lent et/ou avare en mémoire Sac à dos
▶ But: maximiser valeur sans excéder capacité
Techniques pour surmonter explosion ▶ Algo.: essayer sans et avec chaque objet
▶ Élagage: ne pas développer branches inutiles ▶ Mieux: élaguer dès qu’il y a excès de capacité
▶ Contraintes: élaguer si contraintes enfreintes ▶ Mieux++: élaguer si aucune amélioration avec somme valeurs
▶ Bornes: élaguer si impossible de faire mieux
Retour de monnaie
▶ Approximations: débuter avec approx. comme meilleure sol.
▶ But: rendre montant avec le moins de pièces
▶ Si tout échoue: solveurs SAT ou d’optimisation
▶ Algo.: pour chaque pièce, essayer d’en prendre 0 à # max.
7. Programmation dynamique
Approche
▶ Principe d’optimalité: solution optimale obtenue en combi-
Plus courts chemins
nant solutions de sous-problèmes qui se chevauchent
▶ Plus court chemin: chemin simple de poids minimal
▶ Descendante: algo. récursif + mémoïsation (ex. Fibonacci)
▶ Bien défini: si aucun cycle négatif
▶ Ascendante: remplir tableau itér. avec solutions sous-prob.
▶ Approche générale: raffiner distances partielles itérativement
Retour de monnaie
▶ Dijkstra: raffiner en marquant sommet avec dist. min.
▶ Sous-question: # pièces pour rendre j avec pièces 1 à i?
▶ Floyd-Warshall: raffiner via sommet intermédiaire vk
▶ Identité: T [i, j] = min(T [i − 1, j], T [i, j − s[i]] + 1)
▶ Bellman-Ford: raffiner avec ≥ 1, 2, . . . , |V | − 1 arêtes
▶ Exemple: montant m = 10 et pièces s = [1, 5, 7]
▶ Sommaire:
0 1 2 3 4 5 6 7 8 9 10
0 0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ Dijkstra Bellman-Ford Floyd-Warshall
1 0 1 2 3 4 5 6 7 8 9 10 Types de chemins d’un sommet vers les autres paires de sommets
2 0 1 2 3 4 1 2 3 4 5 2 Poids négatifs? 7 3 3
3 0 1 2 3 4 1 2 1 2 3 2 Temps d’exécution O(|V | log |V | + |E|) Θ(|V | · |E|) Θ(|V |3 )
Sac à dos Temps (|E| ∈ Θ(1)) O(|V | log |V |) Θ(|V |) Θ(|V |3 )
Temps (|E| ∈ Θ(|V |)) O(|V | log |V |) Θ(|V |2 ) Θ(|V |3 )
▶ Sous-question: val. max. avec capacité j et les objets 1 à i? Temps (|E| ∈ Θ(|V |2 )) O(|V |2 ) Θ(|V |3 ) Θ(|V |3 )
▶ Identité: T [i, j] = max(T [i − 1, j], T [i − 1, j − p[i]] + v[i])
157
Index
N, 3 analyse, 13
O, 15 approche
Ω, 19 ascendante, 104
P, 2 descendant, 103
Q, 3 approximation, 69
R, 3 arborescence, 55
Θ, 20 arbre, 55
Z, 3 couvrant, 55
couvrant minimal, 59
accessibilité, 50, 114 de récursion, 82
Ackermann, 65 arithmétique, 79, 81
acyclique, 48, 54 arête, 46
adjacence, 46
algorithme Bellman-Ford, 114
d’approximation, 69 bijection, 4
de Bellman-Ford, 114 binaire, 43
de Dijkstra, 108 branch-and-bound, 97
de Floyd-Warshall, 110
de Freivalds, 129 carré, 6
de Karatsuba, 81 chemin, 48
de Karger, 123 plus court, 107
de Kruskal, 61 simple, 48, 107
de Las Legas, 122 coefficient binomial, 4
de Monte Carlo, 123 combinatoire, 4
de Prim–Jarník, 59 complément, 2
déterministe, 119 composante
glouton, 59 connexe, 48
probabiliste, 119 fortement connexe, 48
récursif, 73 connexité, 48
vorace, 59 constante multiplicative, 15, 19, 20
amplification, 126 correction, 27
158
INDEX 159