关于Knuth Shuffle算法

这个算法是我前几天才听说的,觉得挺有意思,来写一写。好像出处是TAOCP,但我没看过。#(快哭了)

有的时候我们需要打乱一个排列的顺序,比方说在机器学习里面我们通常都会对一个数据集进行shuffle。以前我就用过numpy里面的random.shuffle。但是我当时就没有仔细想过类似这样一个shuffle是如何实现的。我们先看一下Knuth Shuffle的C伪代码,非常简短:

for (int i = n - 1; i >= 0; --i)
    swap(a[i], a[rand() % (i + 1)]);

其中n是数组的长度,a是待shuffle的数组,swap()交换数组的两项。这里后面对rand()进行一些说明。

首先,我们需要思考一下一个shuffle算法应该满足哪些要求。复杂度不应该太高,OK,Knuth Shuffle满足这个要求。更重要的,通常我们希望这个shuffle之后的序列足够”随机“,这里就出现了公平的概念。什么是公平?我们把这个序列视为1~n的一个排列,那么所有排列就有 n ! n! n!个。如果shuffle后每个排列出现的可能性相同,即 1 n ! \frac{1}{n!} n!1,那么就称这个shuffle算法是公平的。实际上这个算法的精髓就在于它的公平性上。

先思考一下,如果我们要获得一个1~n的随机排列,而且生成每种排列都是等可能的,要怎么做?实际上这并不困难。逐一考虑每个位置,对于第一个位置,我们随便等可能地从1~n里挑出一个数,扔到第一个位置。对于第二个位置,我们从剩下的n - 1个数里再等可能地选一个放进去。以此类推,直至放完。
设这样操作得到的排列是 p 1 p 2 . . . p n p_1p_2...p_n p1p2...pn,即第一次挑出了数字 p 1 p_1 p1,第i次挑出数字 p i p_i pi。再记shuffle后第i个位置的值是 X i X_i Xi。那么根据过程便有 P ( X 1 = p 1 ) = 1 n , P ( X 2 = p 2 ∣ X 1 = p 1 ) = 1 n − 1 , ⋯   , P ( X i = p i ∣ X 1 = p 1 , X 2 = p 2 , . . . , X i − 1 = p i − 1 ) = 1 n − ( i − 1 ) P(X_1 = p_1) = \frac{1}{n}, P(X_2 = p_2 | X_1 = p_1) = \frac{1}{n - 1}, \cdots, P(X_i = p_i | X_1 = p_1, X_2 = p_2, ..., X_{i-1} = p_{i-1}) = \frac{1}{n-(i-1)} P(X1=p1)=n1,P(X2=p2X1=p1)=n11,,P(Xi=piX1=p1,X2=p2,...,Xi1=pi1)=n(i1)1,把它们乘起来就得到 P ( X 1 = p 1 , X 2 = p 2 , . . . , X n = p n ) = 1 n ∗ 1 n − 1 ∗ ⋯ 1 2 ∗ 1 = 1 n ! P(X_1 = p_1, X_2 = p_2, ..., X_n = p_n) = \frac{1}{n} * \frac{1}{n-1} * \cdots \frac{1}{2} * 1 = \frac{1}{n!} P(X1=p1,X2=p2,...,Xn=pn)=n1n11211=n!1。好的。

再来看Knuth Shuffle算法,它其实就是上述过程,只不过是为了代码书写方便从最后一个位置开始向前挑。这里对于rand()的要求便是它产生的随机数应该是均匀的,或者说rand() % (i + 1)对应着能等可能地产生0 ~ i这i + 1个整数的均匀分布。a[rand() % (i + 1)]即从剩下的i + 1个数里面随机挑选了一个出来。我们不希望任何一个数被重复挑选,这个数已经被挑过了,怎么办?swap一举两得,它不仅把挑出来的数放置在当前正在考虑的位置i上,而且使得数组当前的 [ 0 , i ) [0, i) [0,i)项是剩下的还没被挑选的数, [ i , n ) [i, n) [i,n)项是已经安排好位置的数,因为i是递减的,每个数被安排好位置之后便不再会受到影响。

明白了这个过程,也可以正着来写。这回我们让数组项对应的下标是 [ 1 , n ] [1, n] [1,n],数组前面一部分是已经安排好的,后面一部分是等待被挑选的。

for (int i = 1; i <= n; ++i)
    swap(a[i], a[i + rand() % (n - i + 1)]); // select an index from [i, n]
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值