以下是Knuth for Reservoir Sampling的pseodo代码(如何从一组k
数字中选择n
个数字,确保每个数字具有相同的概率)。
Init:一个大小为k
的水库。
for i = k+1 to N
M = random(1, i);
if (M < k) // should this be if (M <= k)
SWAP the Mth value and ith value
end if
end for
从此代码中,M < K
的概率为(k-1)/i
,而非k/i
,因此我认为循环体中的if
语句应为{{1} }}。我试着测试它们之间的区别,但我没有到达任何地方。
答案 0 :(得分:4)
你是对的。但是,你的代码没有正确实现算法R.这个bug是你的(或者是谁编写了这段代码),而不是Knuth的; - )
引自Knuth(计算机程序设计艺术Vol.2 3Ed 1998,p.144):
...如果我们事先不知道N的值,就会出现问题,因为N的精确值在算法S中是至关重要的。假设我们想要从文件中随机选择n个项目,而不确切地知道该文件中有多少个。我们可以先查看记录,然后再通过第二遍来选择它们;但通常更好的是采样m>第一遍中的原始项目中的n个,其中m远小于N,因此在第二遍中必须考虑m个项目。当然,诀窍就是这样做 这样一种方式,即最终结果是原始文件的真正随机样本。
由于我们不知道输入何时结束,我们必须跟踪到目前为止看到的输入记录的随机样本,因此总是为最终做好准备。当我们阅读输入时,我们将构建一个“库”,其中仅包含先前样本中出现的记录。前n个记录总是进入水库。当输入(t + 1)st记录时,对于t> n,我们将在内存中有一个n个索引的表,指向我们从第一个t中选择的记录。问题是保持这种情况,增加1,即从现在已知的t + 1记录中找到一个新的随机样本。不难看出我们应该以新概率n /(t + 1)在新样本中包含新记录,在这种情况下,它应该替换前一个样本的随机元素。
因此,以下程序完成了这项工作:
算法R (水库采样)。从未知大小的文件中随机选择n个记录&gt; n,给定n> 0.称为“库”的辅助文件包含最终样本的候选记录。该算法使用不同索引 I [j]的表来表示1&lt; j&lt; n,每个都指向水库中的一个记录。
R1。 [初始化。]输入前n个记录并将它们复制到储存库。设置 I [j]←j为1&lt; j&lt; n,并设置t←m←n。 (如果正在采样的文件少于n个记录,则当然有必要中止算法并报告失败。在此算法期间,索引 I [1],...,我 [n]指向当前样本中的记录; m是库的大小; t是到目前为止处理的输入记录数。)
R2。 [文件结束?]如果没有更多要输入的记录,请转到步骤R6。
R3。 [生成并测试。]将t增加1,然后生成1到t(含)之间的随机整数M.如果M> n,转到R5。
R4。 [添加到水库。]将输入文件的下一条记录复制到水库,将m增加1,然后设置I [M]←m。 (I [M]先前指出的记录在样本中被新记录替换。)返回R2。
R5。 [跳过。]跳过输入文件的下一条记录(不要将其包含在水库中),然后返回步骤R2。
R6。 [第二遍。]对 I 表条目进行排序,以便 I [1]&lt; ......&lt; I 的[N];然后通过水库,将带有这些指数的记录复制到保存最终样本的输出文件中。
算法R的伪代码看起来像:
for j= 1 to n
Reservoir[j]= File.GetNext()
I[j]= j
t=n // number of input records so far
m=n // size of the reservoir
while not File.EOF()
x= File.GetNext()
t++
M= Random(1..t)
if (M<=n)
m++
Reservoir[m]= x
I[M]= m
Sort(I[1..n])
for j= 1 to n
Output[j]= Reservoir[I[j]]
答案 1 :(得分:3)
是的,它应该是<=
。这是基于查看wikipedia中的代码,并通过this answer,这是一个很好的解释,为什么超集中的每个数字都有相同的出现机会。但是,我会毫不犹豫地声称Knuth有一个错误!