Pour tous les exercices de cette session, on vous fournit un fichier
squelette Session7.java.
Les objectifs sont, dans l’ordre, que le code soit
correct, que le code soit efficace et
que le code soit lisible. Des tests unitaires
sont fournis dans Session7Test.java.
Cette séance est adaptée d’un cours d’optimisation combinatoire de Cyril Nicaud pour les étudiants de BUT3.
La méthode allSubsets renvoie la liste de tous les
sous-ensembles de [0..n[.
C’est moins efficace que la technique vue dans la session précédente,
mais c’est une illustration simple de la technique du
backtracking.
public static List<BitSet> allSubsets(int n) {
var result = new ArrayList<BitSet>();
subsetsBacktrack(0, n, new BitSet(n), result);
return result;
}
private static void subsetsBacktrack(int start, int n, BitSet current, List<BitSet> result) {
if (start == n) {
result.add((BitSet) current.clone());
return;
}
subsetsBacktrack(start + 1, n, current, result);
current.set(start);
subsetsBacktrack(start + 1, n, current, result);
current.clear(start);
}Version alternative (légèrement optimisée) :
private static void subsetsBacktrack(int start, int n, BitSet current, List<BitSet> result) {
result.add((BitSet) current.clone());
for (var i = start; i < n; i++) {
current.set(i);
subsetsBacktrack(i + 1, n, current, result);
current.clear(i);
}
}On considère le problème suivant :
weights de taille n, où weights[i] est le
poids de l’objet i.On veut donner (s’il existe) un sous-ensemble d’objets dont la somme des poids est exactement égale à target. Remarquez qu’il peut y avoir plusieurs solutions.
On traite les objets un par un. À l’étape k, on a déjà décidé, pour chaque objet d’indice strictement inférieur à k, s’il appartient ou non au sac. Cela constitue une solution partielle. Pour passer à l’étape suivante, on explore récursivement deux possibilités :
On obtient donc un arbre binaire de recherche. On peut optimiser cette recherche en s’arrêtant dans deux cas :
Si les poids sont { 1, 4, 8, 3, 5 }, et
target = 10, un arbre de recherche possible explore les
sous-ensembles (d’indices) :
{} --> 0
{0} --> 1
{0, 1} --> 1+4=5
{0, 1, 2} --> 1+4+8=13
{0, 1, 3} --> 1+4+3=8
{0, 1, 3, 4} --> 1+4+3+5=13
{0, 1, 4} --> 1+4+5=10
L’ordre exact dépend de la manière dont on parcourt l’arbre.
public static List<Integer> knapsack(int[] weights, int target) {
return knapsackBacktrack(0, weights, target, new ArrayList<Integer>(), 0);
}
private static List<Integer> knapsackBacktrack(int start, int[] weights, int target, List<Integer> current,
int currentWeight) {
if (currentWeight == target) {
return current;
}
if (currentWeight > target) {
return List.of();
}
for (var i = start; i < weights.length; i++) {
current.add(weights[i]);
var knapsack = knapsackBacktrack(i + 1, weights, target, current, currentWeight + weights[i]);
if (!knapsack.isEmpty()) {
return knapsack;
}
current.removeLast();
}
return List.of();
}On appelle k-combinaison de [0..n[ tout sous-ensemble de cardinal k de l’ensemble {0,1,,n-1}. Par exemple, pour n = 4 et k = 3, les 3-combinaisons sont : {0,1,2}, {0,1,3}, {0,2,3} et {1,2,3}.
L’objectif est d’implémenter en Java deux méthodes permettant de produire toutes les k-combinaisons de [0..n[.
BitSet
;Indication : On pourra utiliser la décomposition classique suivante.
public static List<BitSet> slowCombinations(int k, int n)public static List<BitSet> combinations(int k, int n)
On considère des graphes non orientés sans boucle, encodés par leur
matrice d’adjacence. Les sommets sont numérotés de 0 à n − 1 et on dispose d’une matrice
graph (tableau à deux dimensions) telle que
graph[i][j] est vrai s’il existe une arête entre les
sommets i et j, et faux sinon. Comme le graphe
est non orienté, on a graph[i][j] = graph[j][i] pour tous
les sommets i et j.
Un 3-coloriage du graphe consiste à associer à
chaque sommet une couleur parmi trois couleurs. Dans la suite, on
représentera les couleurs par les entiers [1, 2, 3]. Un
3-coloriage est valide si deux sommets reliés par une
arête n’ont jamais la même couleur. Ce n’est pas toujours possible, voir
l’exemple ci-contre.
On représente un 3-coloriage par un tableau colors de
taille n, où
colors[i] désigne la couleur du sommet i. Un coloriage est partiel
peut contenir des couleurs non affectées (valeur 0).
Écrire une fonction suivante qui retourne true si le
3-coloriage partiel colors est valide pour le graphe
représenté par graph, et false sinon.
Attention, les sommets qui n’ont pas de couleur n’influencent pas la
validité du coloriage.
public static boolean isValid3Coloring(boolean[][] graph, int[] colors)Écrire une fonction suivante qui retourne un coloriage valide du graphe, s’il en existe au moins un, en utilisant une méthode brute force.
public static Optional<int[]> threeColorsBruteForce(boolean[][] graph)Écrire une fonction suivante qui retourne un coloriage valide du graphe, s’il en existe au moins un, en utilisant une méthode par backtracking.
public static Optional<int[]> threeColors(boolean[][] graph)Caractéristiques des fichiers de test :
La taille maximale d’un graphe est 20.
Exemples :
0110
1011
1101
0110
Les coloriages possibles sont :
[1, 3, 2, 1]
[1, 2, 3, 1]
[2, 3, 1, 2]
[2, 1, 3, 2]
[3, 2, 1, 3]
[3, 1, 2, 3]
[1, 2, 3, 1]

Le problème des n-reines est un problème classique d’optimisation. Il s’agit de placer n jetons (les reines) sur les cases d’une grille n × n de façon à ce qu’il n’y ait pas deux reines sur la même ligne, ni sur la même colonne, ni sur la même diagonale.
Comme il ne peut y avoir qu’une reine par ligne, on encode une
solution au problème comme une liste d’entier queens de
taille n où
queens.get(i) est le numéro de la colonne où se trouve la
reine de la ligne i. Pour
simplifier le code, on numérote les lignes et les colonnes de 0 à n − 1. Une solution est
nécessairement une permutations, c’est à dire que la liste contient
exactement une fois chaque entier de [0..n−1]. Une solution partielle de
taille k est une liste qui
contient des numéros de colonnes (entre 0 et n − 1) pour les k premières lignes. Il peut manquer
des numéros de colonne, mais il n’y a pas de doublons dans la liste.
Écrire une fonction isValidQueensPermutation suivante
qui vérifie si la solution partielle queens est valide pour
le problème à n reines.
private static boolean isValidQueensPermutation(int n, List<Integer> queens)On veut écrire une fonction qui retourne une liste de toutes les solutions valides pour le problème à n reines, en utilisant une méthode brute force. Pour cela, il faut pouvoir énumérer les solution candidates. Vous pouvez implémenter une des deux méthodes suivantes : soit énumérer tous les k-uplets de [0..n−1] (méthodes facile), soit directement les permutations de [0..n−1] (par backtracking).
public static List<List<Integer>> allTuples(int k, int n)
public static List<List<Integer>> allPermutations(int n)Écrire la fonction suivante qui retourne une liste de toutes les solutions valides pour le problème à n reines, en utilisant une méthode brute force.
public static List<List<Integer>> nQueensBruteForce(int n)Écrire la fonction suivante qui retourne une liste de toutes les solutions valides pour le problème à n reines, en utilisant une méthode par backtracking.
public static List<List<Integer>> nQueens(int n)Caractéristiques des fichiers de test :
Le nombre maximal de reines est 15.
Exemples :
Les solutions valides pour n = 4 sont les suivantes
[1, 3, 0, 2]
[2, 0, 3, 1]
Les solutions valides pour n = 5 sont les suivantes
[0, 2, 4, 1, 3]
[0, 3, 1, 4, 2]
[1, 3, 0, 2, 4]
[1, 4, 2, 0, 3]
[2, 0, 3, 1, 4]
[2, 4, 1, 3, 0]
[3, 0, 2, 4, 1]
[3, 1, 4, 2, 0]
[4, 1, 3, 0, 2]
[4, 2, 0, 3, 1]