【编程珠玑】第十二章 取样问题
一,概述
问题描述:如何生成0~n-1内的m个随机整数(不重复)
需求:按序输出,并且保证每个子集被选中的可能性相等。
1)给出下面代码
算法时间复杂度 O(n)
2)非常规求法:
将n个数写到大小相等的纸片上,摇匀。然后取出m个纸片,按序输出m个纸片
3)解决算法时间复杂度问题,提出以下优化方案
给定一个集合S,每次插入一个元素。插入之前检查S中个数是否达到m,且随机数在不在m中。
4)生成随机数的另一种方式:把包含0 - n-1的数组顺序打乱,然后把前m个元素输出。
更好的方式是,我们只需要打乱前m个元素,然后排序输出。
或者生成大于n个1 - n范围的随机数,然后去掉重复的,输出前面的m个元素
1)
2)选择的m子集的概率相等,如何做?
在1 - n范围内随机选择一个数,然后其后的m-1 个数为所选择的子集(有可能到头,然后从0开始)
3)
当m < n/2时,
总共试了k次,则前面k-1次找到的数都是在集合中,那么只有第k次不在里面,那么概率
p = (m/n)^(k-1) * (n-m)/n
那么期望是 连加 k = 1至无穷大,根据二项式分布可知,期望等于
n/(n-m) < 2
从而可知得证
4)参考算法导论中文版64页。
搜集n张随机赠送的赠券,需要多少次? nlogn次
7)先输出再递归,改成先递归再输出
9)给出一个算法,在最坏情况下只使用m个随机数。而不用丢弃已经生成的随机数
解答:我们总是选择第一行,并使用二分之一的概率选择第二行,使用三分之一的概率选择第三行,以此类推。在该过程结束的时候,每一行具有相同的选中概率(1/n,其中n是文件的总行数):
i = 0
while more input lines
with probability 1.0/++i
choice = this input line //如果前面做了选择,并不会break,而是直到最后一个为止。
print choice
这里比较有些疑惑的是第一行:总是选第一行 为什么概率还是1/n?
概率=1*(1/2)*(2/3)*(3/4)……(n-1/n) =1/n
证明:
当做第i步选择(选择第i行)时,选择该行的概率为1/i,则不选择的概率为(i-1)/i
对于一篇有n行的文档,现需证明最终选定第i行的概率为1/n。
当最终选择第i行,前(i-1)步的选择对最终结果不会产生影响,第i步选择的概率为1/i,即选择第i行,第(i+1~n)步中均采取不选择的动作,即对于任意j(i+1<=j<=n),当前步的概率为(j-1)/j,那么最终的概率为:
(1/i)*((i)/(i+1))*...*((n-1)/n) = 1/n
以一篇只有6行的文档为例,最终选择第2行的概率为:
1/2*(2/3)*(3/4)*(4/5)*(5/6) = 1/6
扩展:
原问题可简化为:
如何从n个有序对象中等概率地任意抽取1个,简记为sample(n,1),其中n未知;
若将该问题改为:
如何从n个有序对象中等概率地任意抽取m个,简记为sample(n,m),其中n未知;
分析:
若n已知,sample(n,m)是普通的抽样问题;当n未知时,可否根据上述算法进行相应的转化求解?
解决方案:
将sample(n,m)问题转化为m个sample(n*,1)问题,更具体一点是,转化为
sample(n,1);sample(n-1,1);sample(n-2,1)....;sample(n-m+1,1)问题。
仍然以一篇6行文档为例,任取其中2行,做法如下:
第一遍,以如下概率选中一行:
1(1) 2(1/2) 3(1/3) 4(1/4) 5(1/5) 6(1/6)
假设选中第2行,接着概率修改如下:
3(1) 4(1/2) 5(1/3) 6(1/4) 1(1/5)
【说明】:当选中第2行,从第3行开始修改概率,并将第2行排除在外,继续扫描,这样能保证在剩下的5个数中仍然以等概率抽取其中的一个。
11)这个题看似很复杂,其实很简单。只需要关注1,2,3如何输出即可。要想获胜,只需要1,2先输出,三个数的全排列中这种情况有2种。所以获胜概率为2/6
=1/3