n个整数中随机选择m个整数(m<n),随机数的范围为[0,n-1].
要求:每个整数最多出现一次;每个子集出现的概率相同。
假设:函数bigrand(),返回一个大的随机整数(比m,n大很多)
函数randint(i,j)返回[i,j]间均匀的随机整数
Knuth《计算机程序设计艺术》中3.4.2节提出的算法
C++实现
在一个初始为空的集合中插入随机整数,直到插入足够的整数.
伪代码:
代码
缺点:空间消耗增大,m=1700000,128MB内存不够用
优点:时间复杂度减小
时间复杂度
插入:O(logm)
集合内部迭代:O(m)
总时间:O(m*logm)[和n相比,m较小]
1,弄乱一个n个元素数组,这个数组包含数值范围为[0,n-1]【3.4.2节Knuth的算法P作用:弄乱数组x[0...n-1]】;
2,排序前m个元素并输出
空间复杂度:O(n)
时间复杂度:O(n+m*logm)
《STL源码剖析》random_shuffle的实现:
即相当于:swap(i,rand()%(i+1)),和Knuth的算法P不同。P算法是swap(i,randint(i,n-1))
习题答案
代码和概率分析:摘录自
:http://blog.csdn.net/morewindows/article/details/7659532
概率分析:
分析:对代码进行下讲解,以三行数据为例,首先对文本的第一行,rand()% 1,结果必然为0。所以第一行已被选中了,然后对第二行,rand()%2,结果要么为0,要么为1。故第二行有 50的可能性被选中,然后对第三行,rand()%3,显然被选中的概率为1/3。故有:
选中第一行的概率为1 * 1/2(第2行没被选中概率) * 2/3(第三行没被选中概率) = 1/3。
选中第二行的概率为1/2(第1行没被选中概率) * 2/3 = 1/3。
选中第三行的概率为1/3。
故每一行被选中是等概率的。
补充-完整的证明过程:原链接:http://blog.csdn.net/v_july_v/article/details/6712171
顺序遍历,当前遍历的元素为第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,
第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为文件中元素的总数。