Estou procurando um algoritmo para distribuir valores de uma lista para que a lista resultante seja o mais "equilibrada" ou "distribuída uniformemente" quanto possível (entre aspas, porque não tenho certeza de que essas são as melhores maneiras de descrevê-la ... mais tarde, fornecerei uma maneira de medir se um resultado é melhor que outro).
Então, para a lista:
[1, 1, 2, 2, 3, 3]
Um dos melhores resultados, após a redistribuição dos valores, é:
[1, 2, 3, 1, 2, 3]
Pode haver outros resultados tão bons quanto este e, é claro, isso fica mais complicado com um conjunto de valores menos uniforme.
É assim que se mede se um resultado é melhor que outro:
Conte as distâncias entre cada item e o próximo item com o mesmo valor.
Calcule o desvio padrão para esse conjunto de distâncias. Uma menor dispersão significa um resultado melhor.
Observações:
- Ao calcular uma distância e chegar ao fim da lista sem encontrar um item com o mesmo valor, voltamos ao início da lista. Portanto, no máximo, o mesmo item será encontrado e a distância para esse item será o comprimento da lista. Isso significa que a lista é cíclica ;
- Uma lista típica possui ~ 50 itens com ~ 15 valores diferentes em quantidades variadas.
Tão:
- Para o resultado
[1, 2, 3, 1, 2, 3]
, as distâncias são[3, 3, 3, 3, 3, 3]
e o desvio padrão é0
; - Para o resultado
[1, 1, 2, 2, 3, 3]
, as distâncias são[1, 5, 1, 5, 1, 5]
e o desvio padrão é2
; - O que torna o primeiro resultado melhor que o segundo (o desvio menor é melhor).
Dadas essas definições, peço uma pista de quais algoritmos ou estratégias devo procurar.
Respostas:
Eu me deparei com essa pergunta enquanto pesquisava um problema semelhante: adições ótimas de líquidos para reduzir a estratificação. Parece que minha solução também seria aplicável à sua situação.
Se você deseja misturar os líquidos A, B e C na proporção 30,20,10 (ou seja, 30 unidades de A, 20 unidades de B e 10 unidades de C), você terminará com estratificação se adicionar todos os o A, depois todo o B e depois todo o C. É melhor misturar unidades menores. Por exemplo, faça adições de unidade única na sequência [A, B, A, C, B, A]. Isso impedirá completamente a estratificação.
A maneira que encontrei para fazer isso é tratá-lo como uma espécie de mesclagem, usando uma fila de prioridade. Se eu criar uma estrutura para descrever as adições:
A frequência é expressa como "um a cada N". Então A, que é adicionado três em seis vezes, tem uma frequência de 2 (6/3).
E inicialize um heap que contém inicialmente:
Agora, removo o primeiro item da pilha e o produzo. Em seguida, reduza sua contagem em 1 e aumente a Prioridade por frequência e adicione-a novamente ao heap. O heap resultante é:
Em seguida, remova B do heap, produza e atualize-o e adicione-o novamente ao heap:
Se eu continuar dessa maneira, recebo a mistura desejada. Uso um comparador personalizado para garantir que, quando itens de prioridade igual forem inseridos no heap, aquele com o maior valor de frequência (ou seja, o menos frequente) seja solicitado primeiro.
Eu escrevi uma descrição mais completa do problema e sua solução no meu blog e apresentei alguns códigos C # funcionais que ilustram isso. Consulte Distribuição uniforme de itens em uma lista .
Atualizar após comentários
Acho que meu problema é semelhante ao do OP e, portanto, que minha solução é potencialmente útil. Peço desculpas por não enquadrar minha resposta mais nos termos da pergunta do OP.
A primeira objeção, de que minha solução está usando A, B e C, em vez de 0, 1 e 2, é facilmente remediada. É simplesmente uma questão de nomenclatura. Acho mais fácil e menos confuso pensar e dizer "dois A's" em vez de "dois 1's". Mas, para os propósitos desta discussão, modifiquei minhas saídas abaixo para usar a nomenclatura do OP.
É claro que meu problema lida com o conceito de distância. Se você deseja "espalhar as coisas uniformemente", a distância está implícita. Mas, novamente, foi minha falha por não mostrar adequadamente como meu problema é semelhante ao problema do OP.
Fiz alguns testes com os dois exemplos que o OP forneceu. Isso é:
Na minha nomenclatura, esses são expressos como [2,2,2] e [4,3,2,1], respectivamente. Ou seja, no último exemplo, "4 itens do tipo 0, 3 itens do tipo 1, 2 itens do tipo 2 e 1 item do tipo 3."
Eu executei meu programa de teste (como descrito imediatamente abaixo) e publiquei meus resultados. Ausência de informações do OP, não sei dizer se meus resultados são semelhantes a, piores ou melhores que os dele. Nem posso comparar meus resultados com os resultados de mais ninguém porque ninguém mais postou nenhum.
Posso dizer, no entanto, que o algoritmo fornece uma boa solução para o meu problema de eliminar a estratificação ao misturar líquidos. E parece que fornece uma solução razoável para o problema do OP.
Para os resultados mostrados abaixo, usei o algoritmo que detalhei na entrada do meu blog, com a prioridade inicial definida como
Frequency/2
e o comparador de heap modificado para favorecer o item mais frequente. O código modificado é mostrado aqui, com as linhas modificadas comentadas.Executando meu programa de teste com o primeiro exemplo do OP, recebo:
Portanto, meu algoritmo funciona para o problema trivial de todas as contagens serem iguais.
Para o segundo problema que o OP postou, obtive:
Não vejo uma maneira óbvia de melhorar isso. Poderia ser reorganizado para fazer as distâncias para o item 0 [2,3,2,3] ou algum outro arranjo de 2 e 3, mas isso mudará os desvios para os itens 1 e / ou 2. Eu realmente não sei o que "ótimo" está nessa situação. É melhor ter um desvio maior nos itens mais frequentes ou nos menos frequentes?
Na falta de outros problemas do OP, usei suas descrições para fazer algumas das minhas. Ele disse em seu post:
Então, meus dois testes foram:
E meus resultados:
E para o segundo exemplo:
fonte
Isso "cheira" como se fosse NP-difícil. Então, o que você faz quando tem um problema NP-hard? Lance uma heurística, ou um algoritmo de aproximação, ou use um solucionador SAT.
No seu caso, se você não precisar da solução ótima absoluta, um ponto de partida razoável pode ser tentar o recozimento simulado . Existe uma maneira natural de pegar qualquer solução candidata e movê-la para uma solução candidata próxima: escolha aleatoriamente dois itens da lista e troque-os. O recozimento simulado tentará iterativamente melhorar a solução. Você pode encontrar muitos recursos no recozimento simulado, se não estiver familiarizado. Você também pode experimentar outros conjuntos de "movimentos locais" que fazem pequenas alterações em uma solução candidata, com a esperança de melhorá-la de forma incremental (isto é, reduzir o desvio padrão das distâncias).
Mas eu sugiro que você comece com um recozimento simulado. Essa é a primeira coisa que eu tentaria, porque acho que poderia funcionar.
fonte
Esboço de um algoritmo heurístico
Não tenho uma solução exata para esse problema. Mas como o comentário de Raphael sugere que ele se parece com o problema de partição, para o qual os algoritmos heurísticos foram desenvolvidos, tentarei uma abordagem heurística. Este é apenas um esboço de um algoritmo heurístico.
Isso guiará nosso algoritmo.
Pode ser um valor com muitas poucas ocorrências no início. Eu acho que realmente não faz diferença, uma vez que as restrições criadas pela ocupação de slots são na proporção do número de valores bem colocados (?).
O primeiro valor considerado pode ser colocado sem qualquer restrição. Os outros valores devem ser colocados de modo a minimizar sua contribuição para o desvio padrão, mas apenas nos slots deixados livres por quaisquer valores que tenham sido colocados anteriormente.
A colocação das ocorrências de um valor nos slots restantes pode ser feita com um algoritmo de programação dinâmica, de modo a mesclar cálculos que colocam o mesmo número de valores entre duas posições, mantendo apenas aqueles que têm contribuição mínima para o desvio padrão (ou seja, valor mínimo para a soma do quadrado de seus desvios).
Em seguida, repita para o próximo valor restantej |n/nj−v|
Em seguida, você coloca os valores singleton nos slots restantes.
Acredito que isso geralmente deva dar uma solução razoável, mas ainda não tenho idéia de como provar ou estimar a lacuna com uma solução ideal.
fonte
[0, 0, 0, 0, 1, 1, 1, 2, 2, 3]
ev4
, colocaríamos os primeiros valores1
(10/3 = 3.33
, mais próximo de v), depois2
(10/2 = 5
, o próximo mais próximo) e depois0
(10/4 = 2.5
)? Ou: você poderia dar um exemplo de "diminuição do desvio médio da distância do valor v"?Parece que estou muito atrasado para a festa, mas postando caso alguém se depare com isso novamente. Minha solução é semelhante à @ babou's plus. Hoje cedo, tive um problema de agendamento em um sistema incorporado que me levou a esse segmento. Eu tenho uma implementação específica para o meu problema em C, mas achei que postaria uma solução mais genérica em Python aqui (a versão C é complicada pelo fato de me restringir a uma pilha pequena e de tamanho fixo e sem memória alocações, então eu executo todo o algoritmo no local). A técnica de suavização de serrilhado usada abaixo é algo que você pode usar para desenhar uma linha em uma tela com cores de 2 bits. O algoritmo aqui obtém uma pontuação mais baixa (ou seja, melhor) quando medido usando a soma do desvio padrão para as entradas usadas por Jim Mischel do que essa solução específica.
Resultados para
Se forem fornecidas entradas do formulário especificado por @moraes, pode-se convertê-lo em um formulário utilizável por esta função em O (n) etapas usando Big Omega (n * log (n)) bits de memória em que n é o número de itens ( em uma lista com 255 elementos, você não precisará de mais de 255 bytes extras) mantendo uma matriz paralela com a contagem de repetições. Como alternativa, é possível executar um par de classificações no local com memória extra O (1).
PS
Edit: Eu sei que esta solução não produz a saída ideal por contra-exemplo. Uma entrada de
[6, 2, 1]
produz[0, 1, 0, 0, 2, 0, 0, 1, 0]
; uma solução melhor é[0, 0, 1, 0, 2, 0, 0, 1, 0]
.fonte
Este algoritmo trabalha com uma matriz de números inteiros, onde cada número inteiro representa uma categoria diferente. Ele cria matrizes separadas para cada categoria. Por exemplo, se a matriz inicial for [1, 1, 1, 2, 2, 3], ela criará três matrizes, [3], [2, 2], [1, 1, 1].
A partir daí, combina recursivamente as duas matrizes menores (neste exemplo, o [3] e o [2,2]) e espaça a colocação dos elementos da matriz menor na segunda menor matriz, baseada principalmente na proporção do número de ocorrências das categorias maiores e menores. Neste exemplo, terminaríamos com [2,3,2]. Em seguida, usaria essa matriz como a matriz menor que será combinada na próxima matriz maior, até que exista apenas uma matriz.
fonte
CÓDIGO ANSI C
Esse código funciona imaginando uma linha reta no espaço dimensional n (onde n é o número de categorias) passando pela origem com o vetor direcional (v1, v2, ..., vi, ... vn) em que vi é o número de itens da categoria i. A partir da origem, o objetivo é encontrar o próximo ponto mais próximo da linha. Usando o exemplo [0 0 0 0 0 1 1 1 2 2 2 3], produz o resultado [0 1 2 0 3 1 0 2 0 1 2 0]. Usando o exemplo de Lungj [0 0 0 0 0 0 1 1 2], obtemos [0 1 0 0 2 0 0 1 0], que é exatamente o mesmo que o resultado de Lungj.
O algoritmo é mais eficiente usando apenas aritmética inteira e considerando apenas os deltas entre as distâncias de cada ponto até a linha.
#define MAXCATEGORIES 100
int main () {int i = 0; int j = 0; int tamanho do gato = 0; vetor int [MAXCATEGORIES]; ponto int [MAXCATEGORIES]; int categorias = 0; int totalitems = 0; int melhor = 0; d2 longo = 0L; vp longo = 0L; v2 longo = 0L; delta longo = 0L; beta longo = 0L;
}
fonte
minha solução:
fonte