Ramdon Shuffle

本文详细介绍了Fisher-Yates(Knuth)洗牌算法,包括线性复杂度的实现方式,证明了其能产生均匀随机排列,并指出了一种常见错误——naïve algorithm,该算法无法产生均匀分布。同时提到了Python中random.shuffle使用的是Knuth-Durstenfeld Shuffle算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Random Shuffle

Goal. Rearrange array so that result is a uniformly random permutation.
(洗牌算法)

Shuffle Sort

使用排序算法来shuffle:

  • Generate a random real number for each array entry.
  • Sort the array. (以随机数为key来排序,每个item和对应的随机数一起exchange)

分析: 复杂度为 O(nlogn)

Linear Shuffle

Algorithm

O(n) 复杂度的shuffle算法——Fisher–Yates shuffle (Knuthe shuffle)。

  • In iteration i , pick integer r between 0 and i uniformly at random.
  • Swap a[i] and a[r].

Pseudo-code:

/*
Input: an array a of length n.
Output: an shuffled array a.
*/
For i = 0 to n-1
    r = random number that in [0,i]
    swap a[i] and a[r]

或者从后往前循环(Knuth-Durstenfeld Shuffle)

To shuffle an array a of n elements (indices 0..n-1):
  for i from n − 1 downto 1 do
       j ← random integer such that 0 ≤ j ≤ i
       exchange a[j] and a[i]



Java code. ( From Algorithm 4th.)

public static void shuffle(Object[] a)
{
    int N = a.length;
    for (int i = 0; i < N; i++)
    {
        // generate random number between 0 and i
        int r = StdRandom.uniform(i + 1);
        exch(a, i, r);
    }
}

注意: 产生随机数的范围为 [0,i] ,而不是 [0,n1]

Proof

Proposition. [Fisher-Yates 1938] Fisher-Yates shuffling algorithm produces a
uniformly random permutation of the input array in linear time.

Proof.1
S[1],S[2],,S[n] 来表示array中的n个元素,为证明该命题,只需证明如下引理

Lemme. The list of elements (S[1],,S[n]) at the end of the algorithm can be any of the n!
permutations with the same probability.
即, (S[1],,S[n]) 在算法结束时,可以等概率地生成所有 n! 排列中的任何一个。

使用归纳法来证明:
当算法完成 i=k(1kn) 次循环后,数组 (S[1],,S[k]) 可以等概率地生成所有 k! 排列中的任何一个。

  • i=1 时,只有 S[1] 一个元素,只有一种排列,显然成立;
  • 假设 i=k 时,命题成立;
  • i=k+1 时,令 x 为第k+1次循环中生成的随机数, P k+1次循环之前数组 (S[1],,S[k]) 的排列, P 表示第 k+1 次循环之后数组的排列,则 P (P,x) 的函数。则根据命题, P k!种可能,而 1xk+1 ,因此pair (P,x) 共有 (i+1)! 中可能。
  • 下面证明:

    • Fact 1: each pair of (P,x) happens with the same probability in the algorithm.
      (P,x) 的每种情况等概率)
    • Fact 2: each pair of (P,x) produces a distinct P .
      (P,x) 的每种情况结果各不相同)

    • Proof of Fact 1. 根据归纳假设, P 可能的k!种情况等概率,而 x 也是等概率生成的,即x [1,,k+1] 中的任何一个数的概率都是 1/(k+1) ,因此每种 (P,x) pair 出现的概率都为 1/(k+1)!

    • Proof of Fact 2. (P1,x1),(P2,x2) 为两个不同的pair,则至少满足如下两个不等式中的一个 P1P2,x1x2 。令 P1,P2 为上述两个pair分别所生成的,

      • Case 1: P1=P2 . 则 x1x2 ,因此 P1,P2 在第 k+1 个位置拥有不同的元素(分别为 S[x1],S[x2] );

      • Case 2: P1P2 . 设 P1 P2 在位置 j 上有不同的元素。若x1x2 x1=x2j ,那么 P1,P2 仍然在位置 j 拥有不同的元素; 否则(x1=x2=j), P1,P2 在位置 k+1 有不同的元素。


A Common Mistake

一种常见的错误是在循环中产生的随机数范围为整个数组长度(the naïve algorithm),这种方法的概率分布是不均衡的。

The naïve algorithm.

for i=0 to n-1
  swap(A[i], A[random(n)])



以数列[1, 2, 3]为例,两种方法的可能结果如下图所示。图1为naïve algorithm产生的结果,从图中可以明显看出结果中包含有重复项,并且每项重复的次数不同。


mistake

图1 - bias


right

图2 - uniform

从数学上看,naïve algorithm一共可以产生 nn 种结果,而不同的permutation一共有 n! 种。由于当 n>2 时, nn 不能被 n! 除尽(因为 n n1没有共同的质因子),因此naïve algorithm不可能产生uniform distribution。

更进一步的对比分析见:
1. Shuffling
2. The Danger of Naïveté

Python - random.shuffle

Python random module中shuffle的源码,使用Knuth-Durstenfeld Shuffle算法。

def shuffle(self, x, random=None, int=int):
    """x, random=random.random -> shuffle list x in place; return None.

    Optional arg random is a 0-argument function returning a random
    float in [0.0, 1.0); by default, the standard random.random.
    """

    randbelow = self._randbelow
    for i in reversed(range(1, len(x))):
        # pick an element in x[:i+1] with which to exchange x[i]
        j = randbelow(i+1) if random is None else int(random() * (i+1))
        x[i], x[j] = x[j], x[i]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值