Po tym, jak Jon omówił już teorię , oto implementacja:
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;
}
Algorytm jest O(n)
, a sortowanie powinno O(n log n)
. W zależności od obciążenia związanego z wykonywaniem kodu JS w porównaniu z sort()
funkcją natywną może to prowadzić do zauważalnej różnicy w wydajności, która powinna rosnąć wraz z rozmiarami tablic.
W komentarzach do odpowiedzi bobobobo stwierdziłem, że omawiany algorytm może nie dawać równomiernie rozłożonych prawdopodobieństw (w zależności od implementacji sort()
).
Mój argument idzie w tym kierunku: algorytm sortowania wymaga pewnej liczby c
porównań, np. c = n(n-1)/2
Dla Bubblesort. Nasza funkcja porównania losowego sprawia, że wynik każdego porównania jest równie prawdopodobny, tj. Istnieją 2^c
równie prawdopodobne wyniki. Teraz każdy wynik musi odpowiadać jednej z n!
permutacji wpisów tablicy, co w ogólnym przypadku uniemożliwia równomierne rozłożenie. (Jest to uproszczenie, ponieważ rzeczywista liczba niezbędnych porównań zależy od tablicy wejściowej, ale stwierdzenie powinno nadal obowiązywać).
Jak zauważył Jon, to samo w sobie nie jest powodem, aby preferować Fisher-Yatesa od używania sort()
, ponieważ generator liczb losowych mapuje również skończoną liczbę wartości pseudolosowych do n!
permutacji. Ale wyniki Fisher-Yatesa powinny być nadal lepsze:
Math.random()
tworzy liczbę pseudolosową z zakresu [0;1[
. Ponieważ JS używa wartości zmiennoprzecinkowych o podwójnej precyzji, odpowiada to 2^x
możliwym wartościom gdzie 52 ≤ x ≤ 63
(jestem zbyt leniwy, aby znaleźć rzeczywistą liczbę). Rozkład prawdopodobieństwa wygenerowany za pomocą Math.random()
przestanie działać dobrze, jeśli liczba zdarzeń atomowych jest tego samego rzędu wielkości.
Używając Fishera-Yatesa, odpowiednim parametrem jest rozmiar tablicy, która nigdy nie powinna się zbliżać 2^52
ze względu na ograniczenia praktyczne.
Podczas sortowania za pomocą funkcji porównania losowego funkcja zasadniczo dba o to, czy wartość zwracana jest dodatnia czy ujemna, więc nigdy nie będzie to problemem. Ale jest podobny: ponieważ funkcja porównania zachowuje się dobrze, 2^c
możliwe wyniki są, jak stwierdzono, równie prawdopodobne. Jeśli c ~ n log n
wtedy 2^c ~ n^(a·n)
gdzie a = const
, co sprawia, że jest przynajmniej możliwe, że 2^c
ma taką samą wielkość jak (lub nawet mniej niż), n!
a tym samym prowadzi do nierównomiernego rozkładu, nawet jeśli algorytm sortowania zakłada równomierne mapowanie na permutacje. Jeśli to ma jakikolwiek praktyczny wpływ, jest poza mną.
Prawdziwym problemem jest to, że algorytmy sortowania nie gwarantują równomiernego odwzorowania na permutacje. Łatwo zauważyć, że Mergesort działa tak, jak jest symetryczny, ale rozumowanie o czymś takim jak Bubblesort lub, co ważniejsze, Quicksort lub Heapsort, nie jest.
Podsumowując: tak długo, jak sort()
używasz Mergesort, powinieneś być w miarę bezpieczny, z wyjątkiem przypadków narożnych (przynajmniej mam nadzieję, że 2^c ≤ n!
jest to przypadek narożny), jeśli nie, wszystkie zakłady są wyłączone.