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,n−1]
。
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(1≤k≤n)
次循环后,数组
(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! 种可能,而 1≤x≤k+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,则至少满足如下两个不等式中的一个 P1≠P2,x1≠x2 。令 P′1,P′2 为上述两个pair分别所生成的,
Case 1: P1=P2 . 则 x1≠x2 ,因此 P′1,P′2 在第 k+1 个位置拥有不同的元素(分别为 S[x1],S[x2] );
Case 2: P1≠P2 . 设 P1 与 P2 在位置 j 上有不同的元素。若
x1≠x2 或 x1=x2≠j ,那么 P′1,P′2 仍然在位置 j 拥有不同的元素; 否则(x1=x2=j ), P′1,P′2 在位置 k+1 有不同的元素。
- Fact 1: each pair of
(P,x)
happens with the same probability in the algorithm.
□
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产生的结果,从图中可以明显看出结果中包含有重复项,并且每项重复的次数不同。
图1 - bias
图2 - uniform
从数学上看,naïve algorithm一共可以产生
nn
种结果,而不同的permutation一共有
n!
种。由于当
n>2
时,
nn
不能被
n!
除尽(因为
n
与
更进一步的对比分析见:
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]