Recentemente, escrevi um código que considerava muito ineficiente, mas como ele incluía apenas alguns valores, aceitei. No entanto, ainda estou interessado em um algoritmo melhor para o seguinte:
- Uma lista de objetos X, a cada um deles é atribuído um "peso"
- Resuma os pesos
- Gere um número aleatório de 0 à soma
- Iterar através dos objetos, subtraindo seu peso da soma até que a soma não seja positiva
- Remova o objeto da lista e adicione-o ao final da nova lista
Os itens 2,4 e 5 levam n
tempo e, portanto, é um O(n^2)
algoritmo.
Isso pode ser melhorado?
Como exemplo de uma reprodução aleatória ponderada, um elemento tem uma chance maior de estar na frente com um peso maior.
Exemplo (gerarei números aleatórios para torná-lo real):
6 objetos com pesos 6,5,4,3,2,1; Soma é 21
Escolhi 19: 19-6-5-4-3-2 = -1
assim, 2 vai na primeira posição, os pesos agora são 6,5,4,3,1; Soma é 19
Escolhi 16: 16-6-5-4-3 = -2
assim, 3 vai para a segunda posição, agora os pesos são 6,5,4,1; Soma é 16
Escolhi 3: 3-6 = -3
assim 6 passa para a terceira posição, agora os pesos são 5,4,1; Soma é 10
Escolhi 8: 8-5-4 = -1
assim, 4 vai para a quarta posição, os pesos agora são 5,1; Soma é 6
Escolhi 5: 5-5=0
assim 5 passa para a quinta posição, os pesos agora são 1; Soma é 1
Escolhi 1:, 1-1=0
assim, 1 vai para a última posição, não tenho mais pesos, termino
fonte
Respostas:
Isso pode ser implementado
O(n log(n))
usando uma árvore.Primeiro, crie a árvore, mantendo em cada nó a soma cumulativa de todos os nós descendentes à direita e à esquerda de cada nó.
Para obter uma amostra de um item, faça uma amostragem recursiva do nó raiz, usando as somas cumulativas para decidir se você retorna o nó atual, um nó da esquerda ou um nó da direita. Toda vez que você amostrar um nó, defina seu peso como zero e também atualize os nós pais.
Esta é a minha implementação em Python:
Uso:
weigthed_shuffle
é um gerador, para que você possa experimentar os principaisk
itens com eficiência. Se você deseja embaralhar toda a matriz, basta percorrer o gerador até a exaustão (usando alist
função).ATUALIZAR:
A amostragem aleatória ponderada (2005; Efraimidis, Spirakis) fornece um algoritmo muito elegante para isso. A implementação é super simples e também é executada em
O(n log(n))
:fonte
Edição: Esta resposta não interpreta os pesos da maneira que seria de esperar. Ou seja, um item com peso 2 não tem duas vezes mais chances de ser o primeiro que um com peso 1.
Uma maneira de embaralhar uma lista é atribuir números aleatórios a cada elemento da lista e classificar por esses números. Podemos estender essa ideia, basta escolher números aleatórios ponderados. Por exemplo, você poderia usar
random() * weight
. Escolhas diferentes produzirão distribuições diferentes.Em algo como Python, isso deve ser tão simples quanto:
Cuidado para não avaliar as chaves mais de uma vez, pois elas acabam com valores diferentes.
fonte
max*min = min*max
, e, portanto, qualquer permutação é possível, mas alguns são muito mais provável (especialmente se os pesos não são uniformemente spread)Primeiro, vamos trabalhar para que o peso de um determinado elemento na lista a ser classificada seja constante. Não vai mudar entre iterações. Se isso acontecer, então ... bem, isso é um problema maior.
Para ilustração, vamos usar um baralho de cartas onde queremos ponderar as cartas de face para a frente.
weight(card) = card.rank
. Resumindo, se não sabemos que a distribuição de pesos é de fato O (n) uma vez.Esses elementos são armazenados em uma estrutura classificada, como uma modificação em uma lista de pulos indexáveis, de modo que todos os índices dos níveis possam ser acessados a partir de um determinado nó:
No entanto, nesse caso, cada nó também 'ocupa' tanto espaço quanto seu peso.
Agora, ao procurar um cartão nesta lista, é possível acessar sua posição na lista no horário O (log n) e removê-lo das listas associadas no horário O (1). Ok, pode não ser O (1), pode ser O (log log n) tempo (eu teria que pensar muito mais sobre isso). A remoção do sexto nó no exemplo acima envolveria a atualização dos quatro níveis - e esses quatro níveis são independentes de quantos elementos existem na lista (dependendo de como você implementa os níveis).
Como o peso de um elemento é constante, pode-se simplesmente fazer
sum -= weight(removed)
sem precisar atravessar a estrutura novamente.E, portanto, você tem um custo único de O (n) e um valor de pesquisa de O (log n) e um custo de remoção da lista de O (1). Isso se torna O (n) + n * O (log n) + n * O (1), o que fornece um desempenho geral de O (n log n).
Vamos analisar isso com cartões, porque foi o que eu usei acima.
Este é um baralho realmente pequeno, com apenas 4 cartas. Deve ser fácil ver como isso pode ser estendido. Com 52 cartas, uma estrutura ideal teria 6 níveis (log 2 (52) ~ = 6), embora, se você procurar nas listas de pulos, mesmo isso possa ser reduzido a um número menor.
A soma de todos os pesos é 10. Portanto, você obtém um número aleatório de [1 .. 10) e seus 4 Você percorre a lista de pulos para encontrar o item que está no teto (4). Como 4 é menor que 10, você passa do nível superior para o segundo nível. Quatro é maior que 3, então agora estamos no 2 dos diamantes. 4 é menor que 3 + 7, então passamos para o nível inferior e 4 é menor que 3 + 3, então temos um 3 de diamantes.
Após remover os 3 diamantes da estrutura, a estrutura agora se parece com:
Você observará que os nós ocupam uma quantidade de 'espaço' proporcional ao seu peso na estrutura. Isso permite a seleção ponderada.
Como isso se aproxima de uma árvore binária equilibrada, a pesquisa nela não precisa percorrer a camada inferior (que seria O (n)) e, em vez disso, sair do topo permite pular rapidamente a estrutura para descobrir o que você está procurando para.
Muito disso poderia ser feito com algum tipo de árvore equilibrada. O problema é que o reequilíbrio da estrutura quando um nó é removido fica confuso, já que essa não é uma estrutura de árvore clássica e a limpeza deve lembrar que os 4 diamantes agora são movidos das posições [6 7 8 9] para [3 4 5 6] pode custar mais do que os benefícios da estrutura da árvore.
No entanto, enquanto a lista de pulos se aproxima de uma árvore binária em sua capacidade de pular a lista em O (log n), ela tem a simplicidade de trabalhar com uma lista vinculada.
Isso não quer dizer que seja fácil fazer tudo isso (você ainda precisa manter o controle de todos os links que precisa modificar ao remover um elemento), mas isso significa apenas atualizar todos os níveis que você tem e os links deles. do que tudo à direita na estrutura apropriada da árvore.
fonte