Quando classificamos uma lista, como
a = [1,2,3,3,2,2,1]
sorted(a) => [1, 1, 2, 2, 2, 3, 3]
elementos iguais são sempre adjacentes na lista resultante.
Como posso realizar a tarefa oposta - embaralhar a lista de forma que elementos iguais nunca (ou tão raramente quanto possível) sejam adjacentes?
Por exemplo, para a lista acima, uma das soluções possíveis é
p = [1,3,2,3,2,1,2]
Mais formalmente, dada uma lista a
, gere uma permutação p
dela que minimize o número de pares p[i]==p[i+1]
.
Como as listas são grandes, gerar e filtrar todas as permutações não é uma opção.
Pergunta bônus: como gerar todas essas permutações de forma eficiente?
Este é o código que estou usando para testar as soluções: https://gist.github.com/gebrkn/9f550094b3d24a35aebd
UPD: escolher um vencedor aqui foi uma escolha difícil, porque muitas pessoas postaram respostas excelentes. @VincentvanderWeele , @David Eisenstat , @Coady , @ enrico.bacis e @srgerg fornecem funções que geram a melhor permutação possível sem falhas. @tobias_k e David também responderam à pergunta bônus (gerar todas as permutações). Pontos adicionais para David pela prova de correção.
O código de @VincentvanderWeele parece ser o mais rápido.
fonte
[1, 2, 1, 3, 1, 4, 1, 5]
é exatamente o mesmo que[1, 3, 1, 2, 1, 4, 1, 5]
pelo seu critério?[1, 1, 1, ..., 2, 3, 4, ..., N]
com2N
elementos. Você pode colocar um númeron > 1
entre cada par de consecutivos1
para obter uma boa permutação. Em seguida, você permuta osN/2
elementos e obtém todas as permutações válidas (o que significa que nenhuma é ruim, mas pode haver mais). O número de tais permutações é O (N ^ 2), então você não pode fazer melhor do que O (N ^ 2). Ainda melhor do que O (N ^ 3) da abordagem ingênua.Respostas:
Isso segue as linhas do pseudocódigo atualmente incompleto de Thijser. A ideia é pegar o mais frequente dos tipos de itens restantes, a menos que tenha acabado de ser levado. (Veja também a implementação deste algoritmo de Coady .)
Prova de correção
Para dois tipos de itens, com contagens k1 e k2, a solução ótima tem k2 - k1 - 1 defeitos se k1 <k2, 0 defeitos se k1 = k2 e k1 - k2 - 1 defeitos se k1> k2. O caso = é óbvio. Os outros são simétricos; cada instância do elemento minoritário evita no máximo dois defeitos de um total de k1 + k2 - 1 possível.
Este algoritmo guloso retorna soluções ótimas, pela seguinte lógica. Chamamos um prefixo (solução parcial) de seguro se ele se estende a uma solução ótima. Claramente, o prefixo vazio é seguro, e se um prefixo seguro for uma solução completa, então essa solução é ótima. Basta mostrar indutivamente que cada passo ganancioso mantém a segurança.
A única maneira de uma etapa gananciosa introduzir um defeito é se apenas um tipo de item permanecer; nesse caso, há apenas uma maneira de continuar e essa maneira é segura. Caso contrário, seja P o prefixo (seguro) logo antes da etapa em consideração, seja P 'o prefixo logo depois e seja S uma solução ótima estendendo P. Se S estende P' também, então está feito. Caso contrário, seja P '= Px e S = PQ e Q = yQ', onde x e y são itens e Q e Q 'são sequências.
Suponha primeiro que P não termine com y. Pela escolha do algoritmo, x é pelo menos tão frequente em Q quanto y. Considere as substrings máximas de Q contendo apenas x e y. Se a primeira substring tiver pelo menos tantos x quanto y, ela poderá ser reescrita sem introduzir defeitos adicionais para começar com x. Se a primeira substring tiver mais y's do que x's, então alguma outra substring terá mais x's do que y's, e podemos reescrever essas substrings sem defeitos adicionais para que x vá primeiro. Em ambos os casos, encontramos uma solução ótima T que estende P ', conforme necessário.
Suponha agora que P termine com y. Modifique Q movendo a primeira ocorrência de x para a frente. Ao fazer isso, introduzimos no máximo um defeito (onde costumava ser x) e eliminamos um defeito (o yy).
Gerando todas as soluções
Esta é a resposta de tobias_k mais testes eficientes para detectar quando a escolha atualmente em consideração é globalmente restrita de alguma forma. O tempo de execução assintótico é ótimo, uma vez que a sobrecarga de geração é da ordem do comprimento da saída. O atraso do pior caso, infelizmente, é quadrático; poderia ser reduzido a linear (ótimo) com melhores estruturas de dados.
fonte
Pseudo-código:
Você só terá
p[i]==p[i+1]
se mais da metade da entrada consistir no mesmo elemento, caso em que não há outra escolha a não ser colocar o mesmo elemento em pontos consecutivos (pelo princípio do pidgeon hole).Conforme apontado nos comentários, esta abordagem pode ter um conflito a mais no caso de um dos elementos ocorrer pelo menos
n/2
vezes (oun/2+1
para ímparn
; isso generaliza para(n+1)/2)
para par e ímpar). Existem no máximo dois desses elementos e, se houver dois, o algoritmo funciona bem. O único caso problemático é quando há um elemento que ocorre pelo menos metade do tempo. Podemos simplesmente resolver esse problema encontrando o elemento e lidando com ele primeiro.Não sei o suficiente sobre python para escrever isso corretamente, então tomei a liberdade de copiar a implementação do OP de uma versão anterior do github:
fonte
[0, 1, 1]
ou[0, 0, 1]
, dependendo se você usa índices baseados em 0 ou em 1.O algoritmo já fornecido de pegar o item mais comum restante que não é o item anterior está correto. Aqui está uma implementação simples, que usa um heap para rastrear o mais comum.
fonte
Você pode gerar todas as permutações 'perfeitamente não classificadas' (que não têm dois elementos iguais em posições adjacentes) usando um algoritmo de retrocesso recursivo. Na verdade, a única diferença para gerar todas as permutações é que você mantém o controle do último número e exclui algumas soluções de acordo:
Observe que neste formulário a função não é muito eficiente, pois cria muitas sublistas. Além disso, podemos acelerar olhando primeiro para os números mais restritos (aqueles com a contagem mais alta). Esta é uma versão muito mais eficiente usando apenas o
counts
dos números.Você pode usar isso para gerar apenas a
next
permutação perfeita oulist
reter todos eles. Mas observe que, se não houver uma permutação perfeitamente não classificada, esse gerador, conseqüentemente, não produzirá resultados.Para contornar esse problema, você pode usar isso junto com um dos algoritmos propostos nas outras respostas como um fallback. Isso garantirá o retorno de uma permutação perfeitamente não classificada, se houver, ou uma boa aproximação caso contrário.
fonte
T(n+1) = something + T(n)
.next(unsort2(collections.Counter(a)))
;-) Mas como este algoritmo gera todas as possibilidades, por que não verificar todas? São apenas 38 para essa lista de teste de 7 elementos.Em python, você pode fazer o seguinte.
Considere que você tem uma lista classificada
l
, você pode fazer:Estas são apenas operações locais e, portanto, devem ser bastante rápidas (
O(N)
). Observe que você mudará del[i] == l[i+1]
para, del[i] == l[i+2]
modo que a ordem com que você terminar é tudo menos aleatório, mas pelo que entendi a pergunta, não é aleatoriedade que você está procurando.A ideia é dividir a lista classificada no meio e, em seguida, trocar todos os outros elementos nas duas partes.
Pois
l= [1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
isso leva al = [3, 1, 4, 2, 5, 1, 3, 1, 4, 2, 5]
O método falha em eliminar todos os
l[i] == l[i + 1]
logo que a abundância de um elemento seja maior ou igual a metade do comprimento da lista.Embora o procedimento acima funcione bem, desde que a abundância do elemento mais frequente seja menor que a metade do tamanho da lista, a função a seguir também lida com os casos limites (o famoso problema off-by-one) em que todos os outros elementos começando com o primeiro deve ser o mais abundante:
fonte
[3, 2, 1, 2, 1, 3, 2]
(retorna[2, 1, 3, 1, 2, 2, 3]
, deveria ser(3, 2, 1, 2, 1, 3, 2)
) - veja a essência+1
. Tente novamente agora.[1, 3, 3, 3, 3, 1, 1]
=>[3, 1, 3, 3, 1, 3, 1]
Aqui está um bom algoritmo:
Em primeiro lugar, conte para todos os números a frequência com que ocorrem. Coloque a resposta em um mapa.
classifique esse mapa de forma que os números que ocorrem com mais frequência venham primeiro.
O primeiro número de sua resposta é o primeiro número no mapa classificado.
Recorra ao mapa com o primeiro sendo agora um menor.
Se você deseja melhorar a eficiência, procure maneiras de aumentar a eficiência da etapa de classificação.
fonte
Em resposta à pergunta bônus: este é um algoritmo que encontra todas as permutações de um conjunto onde nenhum elemento adjacente pode ser idêntico. Acredito que este seja o algoritmo mais eficiente conceitualmente (embora outros possam ser mais rápidos na prática porque se traduzem em um código mais simples). Ele não usa força bruta, apenas gera permutações únicas, e os caminhos que não levam a soluções são interrompidos no ponto inicial.
Usarei o termo "elemento abundante" para um elemento em um conjunto que ocorre com mais freqüência do que todos os outros elementos combinados, e o termo "abundância" para o número de elementos abundantes menos o número de outros elementos.
por exemplo, o conjunto
abac
não tem elemento abundante, os conjuntosabaca
eaabcaa
têma
como elemento abundante e abundância 1 e 2, respectivamente.Este algoritmo gera permutações únicas. Se você quiser saber o número total de permutações (onde
aba
é contado duas vezes porque você pode trocar os a's), multiplique o número de permutações únicas por um fator:onde N é o número de ocorrências de cada elemento do conjunto. Para um conjunto,
abcdabcaba
isso seria 4! * 3! * 2! * 1! ou 288, que demonstra quão ineficiente é um algoritmo que gera todas as permutações em vez de apenas as únicas. Para listar todas as permutações neste caso, apenas liste as permutações exclusivas 288 vezes :-)Abaixo está uma implementação (um tanto desajeitada) em Javascript; Suspeito que uma linguagem como o Python pode ser mais adequada para esse tipo de coisa. Execute o trecho de código para calcular as permutações separadas de "abracadabra".
fonte
A ideia é ordenar os elementos do mais comum ao menos comum, pegar o mais comum, diminuir sua contagem e colocá-lo de volta na lista mantendo a ordem decrescente (mas evitando colocar o último elemento usado primeiro para evitar repetições quando possível) .
Isso pode ser implementado usando
Counter
ebisect
:Exemplo
fonte
[1, 1, 2, 3]
onde existem soluções como[1, 2, 1, 3]
.[1, 2, 3, 2, 3, 2, 2]
retornar[2, 3, 1, 2, 3, 2, 2]
(1 falha), enquanto o ideal é(2, 1, 2, 3, 2, 3, 2)
) - veja a essência.Ele fornecerá o mínimo de itens da lista em seus lugares originais (por valor de item), então tentará, por exemplo, colocar os 1s, 2s e 3s longe de suas posições classificadas.
fonte
best_shuffle
e gerou[1,1,1,2,3] -> [3, 1, 2, 1, 1]
- não é o ideal!Comece com a lista classificada de comprimento n. Seja m = n / 2. Pegue os valores de 0, depois m, depois 1, depois m + 1, depois 2, depois m + 2 e assim por diante. A menos que você tenha mais da metade dos números iguais, você nunca obterá valores equivalentes em ordem consecutiva.
fonte
Por favor, perdoe minha resposta no estilo "eu também", mas a resposta de Coady não poderia ser simplificada para isso?
Editar: aqui está uma versão do python 2 que retorna uma lista:
fonte
fonte