Probleme – Le voyageur de commerce, ou la dynamique sur les sous-ensembles
Exercice · niveau 3 (difficile) · informatique (MP2I/MPI), chapitre 17 — Programmation dynamique
Énoncé
Un livreur part de la ville , doit visiter chacune des autres exactement une fois, et revenir. On cherche le circuit de coût minimal. L'exercice sur le plus long chemin simple annonçait qu'une contrainte globale se paye en mettant l'ensemble des sommets visités dans l'état : on le fait ici.
- Définir le sous-problème et écrire la récurrence.
- Programmer, en représentant un sous-ensemble par un entier.
- Comparer le coût à celui de l'énumération de tous les circuits.
- Est-ce polynomial ?
Corrigé
1. Le sous-problème. Pour un sous-ensemble et un sommet , on pose
C'est le point décisif : l'ensemble visité fait partie de l'état. La récurrence devient alors immédiate — le chemin arrive en depuis un :
et la réponse est .
Pourquoi la sous-structure optimale est ici vraie alors qu'elle était fausse pour le plus long chemin simple : le sous-chemin de à visite exactement , et cette contrainte est inscrite dans le sous-problème. Rien n'est laissé à l'extérieur. On a acheté la sous-structure au prix de états.
2. Le programme. Un sous-ensemble de se code par un entier dont le bit vaut si :
/* Cout minimal d'un circuit passant par toutes les villes, depart et arrivee
en 0. c[u][v] est le cout de u a v.
Preconditions : 2 <= n <= 20, c[u][v] >= 0.
Complexite : Theta(2^n * n^2) en temps, Theta(2^n * n) en memoire. */
int voyageur(int c[][N_MAX], int n) {
assert(2 <= n && n <= 20);
int taille = 1 << n;
int (*d)[N_MAX] = malloc((size_t) taille * sizeof *d); /* PAS sur la pile */
if (d == NULL) { return -1; }
for (int S = 0; S < taille; S = S + 1)
for (int k = 0; k < n; k = k + 1) { d[S][k] = INT_MAX; }
for (int k = 1; k < n; k = k + 1) { d[1 << k][k] = c[0][k]; }
/* INVARIANT : quand on traite S, tous les d[T][.] avec T strictement
inclus dans S sont definitifs -- vrai car T < S comme ENTIERS. */
for (int S = 1; S < taille; S = S + 1) {
for (int k = 1; k < n; k = k + 1) {
if (!(S & (1 << k)) || d[S][k] == INT_MAX) { continue; }
for (int j = 1; j < n; j = j + 1) {
if (S & (1 << j)) { continue; } /* j deja visite */
int neuf = d[S][k] + c[k][j];
if (neuf < d[S | (1 << j)][j]) { d[S | (1 << j)][j] = neuf; }
}
}
}
int tout = taille - 2, meilleur = INT_MAX; /* toutes les villes sauf 0 */
for (int k = 1; k < n; k = k + 1) {
if (d[tout][k] != INT_MAX && d[tout][k] + c[k][0] < meilleur) {
meilleur = d[tout][k] + c[k][0];
}
}
free(d); /* qui alloue documente qui libere */
return meilleur;
}
L'ordre de remplissage est ici gratuit, et c'est la plus jolie idée du code : si , alors l'entier est strictement plus petit que l'entier (on lui a retiré des bits). Une simple boucle croissante sur les entiers respecte donc l'ordre d'inclusion. Aucun tri, aucune boucle par cardinal.
Mesure sur quatre villes, avec la matrice
la table rend , et l'énumération des circuits confirme , réalisé par .
3. Le gain. L'énumération examine circuits (ou si la matrice est symétrique), chacun coûtant à évaluer. La table examine états, chacun en :
À , le rapport entre les deux colonnes de droite vaut : l'énumération demanderait des siècles, la table quelques secondes. La mémoire, en revanche, vaut octets, soit Mo — et c'est elle, et non le temps, qui arrête l'algorithme vers .
4. Non, ce n'est pas polynomial, et il ne faut pas s'y tromper : reste exponentiel. Le problème est np-difficile (chapitre chap:decidabilite) et personne ne connaît d'algorithme polynomial.
Ce que le problème enseigne malgré tout : la programmation dynamique ne rend pas facile ce qui est difficile. Elle transforme en — d'une exponentielle à une autre, mais infiniment plus praticable. Et elle le fait par le même geste que partout ailleurs dans ce chapitre : choisir un état qui rende les sous-problèmes indépendants, ici en y logeant l'ensemble déjà visité. C'est l'étape 2 de la méthode en cinq points, et c'est toujours la seule qui demande de l'invention.
Les autres exercices de ce chapitre Le cours du chapitre
Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.