Vous êtes sur la page 1sur 16

Chapitre 3

La force brute

Après avoir introduit le cadre et les méthodes d’analyse des algorithmes au chapitre précédent, nous
sommes maintenant prêts pour entamer la discussion sur les techniques de conception des
algorithmes. Chacun des neuf chapitres suivants est consacré à une stratégie particulière de
conception. Le thème de ce chapitre est la force brute, la plus simple des stratégies. Elle peut être
décrite de la façon suivante :
La force brute est une approche directe pour résoudre un problème, habituellement basée
directement sur l’expression du problème et la définition des concepts impliqués.

La force impliquée par la définition de la stratégie est celle d’un ordinateur et non celle de
l’intelligence. « Fait le juste ! » serait une autre façon de décrire la prescription de l’approche par la
force brutale. Et très souvent, la stratégie de la force brutale est en effet celle qui est la plus simple à
appliquer.

Comme exemple, considération le problème de l’exponentiation : calculer a n pour un nombre


donné a et un entier naturel n. Même si ce problème peut être considéré comme trivial, il nous offre
un support utile pour illustrer plusieurs techniques de conception des algorithmes, incluant
l’approche de la force brute.
Par définition de l’exponentiation,
a  a*
n
a 
*a
n fois

Ceci suggère de calculer a n en multipliant simplement a par a n fois.


Nous avons déjà rencontré au moins deux algorithmes de la force brute : l’algorithme de la
comparaison des entiers successifs pour le calcul du GCD(m, n) et la multiplication des matrices
basée sur la définition. Plusieurs autres exemples sont donnés dans la suite de ce chapitre.
Bien qu’étant rarement une source d’algorithmes intelligents ou efficaces, l’approche de la force
brute ne devrait pas être négligée comme une approche importante de conception des algorithmes.
D’abord, contrairement aux autres stratégies, la force brute est applicable à une grande variété de
problèmes. Deuxièmement, pour certains problèmes importants (ex : tri, recherche, multiplication
matricielle, correspondance de chaînes), l’approche par la force brute produit des algorithmes
raisonnables au moins pour certaines valeurs pratiques avec aucune limite sur la taille des exemples.
Troisièmement, le temps perdu pour concevoir un algorithme plus efficace peut parfois être
injustifié si quelques exemples seulement du problème doivent être résolus alors que l’on a un
algorithme de la force brute capable de résoudre ces exemples avec une efficacité acceptable.
Quatrièmement, bien que trop inefficace en général, un algorithme de la force brute peut encore être
utile pour résoudre des exemples de petite taille d’un problème. Enfin, un algorithme de la force
brute peut servir un objectif théorique ou éducationnel important, par exemple, comme une
référence par rapport à laquelle on juge des alternatives plus efficaces pour résoudre un problème.

3.1 Le tri par sélection et le tri par échanges


Dans cette section, nous considérons l’application de l’approche de la force brute au problème de
tri : étant donné une liste de n éléments ordonnables (ex : des nombres, des caractères, des chaînes
de caractères), réarranger ces éléments pour les mettre dans un ordre non décroissant. Comme nous
60
l’avons mentionné au paragraphe 1.3, plusieurs algorithmes ont été développés pour résoudre ce
problème important.
La question maintenant est de savoir lequel de ces algorithmes est le plus simple. Les deux
algorithmes que nous étudions ici sont le tri par sélection et le tri par échanges qui semblent être les
premiers candidats. Le premier de ces deux algorithmes est un meilleur algorithme en général, et il
implémente plus clairement l’approche de la force brute.

3.1.2 Le tri par sélection

On commence le tri par sélection en parcourant la liste entière pour trouver le plus petit élément et
permuter celui-ci avec le premier élément de la liste, plaçant ainsi le plus élément dans sa position
finale dans la liste triée. On parcourt ensuite la liste en commençant par le deuxième élément, pour
trouver le plus petit parmi les n – 1 derniers éléments et permuter celui-ci avec le deuxième
élément, plaçant ainsi le deuxième plus petit élément dans sa position finale. Généralement, au ième
parcours de la liste, l’algorithme cherche le plus petit parmi les n – i derniers éléments pour le
permuter avec le ième élément :

A  A1    A i 1 A i ,  , A min ,  , A n 1
0  
          
dans leur position finale les n 1 derniers éléments

Après n – 1 passages, la liste est triée.

Voici un pseudocode de cet algorithme, pour plus de simplicité nous supposons que la liste est
implémentée sous forme de vecteur.

ALGORITHME SelectionSort(A[0..n – 1])


//Trie un vecteur à l’aide du tri par sélection.
//Input : Un vecteur A[0..n – 1] d’éléments ordonnables
//Output : Le vecteur A[0..n – 1] trié par ordre croissant.
for i  0 to n – 2 do
min  i ;
for j  i + 1 to n – 1 do
if A[j]  A[min]
min  j ;
swap(A[i], A[min])
Comme exemple, l’action de l’algorithme sur la liste 89, 45, 68, 90, 29, 34, 17 est illustrée dans la
Figure 3.1.

89 45 68 90 29 34 17
17 45 68 90 29 34 89
17 29 68 90 45 34 89
17 29 34 90 45 68 89
1 29 34 45 90 68 89
17 29 34 45 68 90 89
17 29 34 45 68 89 90

FIGURE 1.3 – Exemple de tri avec le tri par sélection. Chaque ligne correspond à une itération de
l’algorithme. Les éléments à gauche de la barre verticale sont dans leurs positions finales et ne sont
pas considérés dans cette itération les suivantes.

61
L’analyse du tri par sélection est évidente. La taille de l’input est donnée par le nombre d’éléments
n de la liste ; l’opération de base est la comparaison A[j]  A[min]. Le nombre de fois que cette
comparaison est exécutée dépend de la taille de la liste et est donné par la somme :
n2 n 1 n2 n2

C (n)    1   ( n  1)  ( i  1)  1   ( n  1  i ).
i  0 j  i 1 i0 i0

Nous avons déjà rencontré la dernière somme en analysant l’algorithme de l’Exemple 2 de la


Section 2.3. Que vous la calculiez en distribuant le symbole de sommation ou en trouvant
immédiatement la somme des entiers décroissants, la réponse sera bien évidemment la même :
n2 n 1 n2
( n  1) n
C (n)   1  (n  1  i)  2
.
i  0 j  i 1 i0

Donc le tri par sélection est un algorithme  n 2  pour toutes les entrées. Noter cependant que le
nombre de permutations clés est seulement   n  ou, plus précisément n  1 (une pour chaque
itération de la boucle sur i). Cette propriété distingue positivement le tri par sélection de plusieurs
autres algorithmes de tri.

3.1.2 Le tri par échanges


Une autre application de la force pour le problème de tri est de comparer les éléments consécutifs
de la liste et les permuter s’ils ne sont pas dans le bon ordre. En faisant cela de façon répétitive, on
finit par amener le plus grand élément dans la dernière position de la liste. La prochaine itération
amène le deuxième plus grand élément dans l’avant dernière position et ainsi de suite. Après n  1
itérations, la liste est complètement triée. La ième itération ( 0  i  n  2 ) du tri par échanges peut
être représentée par le diagramme suivant :
?
A 0 ,  , A j  A j  1 ,  , A n  i 1 A n  i    A n 1
   
dans leur position finale

Voici un pseudocode de cet algorithme.


ALGORITHME BubbleSort(A[0..n – 1])
//Trie un vecteur à l’aide du tri par échange.
//Input : Un vecteur A[0..n – 1] d’éléments ordonnables
//Output : Le vecteur A[0..n – 1] trié par ordre croissant.
for i  0 to n – 2 do
for j  0 to n –2 – i do
if A[j+1]  A[j]
swap(A[j], A[j+1])

L’action de l’algorithme sur la liste 89, 45, 68, 90, 29, 34, 17 est illustrée comme exemple dans la
Figure 3.1. Le nombre de comparaisons clés correspondant à la version du tri par échanges ci-
dessus est le même pour toutes les listes de taille n ; il est obtenu par une somme presque identique
à la somme associée au tri par sélection :
n 2 n 2i n2

C (n)   1  ( n  2  i )  0  1
i0 j0 i0

n2
( n  1) n
  (n  1  i)   n .  2

i0 2

62
Le nombre de permutations clés dépend cependant de l’entée. Pour le pire cas des vecteurs
décroissants, il est identique au nombre de comparaisons clés :
( n  1) n
C w (n)  C (n)   
 n .
2

?
89  45 68 90 29 34 17
?
45 89  68 90 29 34 17
? ?
45 68 89  90  29 34 17
?
45 68 89 29 90  34 17
?
45 68 89 29 34 90  17
45 68 89 29 34 17 | 90

? ? ?
45  68  89  29 34 17 | 90
?
45 68 29 89  34 17 | 90
?
45 68 29 34 89  17 | 90
45 68 29 34 17 | 89 90
etc.

FIGURE 3.2 – Les deux premières étapes du tri par échange sur la liste 89, 45, 68, 90, 29, 34, 17.
Une nouvelle ligne est montrée après le swap de deux éléments. Les éléments à droite de la barre
verticale sont dans leurs positions finales et ne sont pas considérés dans l’étape et les suivantes.
Comme cela est souvent le cas avec une application de la stratégie de la force brute, la première
version d’un algorithme obtenue peut souvent être améliorée avec un effort modeste. Spécialement,
nous pouvons améliore la version primaire du tri par échanges présentée ci-dessus en exploitant
l’observation suivante : si un passage à travers la liste n’effectue aucun échange, alors la liste est
triée et nous pouvons déjà arrêter l’algorithme. Bien que la nouvelle version s’exécute plus
rapidement pour certaines entrées, elle est toujours dans  n 2  dans les pire et moyen cas. En
réalité, même parmi les méthodes de tri élémentaires, le tri par échanges est un choix inférieur, et ce
n’état pas à cause de son nom plaisant, vous n’aurez probablement pas entendu parlé de lui.
Toutefois, la leçon générale que vous avez juste apprise est importante et bonne à répéter : Une
première application de l’approche de la force brutale résulte souvent en un algorithme qui peut
être amélioré avec une quantité infime d’effort.

Exercices 3.1
Problème 1. a) Donnez un exemple d’algorithme qui ne sera pas considéré comme une application
de l’approche de la force brute.
b) Donnez un exemple de problème qui ne peut pas être résolu par un algorithme de la force brute.

Problème 2. a) Quelle est l’efficacité de l’algorithme de la force brute pour le calcul de a n comme
une fonction de n ? Comme une fonction du nombre des bits de la représentation binaire de n ?

b) Si vous devez calculer a n mod m , où a  1 et n est un grand entier positif, comment pouvez-
vous contourner le problème d’une très magnitude de a n ?

Problème 3. a) Ecrire un algorithme de la force brute pour évaluer le polynôme


63
n 1
p ( x )  a n x  a n 1 x    a1 x  a 0
n

en un point donné x 0 et déterminer la classe d’efficacité au pire.


b) Si l’algorithme que vous avez conçu est dans  n 2  , concevoir un algorithme linéaire pour ce
problème.
c) Est-il possible de concevoir un algorithme ayant une efficacité meilleure que l’efficacité linéaire
pour ce problème ?

Problème 4. Est-il possible d’implémenter le tri par échanges pour les listes chaînées avec la même
efficacité  n 2  comme pour la version vecteur ?

Problème 5. Le tri par sélection est-il stable ? Le tri par échanges est-il stable ?

Problème 6. a) Montrer que si le tri par échanges n’effectue aucun échange au cours d’un parcours
de la liste, alors la liste est triée et l’algorithme peut être stoppé.
b) Ecrire un pseudocode pour la méthode qui intègre cette amélioration.
c) Montrer que l’efficacité au pire de la version améliorée est quadratique.

Problème 7. Alterner des disques. Vous avez une rangée de 2n disques de deux couleurs, n noirs et
n blancs. Elles alternent : noir, blanc, noir, blanc, et ainsi de suite. Vous voulez avoir tous les
disques noirs à l’extrémité droite et toutes les boules blanches à l’extrémité gauche. Les seuls
mouvements que vous êtes autorisés de faire sont ceux qui inter-changent les positions de deux
disques voisins.

Concevoir un algorithme pour résoudre ce puzzle et déterminer le nombre de mouvements il prend.

3.2 Recherche séquentielle et reconnaissance des chaînes

Nous avons vu dans la section précédente deux applications de l’approche de la force brute au
problème de tri. Nous présentons ici deux applications de cette stratégie au problème de recherche.
Le premier traite du problème classique de la recherche associative dans une liste donnée. Le
deuxième est différent parce qu’il traite du problème de la reconnaissance des chaînes.

3.2.1 Recherche séquentielle


Nous avons déjà rencontré un algorithme de la force brute pour le problème général de la recherche
dans une liste. En guise de rappel, l’algorithme compare simplement les éléments successifs de la
liste donnée avec la clé de recherche jusqu’à ce que l’on trouve une correspondance (recherche
positive) ou que la liste soit épuisée sans trouver une correspondance (recherche négative). Une
simple astuce est souvent utilisée en implémentation la recherche séquentielle : si on ajoute la clé de
recherche à la fin de la liste, la recherche de la clé sera toujours positive, et donc nous pouvons
éliminer le contrôle de la fin liste à chaque itération de l’algorithme. Voici un pseudocode
correspondant à cette version améliorée avec une entrée implémentée sous forme de vecteur.
ALGORITHME SequentialSearch(A[0..n – 1], K)
//Implémente la recherche séquentielle avec une clé de recherche comme sentinelle.
//Input : Un vecteur A de n éléments et une clé de recherche K.
//Output : L’indice du premier élément de A[0..n – 1] donc la valeur est égal à K ou –1 sinon.
A[n]  K
i0

64
while A[i]  K do
ii+1
if i  n return i
else return –1
Une autre amélioration évidente peut être incorporée dans la recherche séquentielle si le vecteur est
trié : la recherche dans une telle liste peut être arrêtée dès qu’un élément supérieur ou égal à la clé
de recherche est trouvé.
La recherche séquentielle offre une excellente illustration de l’approche de la force brute : sa force
caractéristique (simplicité) et sa faiblesse (efficacité inférieure). Le résultat d’efficacité obtenu à la
section 2.1 pour la version standard ne change que très peu pour la solution améliorée, de sorte que
l’algorithme reste linéaire au pire et en moyenne. Nous étudierons dans la suite du cours plusieurs
autres algorithmes de recherche ayant une meilleure efficacité temporelle.

3.2.2 La reconnaissance des chaînes par la force brute


Rappelons le problème de la reconnaissance des chaînes introduit à la section 1.3 : étant donné une
chaîne de n caractères appelée texte et une chaîne de m caractères ( m  n ) appelée forme, trouver
une sous chaîne du texte égale à cette forme. Pour le dire plus précisément, nous voulons trouver un
indice i —l’indice du caractère le plus à gauche de la première sous-chaîne assortissante dans le
texte—tel que t i  p 0 ,  ,t i  j  p j ,  , t i  m 1  p m 1 :

t0  ti  ti j  t i  m 1  t n 1 Texte T
↕ ↕ ↕
p0  pj  p m 1 Forme P

Si d’autres correspondances autres que la première doivent être trouvées, l’algorithme de


reconnaissance des chaînes peut continuer simplement de s’exécuter jusqu’à ce que la chaîne
d’entrée soit épuisée.
Un algorithme de la force brute pour le problème de la reconnaissance des chaînes est tout à fait
évident : on aligne la forme contre les m premiers caractères du texte et on commence à comparer
les paires de caractères correspondants de la gauche jusqu’à ce que toutes les paires correspondent
(alors l’algorithme s’arrête) ou une non correspondance soit trouvée. Dans le dernier cas, on décale
la forme d’une position vers la droite et on recommence les comparaisons, en commençant encore
par le premier caractère de la forme et son homologue dans le texte. Noter que la dernière position
du texte qui peut encore être le début d’une sous-chaîne correspondante est n – m. Au-delà de cette
position, il n’y a plus assez de caractères pour reconnaître la forme entière ; donc l’algorithme ne
doit plus aucune comparaison à partir de là.
ALGORITHME BruteForceStringMatch(T[0..n – 1], P[0..m – 1])
//Implémente la reconnaissance des chaînes par la force brute
//Input : Un vecteur T[0..n – 1] de n caractères représentant un texte et un vecteur P[0..m – 1] de
// m caractères représentant une forme.
//Output : L’indice du premier caractère du texte où commence une correspondance
// ou -1 si la recherche échoue.
for i  0 to n – m do
j0
while j  m and P[j] = T[i + j] do
jj+1
if j = m return i
return – 1

65
Une application de cet algorithme est illustrée à la Figure 3.3.

N O B O D Y _ N O T I C E D _ H I M
N O T
N O T
N O T
N O T
N O T
N O T
N O T
N O T

FIGURE 3.3 Exemple de reconnaissance de forme par la force brute. (Les caractères de la
forme qui sont comparés avec leurs homologues du texte sont en gras)

Noter que pour et exemple, l’algorithme décale la forme presque toujours après une seule
comparaison de caractère. Cependant, le pire cas est plus pire : l’algorithme peut effectuer toutes les
m comparaisons avant de décaler la forme et ce peut arriver pour toutes les n  m  1 tentatives.
Donc dans le pire cas, l’algorithme est dans  (nm ) . Pour une recherche typique d’un mot dans un
texte en langage naturel, nous pouvons espérer que la plupart des décalages arriveront après très peu
de comparaisons. Par conséquent, l’efficacité moyenne sera considérablement meilleure que
l’efficacité au pire. Il a en effet montré que l’efficacité de la recherche dans un texte aléatoire est
linéaire, c’est-à-dire  ( n  m )   ( n ) . Il existe plusieurs algorithmes plus sophistiqués et plus
efficaces pour la recherche des chaînes. Le plus connu parmi eux est celui de R. Boyer and J.
Moore.

Exercices 3.2
Problème 1. Trouver le nombre de comparaisons effectuées par la version avec sentinelle de la
recherché séquentielle.
a) dans le pire cas
b) en moyenne si la probabilité d’une recherche positive est p 0  p  1

Problème 2. Comme cela a été montré à la Section 2.1, le nombre moyen de comparaisons clés
effectuées par la recherche séquentielle (avec sentinelle, sous les hypothèses standard concernant
son entrée) est donné par la formule
p ( n  1)
C avg ( n )   n (1  p )
2

où p est la probabilité d’une recherche positive. Déterminer, pour un n fixé, les valeurs de
p 0  p  1 pour lesquelles cette formule produit la plus grande valeur de C avg ( n ) et la plus petite
valeur de C avg ( n ) .

Problème 3. Déterminer le nombre de comparaisons de caractères qui seront effectués par


l’algorithme de la force brute en cherchant la forme GANDHI dans le texte
THERE_IS_MORE_TO_LIFE_THAN_INCREASING_ITS_SPEED
On suppose que la longueur du texte est connue avant que la recherche commence.

66
Problème 4. Combien de comparaisons (à la fois pour les recherches positives et négatives) seront
effectuées par l’algorithme de la force brute en cherchant chacune des formes suivantes dans une
chaîne binaire de mille zéros ?
a) 00001 b) 10000 c) 01010
Problème 5. En résolvant le problème de la reconnaissance des formes, y aura-t-il un avantage à
comparer la forme et le texte de la droite vers la gauche plutôt que de la gauche vers la droite ?

3.3 Problèmes de la paire la plus proche et de la couverture convexe


Dans cette section, nous considérons une approche directe à deux problèmes bien connus qui
traitent avec un ensemble finie de points du plan. Ces problèmes à côté de leur intérêt théorique, se
produisent dans deux domaines pratiques importent : la géométrie numérique et la recherche
opérationnelle.
3.3.1 Le problème de la paire la plus proche
Le problème de la paire la plus proche demande de trouver deux points les plus proches dans un
ensemble de n points. Pour simplifier, nous considérons le cas bidimensionnel, même si le problème
peut aussi bien se poser pour des points appartenant à des espaces de dimension supérieure. Nous
supposons que les points en question se spécifiés dans une forme standard par leurs coordonnées
cartésiennes (x , y) et que la distance entre deux points Pi  ( x i , y i ) et P j  ( x j , y j ) est la distance
euclidienne standard
d Pi , P j   ( xi  x j )  ( yi  y j )
2 2

L’approche de la force brute pour résoudre ce problème conduit à l’algorithme évident suivant :
calculer la distance entre chaque paire de points distincts et trouver la paire qui a la plus petite
distance. Naturellement, nous n’avons pas besoin de calculer deux fois la distance associée à la
même paire de points. Pour éviter de le faire, nous considérons uniquement les paires de points
( Pi , P j ) pour lesquels i  j.

ALGORITHME BruteForceClosestPoints(P)
//Trouve les points les plus proches dans le plan par la force brute
//Input : Une liste P de n points P1  ( x1 , y 1 ),  , Pn ( x n , y n )
//Output : Les indices index1 et index2 des points les plus proches
dmin  
for i  1 to n – 1 do
for j  i + 1 to n do
d  sqrt ( x i  x j ) 2  ( y i  y j ) 2 
if d  dmim
dmin  d ; index1  i; index2  j
return index1, index2
L’opération de base de cet algorithme est le calcul de la distance euclidienne entre deux points.
Pour éviter le calcul de la racine carrée qui est encore aujourd’hui problématique, l’astuce consiste
tout simplement à ignorer la racine carrée et à comparer les valeurs ( x i  x j ) 2  ( y i  y j ) 2 elles-
mêmes. Ainsi, si nous remplaçons 
d  sqrt ( x i  x j )  ( y i  y j )
2 2
 par
dsqr  ( x i  x j )  ( y i  y j ) , l’opération de base de l’algorithme sera l’élévation au carré d’un
2 2

nombre. Le nombre de fois que cette opération sera exécutée sera calculé de la manière suivante :

67
n 1 n n 1

C (n)    2  2  ( n  i )  2 ( n  1)  ( n  2 )    1  ( n  1) n   ( n ) .
2

i 1 j  i  1 i 1

Au Chapitre 4, nous discutons un algorithme n log n pour ce problème.

3.3.2 Le problème de la couverture convexe


Passons à un autre problème—le calcul de la couverture convexe. Nous commençons par une
définition d’un ensemble convexe.

DEFINITION. Un ensemble de points du plan est dit convexe si pour deux points quelconque P et
Q de l’ensemble, le segment de droite d’extrémités P et Q est contenu dans l’ensemble.

(a) (b)
FIGURE 3.4 – (a) Ensembles convexes. (b) Ensembles qui ne sont pas convexes.

Tous les ensembles présentés à la Figure 3.4a sont convexes, et tel est le cas pour une droite, un
triangle, un rectangle et plus généralement tout polygone convexe, un cercle et le plan tout entier.
Par contre, les ensembles présentés à la Figure 3.4b, tout ensemble fini de deux ou plusieurs points,
la frontière d’un polygone convexe et une circonférence sont des exemples d’ensembles qui sont
non convexes.

FIGURE 3.5 – Interprétation de la bande élastique de la couverture convexe


Nous sommes maintenant prêts pour aborder la notion de couverture convexe. Intuitivement, la
couverture convexe d’un ensemble de n points du plan est le plus petit polygone convexe qui
contient tous ces points (intérieurement ou sur sa frontière). Si cette formulation ne réveille pas

68
votre enthousiasme, considérez le problème en tant celui qui consiste à barricader n tigres endormis
par une barrière ayant la plus courte longueur. Cette interprétation est due à D. Harel [Har92]; elle
est cependant quelque peu effrayante parce que les poteaux de la barrière doivent être érigés juste à
côté de la barrière où certains des tigres dorment! Il y a une autre interprétation beaucoup plus
commode de cette notion. Imaginez que les points en question soient représentés par des onglets
dressés sur une grande feuille de contre-plaqué représentant le plan. Prenez une bande élastique et
étirez-la pour inclure tous les onglets, puis laissez-la revenir à sa place. La couverture convexe est le
domaine délimité par la bande élastique cassée.
Une définition formelle de la couverture convexe qui est applicable à un ensemble arbitraire,
incluant les ensembles de points pouvant être situés sur la même droite est la suivante.

DEFINITION. La couverture convexe d’un ensemble de points est le plus petit ensemble convexe
qui contient S. (La condition de plus petit signifie que la couverture convexe de S doit être un sous-
ensemble de tout ensemble convexe contenant S).

Si S est convexe, sa couverture convexe est évidemment S lui-même. Si S est un ensemble de deux
points sa couverture convexe est le segment de droite reliant ces deux points. Si S est un ensemble
de trois points non alignés, sa couverture convexe est le triangle dont les sommets sont les points
donnés ; si les trois points sont alignés, la couverture convexe est le segment de droite dont les
extrémités sont les deux points les plus éloignés. Comme exemple de couverture convexe d’un
ensemble plus grand, voir la Figure 3.6. Une étude de ces exemples rend le théorème suivant un
résultat attendu.

THEOREME. La couverture convexe de tout ensemble de n  2 points (non tous situés sur la
même droite) est un polygone convexe dont les sommets correspondent à certains points de S. (Si
tous les points sont alignés, le polygone dégénère en un segment de droite mais encore avec les
extrémités situées à deux points de S.)

P6

P7
 P2
 P8

P3  P4 P5

P1
FIGURE 3.6 – La couverture convexe de cet ensemble de huit points est le polygone convexe dont
les sommets sont P1, P5, P6, P7 et P3.
Le problème de la couverture convexe est le problème de la construction de la couverture convexe
d’un ensemble donné de n points. Pour le résoudre, nous devons trouver les points qui serviront
comme les sommets du polygone en question. Les mathématiciens appellent les sommets d’un tel
polygone les « points extrêmes ». Par définition, un point extrême d’un ensemble convexe est un
point de cet ensemble qui n’est pas un point intérieur d’un segment de droite dont les extrémités
sont dans S. Par exemple, les points extrêmes d’un triangle sont ses trois sommets, les points
extrêmes d’un cercle sont tous les points de sa circonférence et les points extrêmes de la couverture
convexe de l’ensemble de huit points de la Figure 3.6 sont P1, P5, P6, P7 et P3.

69
Les points extrêmes ont plusieurs propriétés particulières que d’autres points de la couverture
convexe n’ont pas. Une de ces propriétés est exploitée par un algorithme très important appelé la
méthode de simplex. Cet algorithme résout les problèmes de programmation linéaire, problèmes
de détermination du minimum ou du maximum d’une fonction linéaire de n variables sujettes à des
contraintes linéaires. Ici cependant, nous sommes intéressés aux points extrêmes car leur
identification résout le problème de la couverture convexe. Dans la pratique, pour résoudre ce
problème complètement, nous devons savoir un peu plus que de savoir lesquels des n points d’un
ensemble donné sont les points extrêmes de la couverture convexe de l’ensemble : nous devons
connaître quelles paires de points doivent être reliés pour former la frontière de la couverture
convexe. Noter que ce sujet peut aussi être abordé en listant les points extrêmes dans l’ordre des
aiguilles d’une montre ou inversement.
Ainsi comment pouvons nous résoudre le problème de la couverture convexe en utilisant la force
brute ? Si vous ne trouvez pas un plan intermédiaire pour une attaque frontale, ne soyez pas
consterné : le problème de la couverture convexe est un des problèmes qui n’ont pas de solution
algorithmique évidente. Néanmoins, il existe un algorithme simple mais inefficace basé sur
l'observation suivante au sujet des segments de droite qui constituent la frontière de la couverture
convexe : un segment reliant deux points Pi et Pj d'un ensemble de n points est une partie de la
frontière de sa couverture convexe si et seulement si tous les autres points de l'ensemble se trouvent
du même côté de la droite passant par ces deux points. La répétition de ce test pour chaque paire de
points produit une liste de segments de droite qui composent la frontière de la couverture convexe.
Quelques notions élémentaires de la géométrique analytique sont nécessaires pour implémenter cet
algorithme. D’abord, la ligne droite passant par deux points ( x1 , y 1 ) et ( x 2 , y 2 ) du plan peut être
défini par l’équation
ax  by  c
où a  y 2  y 1 , b  x1  x 2 , c  x1 y 2  y 1 x 2 .

Deuxièmement, une telle droite divise le plan en deux demi-plans : pour tous les points qui se
trouvent dans un d’entre eux, ax  by  c , tandis que pour tous les points de l’autre, ax  by  c .
Donc pour déterminer si certains points se situent du même côté de la droite, on peut simplement
vérifier si l’expression ax  by  c a le même signe en chacun de ces points.
Quelle est l’efficacité temporelle de cet algorithme ? Elle est dans  n 3  : pour chacune des
n ( n  1) / 2 paires de points distincts, nous avons besoin de trouver le signe de ax  by  c pour
chacun des autres n – 2 points. Il existe des algorithmes plus efficaces pour cet important problème,
nous discuterons un d’entre eux plus tard dans la suite du cours.
Exercices 3.3
Problème 1. Pouvez-vous concevoir un algorithme plus rapide que celui basé sur la stratégie de la
force brute pour résoudre le problème de la paire la plus proche pour n points x1 ,  , x n de la droite
réelle ?
Problème 2. Soient x1  x 2    x n des nombres réels représentant les coordonnées de n villages
situés le long d’une route droite. Un poste de police doit être construit dans l’un de ces villages.
a) Concevoir un algorithme efficace pour déterminer la position du poste de police qui
minimise la distance moyenne entre les villages et le poste de police.
b) Concevoir un algorithme efficace pour déterminer la position du poste de police qui
minimise la distance maximum d’un village au poste de police.
Problème 3. Le problème de la paire la plus proche peut être posé dans un espace k-dimensionnel
dans lequel la distance euclidienne entre deux points x  ( x1 ,  , x s ) et y  ( y 1 ,  , y s ) est définie par

70
k

 x  ys  .
2
d ( x, y )  s
s 1

Quelle sera la classe d’efficacité de l’algorithme de la force brute pour le problème k-dimensionnel
de la paire la plus proche ?

Problème 4. Trouver la couverture convexe des ensembles suivants et identifier leurs points
extrêmes (s’ils existent)
a) un segment de droite
b) un carré
c) la frontière d’un carré
d) une ligne droite

Problème 5. Concevoir un algorithme temps-linéaire pour déterminer deux points extrêmes de la


couverture convexe d’un ensemble de n points du plan.

Problème 6. Quelle modification doit être apportée à l’algorithme de la force brute pour le
problème de la couverture convexe pour traiter plus de deux points situés sur la même ligne droite.

Problème 7. Considérons le petit exemple suivant du problème de programmation linéaire :

Maximiser 3x + 5y
Sujet à x+y4
x + 3y  6
x 0, y  0

a) Dessiner dans un plan cartésien, la région définie comme l’ensemble des points satisfaisant
les contraintes du problème.
b) Identifier les points extrêmes de la région.
c) Résoudre le problème donné en utilisant le théorème suivant : un problème de
programmation linéaire avec une région de faisabilité non vide bornée a toujours une
solution, qui peut être trouvée a un des points extrêmes de la région de faisabilité.

3.4 Recherche exhaustive

Plusieurs problèmes importants demandent de trouver un élément ayant une propriété particulière
dans un domaine qui croit exponentiellement (ou rapidement) avec la taille du problème.
Typiquement, de tels problèmes arrivent dans des situations qui impliquent, explicitement ou
implicitement, des objets combinatoires tels que les permutations, les combinaisons et des sous-
ensembles d’un ensemble donné. Plusieurs problèmes de ce genre sont des problèmes
d’optimisation : ils demandent de trouver un élément qui maximise ou minimise certaines
caractéristiques désirées telles que la longueur du chemin ou une affectation de coût.

La recherche exhaustive est simplement une approche de la force brute pour des problèmes
combinatoires. Elle suggère de générer chacun des éléments du domaine du problème, de choisir
ceux d’entre eux qui satisfont toutes les contraintes et trouver ensuite l’élément désiré (ex : celui qui
optimise une certaine fonction objectif). Noter que même si l’idée de la recherche exhaustive est
assez évidente, ses implémentations nécessitent typiquement un algorithme pour générer certains
objets combinatoires. Nous allons illustrer la recherche exhaustive en l’appliquant à trois problèmes
importants : le problème du voyageur de commerce, le problème du sac à dos et le problème
d’affectation.

71
3.4.1 Le problème du voyageur de commerce

Le problème du voyageur de commerce a intrigué les chercheurs au cours des 150 dernières années
à cause de sa formulation apparemment simple, ses applications importantes et ses liens intéressants
avec d’autres problèmes combinatoires. En termes simples, ce problème demande de trouver le plus
court chemin passant par n cités qui visite chaque cité exactement une fois avant de revenir à la cité
de départ. Ce problème peut convenablement être modélisé par un graphe pondéré dans lequel les
sommets représentent les cités et les poids des arcs représentent les distances. Le problème peut
alors être posé comme le problème de la recherche du plus court chemin Hamiltonien d’un graphe.

A 2 B

5 3
8 7

C D
1

Tour Longueur
A  B  C  A L = 2 + 8 + 1 + 7 = 18
A  B  D  A L = 2 + 3 + 1 + 5 = 11 Optimal
A  C  B  A L = 5 + 8 + 3 + 7 = 23
A  C  D  A L = 5 + 1 + 3 + 2 = 11 Optimal
A  D  B  A L = 7 + 3 + 8 + 5 = 23
A  D  C  A L = 7 + 1 + 8 + 2 = 18

FIGURE 3.7 – Résolution d’un petit problème du voyageur de commerce par recherche exhaustive
Il est facile de voir qu’un circuit Hamiltonien peut aussi être défini comme une suite de n + 1
sommets adjacents v i , v i ,  , v i , v i , où le premier sommet de la suite est le même que le dernier
0 1 n 1 0

tandis que tous les autres n – 1 sommets sont distincts. Par ailleurs, nous pouvons supposer sans
perdre la généralité que tous les circuits commencent et se terminent par un sommet particulier.
Ainsi, on peut obtenir tous les tours en générant toutes les permutations de n – 1 cités
intermédiaires, calculer la longueur des tours et déterminer le plus court parmi eux. La figure 3.7
montre un petit exemple du problème et sa solution par cette méthode.
Une inspection de la Figure 3.7 révèle trois paires de tours qui diffèrent seulement par leur
direction. Donc, nous pouvons réduire le nombre de permutations des sommets de moitié. Nous
pouvons par exemple choisir deux sommets intermédiaires quelconques, par exemple A et B, et
considérer ensuite uniquement les permutations dans lesquelles B précède C. (Cette restriction
définit implicitement une direction pour un tour).
Cette amélioration ne peut cependant pas mettre assez en évidence l’idée d’efficacité. Le nombre
total de permutations nécessaires sera encore (n – 1)!/2, ce qui rend la recherche exhaustive
impraticable même pour des petites valeurs de n. D’un autre côté, si vous voyez toujours votre verre
comme moitié-plein, vous pouvez réclamer que la réduction du travail de moitié n’a rien de notable,
même si vous résolvez une petite instance du problème, particulièrement à la main. Noter également

72
que nous n’avons pas limité notre investigation aux circuits commençant par le même sommet, le
nombre de permutations aurait été plus grand, par un facteur de n.
3.4.2 Le problème du sac à dos
Voici un autre problème bien connu en algorithmique. Etant donné n objets de poids connus
w1 ,  , w n et des valeurs v 1 ,  , v n et un sac à dos de capacité W, trouver le sous-ensemble d’objets le
plus valuable qui s’ajuste dans le sac. Si vous n’aimez pas l’idée de vous mettre dans les chaussures
d’un chef qui voudrait attacher le lot le plus valuable qui s’ajuste à son sac à dos, penser à un avion
de transport qui doit délivrer l’ensemble le plus valuable d’objets dans une réservation à distance
sans dépasser la capacité de l’avion. La Figure 3.8a présente un petit exemple du problème du sac à
dos.

10

w3 = 4 w4 = 5
w1 = 7 w2 = 3 v4 = 25F
v1 = 42F v2 = 12F v3 = 40F

sac à dos item 1 item 2 item 3 item 4


(a)

Sous ensemble Poids total Valeur totale


 0 0F
{1} 7 24F
{2} 3 12F
{3} 4 40F
{4} 5 25F
{1, 2} 10 36F
{1, 3} 11 non faisable
{1, 4} 12 non faisable
{2, 3} 7 52F
{2, 4} 8 37F
{3, 4} 9 65F
{1, 2, 3} 14 non faisable
{1, 2, 4} 15 non faisable
{1, 3, 4} 16 non faisable
{2, 3, 4} 12 non faisable
{1, 2, 3, 4} 19 non faisable

(b)
FIGURE 3.8 – (a) Une instance du problème du sac à dos. (b) Sa solution par recherche exhaustive.
La solution optimale est en gras.

73
L’approche par recherche exhaustive de ce problème consiste à générer tous les sous ensembles de
l’ensemble des n objets donnés, calculer le poids total de chacun des sous-ensembles pour identifier
les sous-ensembles acceptables, et trouver un sous-ensemble ayant la plus grande valeur parmi ces
sous-ensembles. Comme exemple, la solution de l’exemple de la Figure 3.8a est donnée à la Figure
3.8b. Comme le nombre de sous-ensembles d’un ensemble de n objets est égal à 2 n , la recherche
exhaustive conduit à un algorithme  2 n  indépendamment de l’efficacité avec laquelle les sous-
ensembles individuels sont générés.
Donc, pour les deux problèmes du voyageur de commerce et du sac à dos, la recherche exhaustive
conduit à des algorithmes extrêmement inefficaces pour toutes les entrées. En effet, ces deux
algorithmes sont des exemples bien connus de ce que l’on appelle des problèmes-NP difficiles.
Aucun algorithme temps-polynomial n’existe pour un problème-NP difficile. Par ailleurs, la plupart
des informaticiens pensent que de tels algorithmes n’existent pas bien que cette importante
conjecture n’est jamais été prouvée. Des approches plus sophistiquées—le backtracting et le
branch-and-bound—permettent de résoudre certaines instances mais pas toutes de ces problèmes
(et des problèmes similaires) dans un temps inférieur au temps exponentiel. Alternativement, nous
pouvons utiliser une des algorithmes d’approximation tels que ce décrits à la Section 12.3.

3.4.3 Le problème d’affectation


Dans notre troisième exemple de problème pouvant être résolu par recherche exhaustive, on a n
personnes qui doivent être affectées pour exécuter n jobs, une personne par job. Le coût qui si la
ième personne est affectée au jème job est une quantité C[i, j] pour chaque paire (i, j). Le problème
est de trouver l’affectation ayant le coût total le plus bas.
Un petit exemple de ce problème est le suivant ; les entrées du tableau représentant les coûts
d’affectation C[i, j] :

Job 1 Job 2 Job 3 Job 4


Personne 1 9 2 7 8
Personne 2 6 4 3 7
Personne 3 5 8 1 8
Personne 4 7 6 9 4

Il est facile de voir qu’une instance du problème d’affectation est complètement définie par sa
matrice des coûts C. En fonction de cette matrice, le problème demande de choisir un des éléments
dans chaque ligne de la matrice tel que tous les éléments sélectionnés se trouvent dans des colonnes
différentes et la somme totale des éléments est la plus petite possible. Noter qu’aucune stratégie
évidente pour trouver la solution n’existe ici. En effet, le plus petit élément de la matrice entière
n’est pas nécessairement une composante de la solution optimale. Donc, opter pour la recherche
exhaustive peut paraître comme un mal inévitable.
Nous pouvons décrire les solutions faisables du problème d’affectation comme des n-uplets
( j1 ,  , j n ) dans lesquels la ième composante, i  1,  , n , indique la colonne de l’élément
sélectionné dans la ième ligne (i.e. le numéro du job affecté à la ième personne). Par exemple, pour
la matrice de coût ci-dessus, (2, 3, 4, 1) indique une affectable possible de la Personne 1 au Job 2, la
Personne 2 au Job 3, la Personne 3 au Job 4 et la Personne 4 au Job 1. Les exigences du problème
d’affectation impliquent qu’il existe une correspondance bijective entre les affectations faisables et
les permutations de l’ensemble des n premiers entiers. Par conséquent, l’approche par recherche
exhaustive pour le problème d’affectation nécessite la génération de toutes les permutations des
entiers 1, 2 ,  , n , le calcul du coût total de chaque affectation et la sommation des éléments
correspondants de la matrice des coûts et finalement, la sélection de celle qui a la plus petite
somme.
74
Comme le nombre de permutations à considérer dans le cas général du problème d’affectation est
égal à n!; la recherche exhaustive est impraticable pour toutes les instances du problèmes.
Heureusement, il existe un algorithme beaucoup efficace pour ce problème appelé la méthode de
Hongroise proposée par les mathématiciens Hongrois König et Egarvary dont les travaux sous-
tendent cette méthode (voir par exemple, [Kol95]).
Ceci est une bonne nouvelle : le fait qu’un problème dont le domaine croit exponentiellement
n’implique pas nécessairement qu’il ne peut pas exister d’algorithme efficace pour le résoudre. En
effet, nous présentons par la suite plusieurs autres exemples de tels problèmes. Cependant, de tels
exemples sont plus une exception que la règle. Plus souvent, il n’existe pas d’algorithmes temps
polynomiaux pour des problèmes dont le domaine croit exponentiellement avec la taille (en
supposant que nous voulons les résoudre exactement).

Exercices 3.4
Problème 1. a) Supposons que chaque tour peut être engendré en un temps constant, quelle sera la
classe d’efficacité de l’algorithme de recherche exhaustive donné dans le texte pour le problème du
voyageur de commerce ?
b) Si cet algorithme est programmé sur un ordinateur qui effectue un milliard d’additions par
seconde, estimer le nombre maximum de cités pour lequel le problème peut être résolu en : (i) une
heure, (ii) 24 heures, (iii) un an, (iv) un siècle.

Problème 2. Donner un algorithme de recherche exhaustive pour le problème du circuit


Hamiltonien.
Problème 3. Concevoir un algorithme qui détermine si un graphe connexe représenté par sa matrice
d’adjacence a un circuit Eulerien. Quelle est la classe d’efficacité de votre algorithme ?
Problème 4. Considérons le problème de la partition : étant donné n entiers positifs, diviser ces
entiers dans deux ensembles disjoints ayant la même somme de leurs éléments. (Naturellement, ce
problème n’a pas toujours une solution). Concevoir un algorithme de recherche exhaustive pour ce
problème. Essayer de minimiser le nombre de sous-ensembles que l’algorithme a besoin de générer.
Problème 5. Considérons le problème de la clique : étant donné un graphe G et un entier positif k,
déterminer si le graphe contient une clique de taille k, c’est-à-dire un sous-graphe complet de k
sommets. Concevoir un algorithme de recherche exhaustive pour ce problème.
Problème 6. Expliquez comment la recherche exhaustive peut être appliquée au problème de tri et
déterminer la classe d’efficacité d’un tel algorithme.
Problème 7. Carrés magiques Un carré magique d’ordre n est un arrangement des nombres de un à
n dans une matrice carrée d’ordre n, avec chaque élément apparaissant exactement une seule fois,
2

tel que chaque ligne, chaque colonne et chaque diagonale principale a la même somme.
a) Montrer que si une matrice carrée d’ordre n existe la somme en question doit être égale à
n ( n  1) / 2 .
2

b) Concevoir un algorithme de recherche exhaustive pour générer tous les carrés magiques
d’ordre n.
c) Chercher sur Internet ou dans votre bibliothèque un algorithme meilleur pour générer les
carrés magiques.
d) Implémenter les deux algorithmes—la recherche exhaustive et celui que vous aurez trouvé—
et effectuer une expérience pour déterminer la plus grande valeur de n pour laquelle chacun
des algorithmes est capable de trouver un carré magique d’ordre n en moins de une minute
sur la machine que vous utilisez.

75

Vous aimerez peut-être aussi