Algorithmique
Algorithmique
Chapitre 9
Algorithmique
On désigne par algorithmique l'ensemble des activités logiques qui relèvent des algorithmes ; en
particulier, en informatique, cette discipline désigne l'ensemble des règles et des techniques qui sont
impliquées dans la définition et la conception des algorithmes.
Le problème des huit dames est un bon exemple de problème simple mais non évident. Pour cette
raison, il est souvent employé comme support de mise en œuvre de différentes techniques de
programmation, y compris d'approches non traditionnelles de la programmation telles que la
programmation par contraintes, la programmation logique ou les algorithmes génétiques.
Exercice 9.1
Écrivez en pseudo-code, puis programmez l'algorithme naïf pour trouver les 92 solutions au
problème des huit dames. Vous représenterez la position par une liste de nombres de 0 à 7. Ces
nombres indiquent la ligne sur laquelle la dame se trouve pour la colonne correspondante. Par
exemple, la 12ème solution du graphique ci-dessus sera représentée par la liste : [2, 4, 1, 7, 0, 6, 3, 5].
Exercice 9.2
Programmez la recherche en profondeur expliquée ci-dessus. Comptez le nombre de situations
(intermédiaires et finales) analysées.
Exercice 9.3
Placez initialement les 8 dames sur la diagonale de l'échiquier, puis échangez deux colonnes
choisies au hasard. Répétez l'opération jusqu'à ce qu'une solution soit trouvée.
Exercice 9.4
Modifiez le programme de l'exercice 9.3 pour trouver les 92 solutions.
Le programme ci-dessous pourra vous aider. Après avoir vu ce qu'il produit, insérez-le dans votre
programme.
for p in permutations([0,1,2]):
print(p)
Exercice 9.5
Une route comporte n stations-service, numérotées dans l'ordre du parcours, de 0 à n-1. La
première est à une distance d [0] du départ, la deuxième est à une distance d [1] de la première, la
troisième à une distance d [2] de la deuxième, etc. La fin de la route est à une distance d [n] de la
n-ième et dernière station-service.
Un automobiliste prend le départ de la route avec une voiture dont le réservoir d'essence est plein.
Sa voiture est capable de parcourir une distance r avec un plein.
Question 9.5.1
Donnez une condition nécessaire et suffisante pour que l'automobiliste puisse effectuer le
parcours. On la supposera réalisée par la suite.
Question 9.5.2
Prenez 17 stations-service avec les distances d = [23, 40, 12, 44, 21, 9, 67, 32, 51, 30, 11, 55, 24,
64, 32, 57, 12, 80] et r = 100.
L'automobiliste désire faire le plein le moins souvent possible. Écrivez en pseudo-code, puis
programmez une fonction Python rapide qui détermine à quelles stations-service il doit s'arrêter.
Exercice 9.6
Un cambrioleur entre par effraction dans une maison. Il n'est capable de porter que K kilos : il lui
faudra donc choisir entre les différents objets de valeur, afin d'amasser le plus gros magot possible.
On supposera dans un premier temps que les objets sont fractionnables (on peut en prendre
n'importe quelle quantité, c'est le cas d'un liquide ou d'une poudre). Il y a n matières différentes,
numérotées de 0 à n-1, la i-ème ayant un prix p[i] par kilo. La quantité disponible de cette matière
est q[i]. On suppose que tous les prix sont différents deux à deux.
Question 9.6.1
Écrivez en pseudo-code un algorithme qui donne un choix optimal pour le voleur. Ce choix est-il
unique ?
Programmez une fonction voleur en Python qui reprenne cet algorithme (vous pourrez supposer
que le tableau p est trié).
Prenez par exemple : p = [43, 40, 37, 33, 28, 25, 20, 17, 14, 13]
q = [ 7, 6, 12, 11, 2, 23, 1, 4, 24, 43]
K = 55
Question 9.6.2
On suppose maintenant que les objets sont non fractionnables (c'est le cas d'un vase ou d'un
téléviseur). Le i-ème objet vaut un prix p[i] et pèse un poids q[i].
Proposez une méthode dérivée de la question 1 (sans la programmer).
Donne-t-elle un choix optimal ?
Exercice 9.7
Dans un cinéma, chaque séance i est caractérisée par l'intervalle (di , fi), où di est l'heure de début
et fi l'heure de fin. On peut représenter ces séances sur un schéma d'intervalles :
a. Décrivez en pseudo-code un algorithme glouton permettant de choisir les séances, après les avoir
classées selon l'un des critères ci-dessus.
b. Pour chacun des trois critères de classement, exhibez un cas (s'il en existe un) où votre
algorithme ne donnera pas un choix optimal. Donnez un exemple avec 5 séances sous forme d'un
schéma d'intervalles comme celui de l'énoncé du problème.
c. Appliquez votre algorithme aux séances ci-dessous. Donner le résultat pour chacun des trois
critères de classement.
Séance (i) 1 2 3 4 5 6 7 8 9 10
Début (di) 9h 9h15 10h 13h 15h 15h15 16h 17h30 18h 19h30
Fin (fi) 11h 10h50 11h20 15h25 16h40 18h15 18h05 19h 20h10 22h
Durée (fi - di) 120 95 80 145 100 180 125 90 130 150
d. Vous voulez absolument assister à la séance 9. Modifiez votre algorithme pour intégrer cette
contrainte supplémentaire, puis répondez à nouveau à la question c.
On peut montrer que la complexité temporelle en moyenne et dans le pire des cas d'un algorithme
basé sur une fonction de comparaison ne peut pas être meilleure que O(nlog(n)). Les tris qui ne
demandent que O(nlog(n)) comparaisons en moyenne sont alors dits optimaux.
Dans les algorithmes ci-dessous, on effectuera des tris par ordre croissant.
def swap(l,i,j):
# echange 2 valeurs d'une liste
l[i],l[i] = l[j], l[i]
def tri_selection(l):
for i in range(len(l)-1):
mini=i
for j in range(i+1,len(l)):
if l[j]<l[mini]: mini=j
swap(l,i,mini)
Complexité
• Meilleur des cas : O(n2) quand le tableau est déjà trié.
• Pire cas : O(n2) quand le tableau est trié en ordre inverse.
• En moyenne : O(n2).
def swap(l,i,j):
# echange 2 valeurs d'une liste
l[i],l[j] = l[j],l[i]
def tri_a_bulles(l):
for i in range(len(l)):
for j in reversed(range(i,len(l))):
if l[j]<l[j-1]:
swap(l,j-1,j)
Complexité
• Meilleur cas : O(n) quand le tableau est trié.
• Pire des cas : O(n2) quand le tableau est trié en ordre inverse (n-1)·(n-1) = O(n2)
Le principe de ce tri est très simple : c'est le tri que toute personne utilise naturellement quand
elle a des dossiers (ou n'importe quoi d'autre) à classer. On prend un dossier et on le met à sa place
parmi les dossiers déjà triés. Puis on recommence avec le dossier suivant.
Pour procéder à un tri par insertion, il suffit de parcourir une liste : on prend les éléments dans
l'ordre. Ensuite, on les compare avec les éléments précédents jusqu'à trouver la place de l'élément
qu'on considère. Il ne reste plus qu'à décaler les éléments du tableau pour insérer l'élément considéré
à sa place dans la partie déjà triée.
def tri_insertion(liste):
j, n = 1, len(liste)
while j != n:
i = j - 1
temp = liste[j]
while i > -1 and liste[i] > temp:
liste[i+1] = liste[i]
i = i - 1
liste[i+1] = temp
j = j + 1
Complexité
• Pire cas : O(n2) quand le tableau est trié en ordre inverse
• Moyenne : O(n2)
9.4.4. Quicksort
Le Quicksort est une méthode de tri inventée par Sir Charles Antony Richard Hoare en 1961 et
fondée sur la méthode de conception « diviser pour régner ». Il peut être implémenté sur un tableau
ou sur des listes ; son utilisation la plus répandue concerne tout de même les tableaux.
Le Quicksort est un tri dont la complexité moyenne est en O(nlog(n)), mais dont la complexité
dans le pire des cas est un comportement quadratique en O(n2). Malgré ce désavantage théorique,
c'est en pratique un des tris les plus rapide pour des données réparties aléatoirement. Les entrées
donnant lieu au comportement quadratique dépendent de l'implémentation de l'algorithme, mais sont
Charles Antony souvent (si l'implémentation est maladroite) les entrées déjà presque triées. Il sera plus avantageux
Richard Hoare alors d'utiliser le tri par insertion.
(né en 1934)
La méthode consiste à placer un élément du tableau (appelé pivot) à sa place définitive, en
permutant tous les éléments de telle sorte que tous ceux qui lui sont inférieurs soient à sa gauche et
que tous ceux qui lui sont supérieurs soient à sa droite. Cette opération s'appelle le partitionnement.
Pour chacun des sous-tableaux, on définit un nouveau pivot et on répète l'opération de
partitionnement. Ce processus est répété récursivement, jusqu'à ce que l'ensemble des éléments soit
trié.
Dans la pratique, pour les partitions avec un faible nombre d'éléments (jusqu'à environ 15
éléments), on a souvent recours à un tri par insertion qui se révèle plus efficace que le Quicksort.
Le problème le plus important est le choix du pivot. Une implémentation du Quicksort qui ne
choisit pas adéquatement le pivot sera très inefficace pour certaines entrées. Par exemple, si le pivot
est toujours le plus petit élément de la liste, Quicksort sera aussi inefficace qu'un tri par sélection.
Complexité
• Pire des cas : O(n2) quand le tableau est trié en ordre inverse
• En moyenne et dans le meilleur des cas : O(nlog(n))
def tri_fusion(liste):
def tri_fusion_interne(liste):
if len(liste)<2 :
return liste
return merge(tri_fusion_interne(liste[:len(liste)//2]),
tri_fusion_interne(liste[len(liste)//2:]))
liste[:] = tri_fusion_interne(liste)
def heapsort(tab):
n = len(tab)
debut = n//2 - 1
fin = n - 1
while debut >= 0:
faire_tas(tab, debut, n)
debut -= 1
while fin > 0:
tab[fin], tab[0] = tab[0], tab[fin]
faire_tas(tab, 0, fin)
fin -= 1
On dira que :
Exercice 9.8
Écrivez un programme Python qui implémente la méthode vue ci-dessus. Le polygone sera donné
par la liste de ses sommets. Vous trouverez sur le site compagnon une ébauche à compléter.
En trois dimensions, l'idée serait la même avec un ballon qui se dégonflerait jusqu'à être en
contact avec tous les points qui sont à la surface de l'enveloppe convexe.
Exercice 9.9
Écrivez un programme Python qui implémente la marche de Jarvis. Vous trouverez sur le site
compagnon une ébauche à compléter.
L'algorithme considère ensuite successivement les séquences de trois points contigus dans le
tableau T, vus comme deux segments successifs. On regarde ensuite si ces deux segments mis bout à
bout constitue un « tournant à gauche » ou un « tournant à droite ».
• Si l'on rencontre un « tournant à droite », l'algorithme passe au point suivant de T.
• Si c'est un « tournant à gauche », cela signifie que l'avant-dernier point considéré (le
deuxième des trois) ne fait pas partie de l'enveloppe convexe, et qu'il doit être enlevé de T.
Cette analyse se répète ensuite, tant que l'ensemble des trois derniers points est un
« tournant à gauche » .
Le processus se terminera quand on retombera sur le point P0. T contiendra alors les points
formant l'enveloppe convexe.
Complexité
Le tri des points peut se faire avec une complexité en temps en O(nlog(n)). La complexité de la
boucle principale peut sembler être en O(n2), parce que l'algorithme revient en arrière à chaque point
pour évaluer si l'un des points précédents est un « tournant à droite ». Mais elle est en fait en O(n),
parce que chaque point n'est considéré qu'une seule fois. Ainsi, chaque point analysé soit termine la
sous-boucle, soit est retiré de T et n'est donc plus jamais considéré. La complexité globale de
l'algorithme est donc en O(nlog(n)), puisque la complexité du tri domine la complexité du calcul
effectif de l'enveloppe convexe.
Exercice 9.10
Écrivez un programme Python qui implémente le parcours de Graham. Vous trouverez sur le site
compagnon une ébauche à compléter.
1. Commencer par un point extrême p0 (on a choisi p0 le point le plus bas dans l'illustration ci-
dessous). On sait que ce point appartient à l'enveloppe convexe. Poser i = 0.
2. Pour chacune des sous-enveloppes convexes, trouver le point qk de sorte que tous les points
de la sous-enveloppe k soient à gauche du segment orienté piqk.
3. Parmi ces segments, garder celui qui n'a aucun des autres points qj à sa droite. C'est un bord
de l'enveloppe convexe. Appeler q l'extrémité terminale de ce segment orienté.
4. Poser i := i + 1 et pi := q. Retourner en 2 tant pi p0 et i < m.
5. Si pi = p0, l'enveloppe convexe est la suite des points [p0, p1, …, pi], sinon recommencer la
phase 1 avec un m plus grand.
Timothy M. Chan
(né en 1976)
Sources
[1] Wikipédia, « Algorithmique », <[Link]
[2] Wikipédia, « Algorithmes de tri »,
<[Link]
[3] Wikipédia, « Marche de Jarvis », <[Link]
[4] Wikipédia, « Parcours de Graham », <[Link]
[5] Wikipédia, « Algorithme de Chan », <[Link]