核心:让每个数被选中的概率都为k/n.
随机取出一个数,剩下的里面再取出一个数,重复k次。
方法一:利用洗牌的原理,将n个数(0至n-1)按次序排好,让最后一个数和一个随机(0~n-1)挑选出的位子进行互换,再让倒数第2个数和(0~n-2)位置中挑选出来的位置进行互换,依次执行k次,数组中的最后k个数就是得出的随机数。
方法二:来自Knuth的《The Art of Computer Programming, Volume2:Seminumerical Algorithms》的伪代码:
select = m
remaining = n
for i = [0,n)
if (rand() % remaining) < select
print i
select--
remaining--
代码理解:针对第一个数使得它被选中的概率为k/n,第二个数使得它被选中的概率为k/n * (k-1)/(n-1) + (n-k)/n * k/(n-1),表示第一个数被选中后,第二个数被选中的概率+第一个数没被选上后,第二个数被选上的概率,……
错误方法:
利用洗牌的原理,将n个数(0至n-1)按次序排好,依次让每个数和一个随机挑选出的位子进行互换,这样肯定不会重复,而且次序被打乱,具有随机性。 只用交换k次,就可以取出k个小于n的互不相同的随机数。
原因:假设k=2,n=5,针对数组中的第一个元素,分析它被选取的概率:第一次执行,被选取的概率为1/5;第二次执行,首先第一个第一次不能不选取的概率为4/5,被换到数组中的第二个位置的概率为1/5,换到其他位置的概率和为3/5,如果在第二个位置被选取的概率为2/5,其他位置被选取的概率为1/5,总和为1/5 + 1/5 * 2/5 + 3/5 * 1/5 = 2/5。
针对数组中的第二个元素,第一次被选取的概率为1/5,第二次被选取的概率为:第一次没选到4/5 * 第二次选到的概率2/5= 8/25,总和为1/5 + 8/25 = 13/25,被选中概率增大。