Depois que Jon já cobriu a teoria , aqui está uma implementação:
function shuffle(array) {
var tmp, current, top = array.length;
if(top) while(--top) {
current = Math.floor(Math.random() * (top + 1));
tmp = array[current];
array[current] = array[top];
array[top] = tmp;
}
return array;
}
O algoritmo é O(n)
, enquanto a classificação deve ser O(n log n)
. Dependendo da sobrecarga de execução do código JS em comparação com a sort()
função nativa , isso pode levar a uma diferença notável no desempenho, que deve aumentar com o tamanho da matriz.
Nos comentários à resposta de bobobobo , afirmei que o algoritmo em questão pode não produzir probabilidades distribuídas uniformemente (dependendo da implementação de sort()
).
Meu argumento segue estas linhas: Um algoritmo de classificação requer um certo número c
de comparações, por exemplo, c = n(n-1)/2
para Bubblesort. Nossa função de comparação aleatória torna o resultado de cada comparação igualmente provável, ou seja, há resultados 2^c
igualmente prováveis . Agora, cada resultado deve corresponder a uma das n!
permutações das entradas da matriz, o que impossibilita uma distribuição uniforme no caso geral. (Isso é uma simplificação, pois o número real de comparações necessárias depende da matriz de entrada, mas a asserção ainda deve ser mantida.)
Como Jon apontou, isso por si só não é motivo para preferir o uso de Fisher-Yates sort()
, pois o gerador de números aleatórios também mapeará um número finito de valores pseudo-aleatórios para as n!
permutações. Mas os resultados de Fisher-Yates ainda devem ser melhores:
Math.random()
produz um número pseudo-aleatório no intervalo [0;1[
. Como o JS usa valores de ponto flutuante de precisão dupla, isso corresponde aos 2^x
valores possíveis em que 52 ≤ x ≤ 63
(estou com preguiça de encontrar o número real). Uma distribuição de probabilidade gerada usando Math.random()
parará de se comportar bem se o número de eventos atômicos for da mesma ordem de magnitude.
Ao usar Fisher-Yates, o parâmetro relevante é o tamanho da matriz, que nunca deve ser abordada 2^52
devido a limitações práticas.
Ao classificar com uma função de comparação aleatória, a função basicamente se importa apenas se o valor de retorno for positivo ou negativo, portanto isso nunca será um problema. Mas existe uma similar: como a função de comparação é bem comportada, os 2^c
possíveis resultados são, como afirmado, igualmente prováveis. Se c ~ n log n
então , 2^c ~ n^(a·n)
onde a = const
, o que torna pelo menos possível que 2^c
seja da mesma magnitude que (ou até menor que) n!
e, assim, leve a uma distribuição desigual, mesmo que o algoritmo de classificação seja mapeado uniformemente nas permutações. Se isso tem algum impacto prático está além de mim.
O verdadeiro problema é que não é garantido que os algoritmos de classificação sejam mapeados uniformemente nas permutações. É fácil ver que o Mergesort faz o que é simétrico, mas raciocinar sobre algo como Bubblesort ou, mais importante, Quicksort ou Heapsort, não é.
Conclusão: Enquanto sort()
usar o Mergesort, você deverá estar razoavelmente seguro, exceto nos casos de canto (pelo menos, espero que 2^c ≤ n!
seja um caso de canto), se não, todas as apostas serão desativadas.