Je recherche un algorithme pour distribuer les valeurs d'une liste afin que la liste résultante soit aussi "équilibrée" ou "uniformément distribuée" que possible (entre guillemets car je ne suis pas sûr que ce soient les meilleures façons de la décrire ... plus tard, je fournirai un moyen de mesurer si un résultat est meilleur que les autres).
Donc, pour la liste:
[1, 1, 2, 2, 3, 3]
L'un des meilleurs résultats, après redistribution des valeurs, est:
[1, 2, 3, 1, 2, 3]
Il peut y avoir d'autres résultats aussi bons que celui-ci, et bien sûr, cela devient plus compliqué avec un ensemble de valeurs moins uniforme.
Voici comment mesurer si un résultat est meilleur que les autres:
Comptez les distances entre chaque élément et l'élément suivant avec la même valeur.
Calculez l'écart type pour cet ensemble de distances. Une dispersion plus faible signifie un meilleur résultat.
Observations:
- Lorsque vous calculez une distance et que la fin de la liste est atteinte sans trouver un élément avec la même valeur, nous revenons au début de la liste. Donc, tout au plus, le même élément sera trouvé et la distance pour cet élément sera la longueur de la liste. Cela signifie que la liste est cyclique ;
- Une liste typique comprend ~ 50 articles avec ~ 15 valeurs différentes en quantités variées.
Alors:
- Pour le résultat
[1, 2, 3, 1, 2, 3]
, les distances sont[3, 3, 3, 3, 3, 3]
et l'écart type est0
; - Pour le résultat
[1, 1, 2, 2, 3, 3]
, les distances sont[1, 5, 1, 5, 1, 5]
et l'écart type est2
; - Ce qui rend le premier résultat meilleur que le second (un écart inférieur est meilleur).
Compte tenu de ces définitions, je demande un indice des algorithmes ou stratégies à rechercher.
Réponses:
J'ai rencontré cette question tout en recherchant un problème similaire: des ajouts optimaux de liquides pour réduire la stratification. Il semble que ma solution soit également applicable à votre situation.
Si vous voulez mélanger les liquides A, B et C dans la proportion 30,20,10 (c'est-à-dire 30 unités de A, 20 unités de B et 10 unités de C), vous vous retrouvez avec une stratification si vous ajoutez tous le A, puis tout le B, puis tout le C. Il vaut mieux mélanger des unités plus petites. Par exemple, effectuez des ajouts d'unité dans la séquence [A, B, A, C, B, A]. Cela empêchera complètement la stratification.
La façon dont je l'ai trouvé est de le traiter comme une sorte de fusion, en utilisant une file d'attente prioritaire. Si je crée une structure pour décrire les ajouts:
La fréquence est exprimée comme "un tous les N". Ainsi, A, qui est ajouté trois fois sur six, a une fréquence de 2 (6/3).
Et initialisez un tas qui contient initialement:
Maintenant, je supprime le premier élément du tas et je le génère. Ensuite, réduisez son nombre de 1 et augmentez la priorité par fréquence et ajoutez-le au tas. Le tas résultant est:
Ensuite, supprimez B du tas, éditez-le et mettez-le à jour, puis rajoutez-le au tas:
Si je continue de cette façon, j'obtiens le mélange souhaité. J'utilise un comparateur personnalisé pour m'assurer que lorsque des éléments de priorité égaux sont insérés dans le tas, celui avec la valeur de fréquence la plus élevée (c'est-à-dire la moins fréquente) est commandé en premier.
J'ai écrit une description plus complète du problème et de sa solution sur mon blog, et présenté un code C # fonctionnel qui l'illustre. Voir Répartition uniforme des éléments dans une liste .
Mettre à jour après les commentaires
Je pense que mon problème est similaire au problème de l'OP, et donc que ma solution est potentiellement utile. Je m'excuse de ne pas avoir cadré ma réponse davantage dans les termes de la question du PO.
La première objection, que ma solution utilise A, B et C plutôt que 0, 1 et 2, est facilement corrigée. C'est simplement une question de nomenclature. Je trouve plus facile et moins déroutant de penser et de dire "deux A" plutôt que "deux 1". Mais aux fins de cette discussion, j'ai modifié mes sorties ci-dessous pour utiliser la nomenclature du PO.
Bien sûr, mon problème concerne le concept de distance. Si vous voulez "répartir uniformément les choses", la distance est implicite. Mais, encore une fois, c'était mon échec de ne pas montrer de manière adéquate comment mon problème est similaire au problème du PO.
J'ai effectué quelques tests avec les deux exemples fournis par l'OP. C'est:
Dans ma nomenclature, ceux-ci sont exprimés respectivement en [2,2,2] et [4,3,2,1]. Soit, dans le dernier exemple, "4 éléments de type 0, 3 éléments de type 1, 2 éléments de type 2 et 1 élément de type 3."
J'ai exécuté mon programme de test (comme décrit ci-dessous) et j'ai publié mes résultats. En l'absence de données de l'OP, je ne peux pas dire si mes résultats sont similaires, pires ou meilleurs que les siens. Je ne peux pas non plus comparer mes résultats à ceux des autres parce que personne d'autre n'en a publié.
Je peux dire, cependant, que l'algorithme fournit une bonne solution à mon problème d'élimination de la stratification lors du mélange de liquides. Et il semble qu'il offre une solution raisonnable au problème du PO.
Pour les résultats ci-dessous, j'ai utilisé l'algorithme que j'ai détaillé dans mon article de blog, avec la priorité initiale définie sur
Frequency/2
et le comparateur de tas modifié pour favoriser l'élément le plus fréquent. Le code modifié est affiché ici, avec les lignes modifiées commentées.En exécutant mon programme de test avec le premier exemple de l'OP, j'obtiens:
Donc mon algorithme fonctionne pour le problème trivial de tous les comptes étant égaux.
Pour le deuxième problème signalé par l'OP, j'ai obtenu:
Je ne vois pas de moyen évident d'améliorer cela. Il pourrait être réorganisé pour faire les distances pour l'élément 0 [2,3,2,3] ou un autre arrangement de 2 et 3, mais cela changera les écarts pour les éléments 1 et / ou 2. Je ne sais vraiment pas quoi "optimum" est dans cette situation. Est-il préférable d'avoir un écart plus important sur les éléments les plus fréquents ou les moins fréquents?
En l'absence d'autres problèmes de l'OP, j'ai utilisé ses descriptions pour en inventer quelques-unes. Il a déclaré dans son message:
Mes deux tests ont donc été:
Et mes résultats:
Et pour le deuxième exemple:
la source
Cela "sent" comme s'il pouvait être NP-dur. Alors, que faites-vous quand vous avez un problème NP-difficile? Lancez une heuristique ou un algorithme d'approximation, ou utilisez un solveur SAT.
Dans votre cas, si vous n'avez pas besoin de la solution optimale absolue, un point de départ raisonnable pourrait être d'essayer un recuit simulé . Il existe un moyen naturel de prendre n'importe quelle solution candidate et de la déplacer vers une solution candidate proche: choisissez au hasard deux éléments de la liste et échangez-les. Le recuit simulé tentera de manière itérative d'améliorer la solution. Vous pouvez trouver de nombreuses ressources sur le recuit simulé, si vous ne le connaissez pas. Vous pouvez également expérimenter avec d'autres ensembles de «mouvements locaux» qui apportent de petits changements à une solution candidate, dans l'espoir de l'améliorer progressivement (c.-à-d. Réduire l'écart type des distances).
Mais je vous suggère de commencer par un recuit simulé. C'est la première chose que j'essaierais, car je pense que cela pourrait bien fonctionner.
la source
Esquisse d'un algorithme heuristique
Je n'ai pas de solution exacte à ce problème. Mais comme le commentaire de Raphaël suggère qu'il ressemble au problème de partition, pour lequel des algorithmes heuristiques ont été développés, je vais essayer une approche heuristique. Ce n'est qu'un croquis d'un algorithme heuristique.
Cela guidera notre algorithme.
Il peut s'agir d'une valeur avec très peu ou très peu d'occurrences au début. Je pense que cela ne fait pas vraiment de différence, car les contraintes créées par l'occupation des créneaux sont proportionnelles au nombre de valeurs bien (?) Placées.
La première valeur considérée peut être placée sans aucune contrainte. Ensuite, les autres valeurs doivent être placées de manière à minimiser leur contribution à l'écart-type, mais uniquement dans les emplacements laissés libres par les valeurs précédemment placées.
Le placement des occurrences d'une valeur dans les emplacements restants peut être fait avec un algorithme de programmation dynamique, de manière à fusionner les calculs qui placent le même nombre de valeurs entre deux positions, en ne gardant que celles qui ont une contribution minimale à l'écart-type (c'est-à-dire valeur minimale pour la somme des carrés de leurs écarts).
Ensuite, vous placez les valeurs singleton dans les emplacements restants.
Je crois que cela devrait généralement donner une solution raisonnable, mais je n'ai encore aucune idée de la façon de le prouver ou d'estimer l'écart avec une solution optimale.
la source
[0, 0, 0, 0, 1, 1, 1, 2, 2, 3]
et v4
, nous placerions d'abord les valeurs1
(10/3 = 3.33
, les plus proches de v), puis2
(10/2 = 5
, les plus proches suivantes), puis0
(10/4 = 2.5
)? Ou: pourriez-vous donner un exemple de "diminution de l'écart moyen de la distance par rapport à la valeur v"?Il semble que je sois très en retard à la fête, mais je poste au cas où quelqu'un se heurterait à nouveau à cela. Ma solution est similaire à @ babou's plus. Plus tôt dans la journée, j'ai eu un problème de planification dans un système embarqué qui m'a conduit à ce fil. J'ai une implémentation spécifique à mon problème en C, mais j'ai pensé publier une solution plus générique en Python ici (la version C est compliquée par le fait que je me suis limité à une petite pile de taille fixe et pas de mémoire allocations, donc j'exécute tout l'algorithme sur place). La technique d'anticrénelage utilisée ci-dessous est quelque chose que vous pourriez utiliser pour dessiner une ligne sur un écran avec une couleur de 2 bits. L'algorithme atteint ici un score inférieur (c'est-à-dire meilleur) lorsqu'il est mesuré en utilisant la somme de l'écart-type pour les entrées utilisées par Jim Mischel que cette solution particulière.
résultats pour
Si des entrées de la forme spécifiée par @moraes sont fournies, on peut la convertir en une forme utilisable par cette fonction en étapes O (n) en utilisant des bits de mémoire Big Omega (n * log (n)) où n est le nombre d'éléments ( dans une liste de 255 éléments, vous n'aurez pas besoin de plus de 255 octets supplémentaires) en conservant un tableau parallèle avec le nombre de répétitions. Alternativement, on peut effectuer une paire de tri sur place avec O (1) mémoire supplémentaire.
PS
Edit: je sais que cette solution ne produit pas la sortie optimale par contre-exemple. Une entrée de
[6, 2, 1]
produit[0, 1, 0, 0, 2, 0, 0, 1, 0]
; une meilleure solution est[0, 0, 1, 0, 2, 0, 0, 1, 0]
.la source
Cet algorithme fonctionne avec un tableau d'entiers, où chaque entier représente une catégorie différente. Il crée des tableaux séparés pour chaque catégorie. Par exemple, si le tableau de départ est [1, 1, 1, 2, 2, 3], il créera trois tableaux, [3], [2, 2], [1, 1, 1].
De là, il combine récursivement les deux plus petits tableaux (dans cet exemple, les [3] et [2,2]) et espace le placement des éléments du plus petit tableau dans le deuxième plus petit tableau basé principalement sur le rapport du nombre des occurrences des catégories plus grandes vs plus petites. Dans cet exemple, nous finirions avec [2,3,2]. Ensuite, il utiliserait ce tableau comme le plus petit tableau qui sera combiné dans le prochain tableau plus grand, jusqu'à ce qu'il ne reste qu'un seul tableau.
la source
CODE ANSI C
Ce code fonctionne en imaginant une ligne droite dans un espace dimensionnel n (où n est le nombre de catégories) passant par l'origine avec un vecteur directionnel (v1, v2, ..., vi, ... vn) où vi est le nombre de articles de la catégorie i. En partant de l'origine, le but est de trouver le prochain point le plus proche de la ligne. En utilisant l'exemple [0 0 0 0 0 1 1 1 2 2 2 3], il produit le résultat [0 1 2 0 3 1 0 2 0 1 2 0]. En utilisant l'exemple de Lungj [0 0 0 0 0 0 1 1 2] nous obtenons [0 1 0 0 2 0 0 1 0], qui est exactement le même que le résultat de Lungj.
L'algorithme est rendu plus efficace en utilisant uniquement l'arithmétique entière et en ne considérant que les deltas entre les distances de chaque point à la ligne.
#define MAXCATEGORIES 100
int main () {int i = 0; int j = 0; int chatsize = 0; vecteur int [MAXCATEGORIES]; int point [MAXCATEGORIES]; catégories int = 0; int totalitems = 0; int meilleur = 0; long d2 = 0L; vp long = 0L; long v2 = 0L; delta long = 0L; bêta longue = 0L;
}
la source
ma solution:
la source