Sau khi Jon đã trình bày lý thuyết , đây là một triển khai:
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;
}
Các thuật toán là O(n)
, trong khi sắp xếp nên được O(n log n)
. Tùy thuộc vào chi phí thực thi mã JS so với sort()
hàm gốc , điều này có thể dẫn đến sự khác biệt đáng chú ý về hiệu suất sẽ tăng theo kích thước mảng.
Trong các bình luận cho câu trả lời của bobobobo , tôi đã nói rằng thuật toán được đề cập có thể không tạo ra xác suất phân bố đồng đều (tùy thuộc vào việc thực hiện sort()
).
Đối số của tôi đi dọc theo các dòng này: Một thuật toán sắp xếp yêu cầu một số c
so sánh nhất định , ví dụ như c = n(n-1)/2
đối với Bubbledort. Hàm so sánh ngẫu nhiên của chúng tôi làm cho kết quả của mỗi so sánh có khả năng như nhau, tức là có kết quả 2^c
có thể xảy ra như nhau . Bây giờ, mỗi kết quả phải tương ứng với một trong các n!
hoán vị của các mục của mảng, điều này làm cho phân phối chẵn không thể trong trường hợp chung. (Đây là một sự đơn giản hóa, vì số lượng so sánh thực tế được ghi nhận phụ thuộc vào mảng đầu vào, nhưng khẳng định vẫn nên giữ.)
Như Jon đã chỉ ra, điều này một mình không có lý do để thích sử dụng Fisher-Yates hơn sort()
, vì trình tạo số ngẫu nhiên cũng sẽ ánh xạ một số hữu hạn các giá trị giả ngẫu nhiên vào các n!
hoán vị. Nhưng kết quả của Fisher-Yates vẫn tốt hơn:
Math.random()
tạo ra một số giả ngẫu nhiên trong phạm vi [0;1[
. Vì JS sử dụng các giá trị dấu phẩy động có độ chính xác kép, nên giá trị này tương ứng với 2^x
các giá trị có thể có 52 ≤ x ≤ 63
(Tôi quá lười để tìm số thực tế). Một phân phối xác suất được tạo bằng cách sử dụng Math.random()
sẽ ngừng hoạt động tốt nếu số lượng các sự kiện nguyên tử có cùng độ lớn.
Khi sử dụng Fisher-Yates, tham số có liên quan là kích thước của mảng, không bao giờ nên tiếp cận 2^52
do những hạn chế thực tế.
Khi sắp xếp với hàm so sánh ngẫu nhiên, về cơ bản, hàm chỉ quan tâm nếu giá trị trả về là dương hay âm, vì vậy điều này sẽ không bao giờ là vấn đề. Nhưng có một điều tương tự: Bởi vì chức năng so sánh được xử lý tốt, nên các 2^c
kết quả có thể, như đã nêu, có thể xảy ra như nhau. Nếu c ~ n log n
sau đó 2^c ~ n^(a·n)
ở đâu a = const
, điều đó làm cho nó ít nhất có thể có 2^c
cùng độ lớn với (hoặc thậm chí nhỏ hơn) n!
và do đó dẫn đến phân phối không đồng đều, ngay cả khi thuật toán sắp xếp nơi ánh xạ lên các hoán vị đồng đều. Nếu điều này có bất kỳ tác động thực tế là ngoài tôi.
Vấn đề thực sự là các thuật toán sắp xếp không được đảm bảo để ánh xạ lên các hoán vị đồng đều. Thật dễ dàng để thấy rằng Mergesort làm như đối xứng, nhưng lý do về một cái gì đó như Bubbledort hoặc quan trọng hơn, Quicksort hoặc Heapsort, thì không.
Điểm mấu chốt: Miễn là sort()
sử dụng Mergesort, bạn nên an toàn một cách hợp lý trừ trường hợp góc (ít nhất là tôi hy vọng đó 2^c ≤ n!
là trường hợp góc), nếu không, tất cả các cược đều bị tắt.