遍历n个元素, 等概率随机取出其中之一元素

转自出处


问题描述
1.一个文件中含有n个元素,只能遍历一遍,要求等概率随机取出其中之一。
先讲一个例子,5个人抽5个签,只有一个签意味着“中签”,轮流抽签,那么这种情况,估计这5个人都不会有异议,都觉得这种方法是公平的,这确实也是公平的,“抓阄”的方法已经有很长的历史了,要是不公平的话老祖先们就不干了。
或许有人觉得先抓的人中签的概率会大一些,因为要是前面的人中了,后面的中签概率就是0了,也可能有人会觉得后面抓的人更有优势,因为前面拿去了不中的签,后面中签的概率就大,那么我们就计算一下吧。
问题分析
第一个人中签的概率是1/5,
第二个人中签的情况只能在第一个人未中时才有可能,所以他中的概率是4/5 X 1/4 = 1/5(4/5表示第一个人未中,1/4表示在剩下的4个签里中签的概率),所以,第二个人最终的中签概率也是1/5,
同理,第三个人中签的概率为:第一个人未中的概率X 第二个人未中的概率X第三个人中的概率,即为:4/5 X 3/4 X 1/3 = 1/5,
一样的可以求出第四和第五个人的概率都为1/5,也就是说先后顺序不影响公平性。
说这个问题是要说明这种前后有关联的事件的概率计算的方式,我们回到第1个问题。前几天我的一个同学电面百度是被问到这个问题,他想了想回答说,依次遍历,遇到每一个元素都生成一个随机数作为标记,如果当前生成的随机数大于为之前保留的元素生成的随机数就替换,这样操作直到文件结束。
但面试官问到:如果生成的随机数和之前保留的元素的随机数一样大的话,要不要替换呢?
你也许会想,一个double的范围可以是-1.79E+308 ~ +1.79E+308,要让两个随机生成的double相等的概率不是一般的微乎其微啊!但计算机世界里有条很让人伤心的“真理”:可能发生的事件,总会发生!
那我们遇到这种情况,是换还是不换?To be or not to be, that’s a question!
就好比,两个人百米赛跑,测出来的时间一样,如果只能有一个人得冠军的话,对于另一个人始终是不公平的,那么只能再跑一次,一决雌雄了!
我的策略
下面,说一个个人认为比较满足要求的选取策略:

  • 顺序遍历,当前遍历的元素为第L个元素,变量e表示之前选取了的某一个元素,此时生成一个随机数r,如果r%L == 0(当然0也可以是0~L-1中的任何一个,概率都是一样的), 我们将e的值替换为当前值,否则扫描下一个元素直到文件结束。
你要是给面试官说明了这样一个策略后,面试官百分之一千会问你这样做是等概率吗?那我们来证明一下。
证明
在遍历到第1个元素的时候,即L为1,那么r%L必然为0,所以e为第一个元素,p=100%,
遍历到第2个元素时,L为2,r%L==0的概率为1/2, 这个时候,第1个元素不被替换的概率为1X(1-1/2)=1/2,
【遍历n个元素, 等概率随机取出其中之一元素】第1个元素被替换,也就是第2个元素被选中的概率为1/2 = 1/2,你可以看到,只有2时,这两个元素是等概率的机会被选中的。
继续,遍历到第3个元素的时候,r%L==0的概率为1/3,前面被选中的元素不被替换的概率为1/2 X (1-1/3)=1/3,前面被选中的元素被替换的概率,即第3个元素被选中的概率为1/3
归纳法证明,这样走到第L个元素时,这L个元素中任一被选中的概率都是1/L,那么走到L+1时,第L+1个元素选中的概率为1/(L+1), 之前选中的元素不被替换,即继续被选中的概率为1/L X ( 1-1/(L+1) ) = 1/(L+1)。证毕。
也就是说,走到文件最后,每一个元素最终被选出的概率为1/n, n为文件中元素的总数。好歹我们是一个技术博客,看不到一丁点代码多少有点遗憾,给出一个选取策略的伪代码,如下:
伪代码
Element RandomPick(file):
Int length = 1;
While( length <= file.size )
If( rand() % length == 0)
Picked = File[length];
Length++;
Return picked
近日,看见我的一些同学在他们的面经里面常推荐结构之法算法之道这个博客,感谢东南大学计算机学院即将找工作的同学们对本博的关注,欢迎批评指正!--BigPotato。

    推荐阅读