Retour sur traceEn informatique, plus précisément en algorithmique, le retour sur trace ou retour arrière[1] (appelé aussi backtracking en anglais) est une famille d'algorithmes pour trouver des solutions à des problèmes algorithmiques, notamment de satisfaction de contraintes. Contrairement à une recherche exhaustive, un algorithme de retour sur trace construit incrémentalement des solutions candidates. Il abandonne la construction lorsqu'il ne peut compléter le candidat courant en solution valide. Il revient alors sur ses choix (d'où le nom de retour sur trace) et en prend d'autres pour construire d'autres solutions candidates. Le retour sur trace est utilisé pour résoudre des problèmes algorithmiques difficile à résoudre, typiquement NP-complets[2]. PrincipeCes algorithmes permettent de tester systématiquement l'ensemble des affectations potentielles du problème. Ils consistent à sélectionner une variable du problème, et pour chaque affectation possible de cette variable, à tester récursivement si une solution valide peut-être construite à partir de cette affectation partielle. Si aucune solution n'est trouvée, la méthode abandonne et revient sur les affectations qui auraient été faites précédemment (d'où le nom de retour sur trace).
Arbre de rechercheLe retour sur trace peut être vu comme un parcours en profondeur dans l'arbre de recherche du problème, aussi appelé arbre de l'espace d'état (en anglais state-space tree[2]). L'arbre de recherche du problème est construit comme suit. La racine de l'arbre est le point de départ de notre algorithme : on part de la solution vide. Les enfants d'un nœud dans l'arbre sont obtenus en étendant la solution, typiquement en affectant une variable du problème. On montre ici deux exemples d'arbres de recherche : le premier pour le problème de placer quatre reines (l'arbre de recherche pour le problème des huit reines est trop grand pour être montré), et pour le problème de couverture exacte.
La pile est une structure de donnée fondamentale pour le retour sur trace, elle permet de stocker (empiler) les branchements effectués et de les mettre à jour (dépiler) lors d'un retour. L'utilisation de cette pile est exactement la façon de procéder d'un parcours en profondeur. Une des implémentations de l'algorithme utilise des appels récursifs et donc la pile d'exécution. Pseudo-codeIl existe plusieurs pseudo-codes selon les auteurs. A chaque fois, on donne des schémas d'algorithmes, c'est-à-dire que les pseudo-codes données ici s'appliquent à de nombreux problèmes. Version récursiveLevitin, dans son livre[2], donne une version récursive. On suppose ici qu'une solution candidate se représente avec des variables X[1..i] et que la i-ème variable X[i] est à valeurs dans Si. L'algorithme générique du backtracking s'écrit comme suit : entrée : X[1..i] les valeurs des i premières variables d'une potentielle solution sortie : écrit toutes les solutions dont les premières variables valent X[1..i] fonction backtrack(X[1..i]) si X[1..i] est une solution écrire X[1..i] sinon pour tout élément x dans Si+1 consistant avec X[1..i] et les contraintes du problème X[i+1] := x backtrack(X[1..i+1]) L'algorithme débute avec Version itérativeDasgupta et al., dans leur livre, donnent une version itérative[3] et restent plus abstrait quant aux contenus des nœuds de l'arbre de recherche, qu'ils appellent sous-problèmes : commencer avec un problème P0 (c'est la racine de l'arbre) S := {P0} l'ensemble des sous-problèmes actifs tant que S est non vide choisir un sous-problème P de S retirer P de S développer P des sous-problèmes plus petits P1, ... Pk pour chaque Pi si Pi peut se voir comme une solution s'arrêter et afficher la solution si Pi ne peut pas être étendue en une solution oublier Pi sinon ajouter Pi à S dire qu'il n'y a pas de solution Les parties soulignées de l'algorithme ci-dessus dépendent du problème que l'on souhaite résoudre. ExemplesProblèmes de satisfaction de contraintesLes problèmes de satisfaction sont des problèmes ayant une solution complète, dans laquelle aucun ordre naturel des éléments n'existe. Ces problèmes se composent d'un ensemble de variables dont les valeurs sont assujetties à des contraintes spécifiques au problème. L'idée maîtresse consiste à essayer chaque possibilité (combinaison) jusqu'à trouver la bonne. Pour cela on examine les possibilités immédiates, puis si aucun blocage n'apparaît on continue sur les possibilités suivantes ; si un blocage apparaît, autrement dit s'il n'y a aucune possibilité, on revient au dernier cas examiné où une autre possibilité existait et on continue à partir de ce cas. Les langages de programmation déclaratifs, comme Prolog, permettent d'exprimer directement ces contraintes et effectuent cette recherche automatiquement. Jeux et casse têteOn peut, dans un jeu que l'on programme, envisager un mouvement donné et ses suites. Il se peut que l'une des suites ne soit pas heureuse. On remonte alors à un point donné et on envisage un autre mouvement. Couverture exacteLe problème de la couverture exacte consiste à sélectionner des sous-ensembles de façon à obtenir une partition d'un ensemble d'éléments. Ce problème est intéressant car plusieurs autres problèmes, comme des pavages, ou résoudre un Sudoku peuvent se voir comme un problème de couverture exacte. Donald Knuth a inventé l'algorithme X[4], consacré à ce problème qui est un algorithme de retour sur trace, ainsi qu'une structure de données pour représenter une solution partielle : les liens dansants. Analyse syntaxiqueComparaison avec d'autres algorithmesLe backtracking est proche des algorithmes suivants : Notes et références
Voir aussi
|