【原】水库抽样详解
此经典算法收录于TAOCP中,但高大爷只给出算法步骤没给出证明,这里将详细定义并做证明
定义:
从N个记录中随机选择n个记录,但一开始并不知道N为多少。
算法:
1. 选择抽样算法:
可顺序扫描N个记录,对第t+1个记录以概率(n-m)/(N-t)来选择(m为已选得的记录数),但这样做必须事先顺序扫描一遍文件以获得N的大小。
2. 水库抽样算法:
2.1 若记录十分大
第一遍扫描文件时获得m>=n个原始记录的“水库”和一张有n个索引的表,其中记录了m个记录中哪n个被抽中,再顺序扫描一遍m个记录的水库即得到n个抽样。
当输入第t+1个记录而t>=n时,n个索引的表中记录了水库中我们从头t个记录中选取为抽样那些记录,对应于水库中的元素。由于不知道输入何时结束,所以我们以n/(t+1)的概率来把新记录包括到新抽样中(这样当结束时每个记录才能有相同的选中概率),并且应该去掉以前抽样中的一个随机元素。
算法如下R1—>R6: 给定n>0,确切大小未知但>=n的一个文件中随机选择n个记录。一个叫“水库”的辅助文件存放有m个作为最后抽样候选者的所有记录。一个有n个索引的表,I[j], 1<=j<=n,每个索引对应着水库中的一个记录。
R1:[初始化] 输入头n个记录,并复制到水库中,对1<=j<=n, I[j]=j,并置t=m=n(若被抽样文件少于n个,有必要中断算法并报告错误)
R2:[文件结束?] 若已无记录可输入,则=>R6
R3:[生成并检验] t增加1,然后生成[1,t]之间的随机整数M。若M>n,则=>R5(即选择概率为n/(t+1))
R4:[加入到水库] 复制下一条记录到水库中,m增加1,并且 I[M]=m(以前由I[M]指向的记录由此新记录替代),之后=>R2
R5:[跳] 跳过下一条记录(不将其置入水库),然后=>R2
R6:[第二次扫描水库] 对I表排序使得I[1]<I[2]<…<I[n]。然后扫描水库,把具有这些索引的记录复制到最后的输出文件中。
2.2 若记录充分小:
当前抽样的n个记录可放入内存中时,当然完全没有必要建立“水库”,删除R6,变量m和水库,用一个记录表pool代替I表,把它初始化为R1中的头n个记录,并置I[M]为新记录。
先将文件中前n项数据放入pool中,再对之后的第i个元素以n/i的概率替换掉pool中的某一个元素。
算法伪码:
1: for i in [n+1,N]
2: M = rand(1,i);
3: if (M <= n)
4: swap the ith and Mth data
需要证明以这种方法选择,所有的元素等概率被选择。归纳法证明如下:
- [初始情况] 当尚未选择时,出现在pool中的n个元素概率是相同的,都是1。证明当第n+1个元素以n/(n+1)的概率被选中时,前n个元素在pool中的概率为n/(n+1),即新旧元素被选中的概率相同:
任意元素被替换的概率为:(n/(n+1)) * (1/n) = 1/(n+1)
所以前n个元素被选择(没有被替换)的概率为:1-1/(n+1) = n/(n+1)
而且前n个元素在选择前在pool中的概率为1,因此两概率相乘:1 * (n/(n+1)) = n/(n+1),符合结论
- [假设] 当第i个元素以n/i的概率被选中时,前i-1个元素被选中的概率同样都是n/i。
- [证明] 第i+1个元素的情况:前i个元素被选中的概率由两部分组成:
a. 第i+1次选择之前,这i个元素在pool中:
由假设知,前i个元素被选中的概率为n/i,也就是在pool中的概率
b. 第i+1次选择没有替换掉现在pool中的元素:
先算任意元素被替换的概率: (n/(i+1)) * (1/n) = 1/(i+1)
因此没有被替换掉的概率为:1-1/(i+1) = i/(i+1)
由a和b相乘,即得到前i个元素被选中的概率:(n/i) * (i/(i+1)) = n/(i+1),符合假设。