Partition équilibrée⚓︎
Si les éléments d'une liste de longueur \(n\) sont répartis entre deux listes liste_1 et liste_2 de longueurs respectives \(n_1\) et \(n_2\), (\(n_1+n_2=n\)), on dit que
liste_1 et liste_2 réalisent une partition de la liste initiale.
On dispose d'une liste de \(n\) nombres entiers strictement positifs, et on souhaite répartir ces \(n\) nombres en deux listes telles que les sommes respectives des éléments des deux listes soient les plus proches possibles.
Cette partition d'une liste en deux listes s'appelle une partition équilibrée. L'ordre des éléments dans chaque liste n'a aucune importance.
Par exemple, avec la liste [10, 8, 5, 7, 9, 5], une partition équilibrée est obtenue avec les deux listes [10, 7, 5] et [8, 9, 5].
Remarque
On peut envisager un algorithme glouton mais ce type de méthode ne fournit pas la solution optimale dans tous les cas (voir l'exercice partition equilibrée 1 ).
La méthode utilisée ici est la programmation dynamique.
Programmation dynamique⚓︎
La fonction sum renvoie la somme des éléments d'une liste: sum(liste) est la somme des éléments de liste. La fonction abs renvoie la valeur absolue d'un nombre.
Si somme a pour valeur sum(liste) et si liste_1 est une liste constituée d'éléments de liste qui réalise le minimum des abs(sum(liste_i) - somme/2)
où liste_i est une liste quelconque constituée d'éléments de liste, alors la liste liste_1 et la liste liste_2 dont les éléments sont ceux de liste
qui ne sont pas dans liste_1, constituent une partition équilibrée de liste.
La solution n'est pas toujours unique. Par exemple avec liste = [3, 2, 2, 1, 1, 1], une partition équilibrée est obtenue avec les deux listes
[3, 2] et [2, 1, 1, 1] ou avec les deux listes [3, 1, 1] et [2, 2, 1].
Principe de l'algorithme:
on parcourt la liste et pour chaque élément
liste[i]on prend une décision entre les deux possibilités suivantes:soit on choisit de prendre
liste[i]et dans ce cas:on le place dans
liste_1; on continue avec la sous-liste restanteliste[i+1:]et la sommesomme/2-liste[i]soit on choisit de ne pas prendre
liste[i]et dans ce cas:on continue avec la sous-liste restante
liste[i+1:]et la sommesomme/2à chaque étape, on fait le choix qui minimise
abs(sum(liste_1) - somme/2)cas de base: liste de longueur 1 ou 0
Question 1
Compléter le code de la fonction partition qui prend en paramètre une liste de nombres nommée liste et qui renvoie la liste liste_1 obtenue par l'algorithme décrit plus haut.
On peut tester ce programme avec des listes de quelques nombres, mais en augmentant la taille de la liste on constate très rapidement un problème de coût. Comme d'habitude en programmation dynamique, le coût élevé a pour cause de nombreuses résolutions des mêmes sous-problèmes. Il s'agit donc de mémoriser les solutions des sous-problèmes plutôt que les recalculer plusieurs fois.
Dans une approche ascendante, on résout les sous-problèmes depuis les plus petits jusqu'aux plus grands et on utilise un tableau tab pour stocker les résultats.
Soit s la somme des éléments de la liste initiale,
tab[i][j] pour i allant de 1 à n et j allant de 1 à s , bornes incluses, a la valeur True si on peut trouver
des éléments parmi les éléments d'indice 0 à i-1 de somme j et la valeur False sinon.
Pour calculer tab[i][j], on utilise les résultats obtenus pour les valeurs plus petites de iet j.
On peut construire le tableau avec une première ligne pour l'ensemble vide, en indiquant ensuite les indices des éléments de liste en colonne, les sommes de 0
à la somme totale s en ligne. Pour remplir le tableau, on utilise la
formule: tab[i][j] = tab[i-1][j] or tab[i-1][j-vi] où vi est la valeur de liste[i-1].
On cherche ensuite dans la dernière ligne du tableau la valeur de m telle que tab[len(liste)][m] vaut True, m étant la somme la plus proche de s//2 inférieure ou égale à s//2
Question 2
Compléter le code de la fonction tableau qui prend en paramètre une liste de nombres nommée liste et qui renvoie le tableau tab et le nombre m obtenus comme décrit ci-dessus.
# Tests (insensible à la casse)(Ctrl+I)
(Alt+: ; Ctrl pour inverser les colonnes)
(Esc)
Après avoir obtenu le tableau, on peut construire une partition équilibrée de la liste.
Question 3
Compléter le code de la fonction partition2 qui prend en paramètres:
-
une liste de nombres nommée
liste; -
un indice
iinitialisé àlen(liste)lors de l'appel de la fonction; -
le tableau
tobtenu avec la fonctiontableaude la question 2; -
un entier
vinitialisé, lors de l'appel de la fonction, avec la valeur demobtenue avec la fonctiontableaude la question 2; -
une liste nommée
liste_1, initialisée par défaut àNone, dans laquelle on ajoute successivement les éléments choisis et qui est renvoyée à la fin.
# Tests (insensible à la casse)(Ctrl+I)
(Alt+: ; Ctrl pour inverser les colonnes)
(Esc)
# Tests(insensible à la casse)(Ctrl+I)
(Alt+: ; Ctrl pour inverser les colonnes)
(Esc)