Bruteforce and backtracking to solve NP-complete problems
Reading time30 minEn bref
Résumé de l’article
Dans cet article, nous vous présentons une approche pour résoudre le TSP, ou plus généralement les problèmes NP-complets. Cette approche est basée sur une recherche exhaustive des solutions possibles, avec une petite stratégie d’accélération appelée “backtracking”.
Points clés
-
Une approche bruteforce (exhaustive) peut être utilisée pour résoudre un problème NP-Complete.
-
Pour le TSP, elle construit un arbre tel que chaque branche est une solution possible.
-
La complexité de la résolution du TSP avec un algorithme bruteforce est $O(n!)$.
-
Le backtracking est une optimisation qui permet d’arrêter l’exploration des branches tôt dans la recherche.
-
Le branch-and-bound est une autre optimisation possible.
Contenu de l’article
1 — Vidéo
Veuillez regarder la vidéo ci-dessous. Vous pouvez aussi lire les transcriptions si vous préférez.
Il y a des “codes” qui apparaissent dans cette vidéo. Veuillez garder à l’esprit que ces “codes” sont ce que nous appelons des pseudo-codes, c’est-à-dire qu’ils ne sont pas écrits dans un vrai langage de programmation. Leur but est simplement de décrire les algorithmes, pas d’être interprétables en soi. Vous aurez donc besoin d’un peu de travail pour les rendre pleinement fonctionnels en Python si vous le souhaitez.
Pour aller plus loin
2 — L’algorithme branch-and-bound
Un troisième algorithme pour résoudre exactement le TSP est l’algorithme branch-and-bound.
L’algorithme branch-and-bound explore l’arbre des possibilités (dans le cas du voyageur de commerce, l’arbre des chemins possibles). Pour ce faire, il utilise un algorithme de type BFS. À chaque sommet rencontré, il essaie d’estimer un intervalle (aussi étroit que possible) estimant la longueur d’un chemin plus court obtenu à partir de la branche de l’arbre où il se trouve actuellement.
Pour obtenir ces intervalles, plusieurs techniques existent :
-
Pour obtenir une borne supérieure, il suffit d’utiliser un algorithme glouton, qui estime la longueur du chemin restant en allant toujours vers le sommet le plus proche. Cela donne un chemin qui n’est pas nécessairement optimal, mais dont la longueur ne peut être que plus longue que celle d’un chemin le plus court.
-
Pour obtenir une borne inférieure, on peut par exemple compter le nombre de sommets encore non visités (appelons ce nombre $k$). Dans le meilleur des cas, ils peuvent être visités en passant par les $k$ arêtes de poids les plus faibles. La somme de ces $k$ poids donne la borne inférieure. Cette réduction est souvent grossière, et il est préférable de chercher la plus fine.
Lorsque l’algorithme a déterminé plusieurs intervalles (un par branche à explorer), il peut les comparer entre eux. Dans le cas de la recherche d’un chemin de longueur minimale, si l’intervalle d’une branche $b_1$ a pour borne supérieure une valeur plus petite que la borne inférieure d’un intervalle d’une autre branche $b_2$, alors le sous-arbre correspondant à $b_2$ est éliminé. En effet, il n’y a aucun intérêt à explorer le sous-arbre $b_2$ sachant que la meilleure solution trouvée sera de toute façon moins intéressante que la pire en continuant sur la branche $b_1$.
2.1 — Complexité
La complexité de l’algorithme est dans le pire des cas la même qu’une recherche exhaustive (si l’estimation des bornes ne coûte pas trop cher). C’est par exemple le cas avec un graphe complet dans lequel toutes les arêtes ont le même poids.
En pratique, avoir une bonne précision dans l’estimation des intervalles peut faire gagner beaucoup de temps. Un cas optimal est celui d’une complexité linéaire.
2.2 — Compromis précision-performance
Comme pour tous les algorithmes approximatifs, l’estimation des intervalles pose le problème du compromis précision-performance. En effet, plus l’intervalle est étroit, plus il permettra d’élaguer des parties de l’arbre. Cependant, si son estimation nécessite un calcul coûteux, cela impacte la complexité totale.
Il est donc nécessaire de trouver des estimations des bornes des intervalles qui offrent un bon compromis entre temps de calcul et précision.
Pour aller au-delà
-
Cet algorithme résout le TSP en $O(n^2 \cdot 2^n)$.
-
C’est un outil rapide pour résoudre le TSP et d’autres problèmes d’optimisation.
-
Une possible implémentation Java/C++ de l’algorithme. ```