为什么这种随机播放算法没有偏差

时间:2011-07-07 21:09:41

标签: javascript sorting shuffle

我的同事和我正在争论为什么这个list of JS tips & tricks中给出的随机算法不会产生像Jeff Atwood describes这样天真洗牌的偏见结果。

提示中的数组随机播放代码是:

list.sort(function() Math.random() - 0.5);

Jeff的天真洗牌代码是:


for (int i = 0; i < cards.Length; i++)
{
  int n = rand.Next(cards.Length);
  Swap(ref cards[i], ref cards[n]);
}

我写了这个JS来测试shuffle:


var list = [1,2,3];
var result = {123:0,132:0,321:0,213:0,231:0,312:0};
function shuffle() { return Math.random() - 0.5; }
for (var i=0; i<60000000; i++) {
    result[ list.sort(shuffle).join('') ]++;
}

我得到的结果(来自Firefox 5)如:

Order   Count          %Diff True Avg
123      9997461       -0.0002539
132     10003451        0.0003451
213     10001507        0.0001507
231      9997563       -0.0002437
312      9995658       -0.0004342
321     10004360        0.000436

据推测,Array.sort正在走list数组并执行类似杰夫示例的(相邻)元素交换。那么为什么结果看起来没有偏见?

3 个答案:

答案 0 :(得分:2)

我找到出现无偏见的原因。

Array.sort()不仅返回数组,还更改数组本身。如果我们为每个循环重新初始化数组,我们得到如下结果:

123 14941
132 7530
321 7377
213 15189
231 7455
312 7508

这表明存在非常显着的偏见。

对于那些感兴趣的人,这里是修改后的代码:

var result = {123:0,132:0,321:0,213:0,231:0,312:0};
var iterations = 60000;
function shuffle() { 
    comparisons++;
    return Math.random() - 0.5;
}
for (var i=0; i<iterations; i++) {
    var list = [1,2,3];
    result[ list.sort(shuffle).join('') ]++;
}
console.log(result);

答案 1 :(得分:1)

天真洗牌的问题是该值可能已经被交换过,您可能会在以后再次交换它。假设您有三张牌,您可以随机选择一张牌作为第一张牌。如果你以后可以随后用后一张卡交换那张卡,那么你就会放弃第一次选择的随机性。

如果排序是快速排序,它会不断将列表分成两半。下一次迭代将这些组中的每一个随机分成两组。这种情况一直持续下去,直到你完成单张牌,然后你将它们组合在一起。不同之处在于,您永远不会从第二个随机选择的组中取出一张卡并将其移回第一组。

Knuth-Fisher-Yates shuffle与天真的shuffle不同,因为你只挑选一张牌。如果你从牌组中挑选随机牌,你会把卡片放回去再挑选吗?不,你一次拿一张随机卡。这是我第一次听说过它,但是我从高中开始做了类似的事情。 KFY可能更快,因为我在随机声明中有额外的添加。

for (int i = 0; i < cards.Length - 1; i++)
{
  int n = rand.Next(cards.Length - i) + i; // (i to cards.Length - 1)
  Swap(ref cards[i], ref cards[n]);
}

不要将其视为交换,将其视为从卡组中选择随机卡。对于阵列中的每个元素(除了最后一个因为只剩下一个),你从所有剩余的卡中挑出一张随机卡并将其放下,形成一堆随机洗牌的新卡片。如果您已经进行了任何交换,那么您剩余的卡不再是原始订单并不重要,您仍在从剩余的所有卡中挑选一张随机卡。

随机快速排序就像拿一堆牌并将它们随机分成两组,然后将每组随机分成两组,然后一直打开,直到你有单独的牌,然后将它们重新组合在一起。 / p>

答案 2 :(得分:0)

实际上,这并没有实现他天真的随机排序。他的算法实际上是手动转换数组键,而排序主动对列表进行排序。

sort使用quicksortinsertion sort(感谢cwolves指出这一点 - 请参阅注释)来执行此操作(这将根据实现情况而有所不同):

  1. 是大于还是小于B?小吗?递减。
  2. 是大于还是小于C?小吗?递减。
  3. 是大于还是小于D?小吗?在D
  4. 之后插入A.
  5. B大于还是小于C?小吗?递减。
  6. B大于还是小于D?小吗?在D之后和A ...之前插入B
  7. 这意味着平均情况下的大O为O(n log n),最坏情况下的大O为每次循环迭代的O(n ^ 2)。

    同时阿特伍德天真的随机排序很简单:

    1. 从A开始。找到随机值。交换。
    2. 转到B.查找随机值。交换。
    3. 转到C.查找随机值。交换。
    4. (Knuth-Fisher-Yates几乎相同,只是倒退)

      所以他对O(n)的最坏情况和O(n)的平均情况有很大的O.