问题:从0到n-1的n个数中,随机选取m个数字,并且顺序打印出来,可以使用标准库的rand()函数
方法:使用Knuth方法,使用rand() % n < m表示概率m/n,在抽样中,加入m = 2, n = 5,那么第一次抽取0,我们有2/5的概率选择0,如果选择了0,那么在剩余的4个数中选取1的概率应该改为1/4,如果没有选取0,那么以2/4的概率选取1,这样5个数中选取1的概率还是2/5 * 1/4 + 3/5 * 2/4 = 2/5,这样选取1的概率还是2/5。
代码:
#include <iostream> #include <cstdlib> #include <cstdio> #include <ctime> using namespace std; void genknuth(int m, int n) { clock_t start, end; start = clock(); srand(time(NULL)); for(int i = 0; i < n; i++) { if(rand() % (n - i) < m) { cout<< i << endl; if(!(--m)) break; } } end = clock(); std::cout << "Process took " << (double(end - start) / CLOCKS_PER_SEC) << "seconds" << endl; return ; } int main() { genknuth(20, 1<<30); return 0; }
-----------------------------------------------
编程珠玑12章后的第10题:
如何在事先不知道文本文件行数的情况下读取该文件,从中随机选择并输出一行?
解法:首先以概率1选择第一行,如果有更多行,比如第i行,我们以1/i的概率选择第i行,最后输出我们选择的行
伪代码:
-----------------------------------------------
-----------------------------------------------
p = 0
i = 0
while more input lines
with the conditional p = 1.0/++i select this line
choice = the i-th line
end while
print choice
-----------------------------------------------
-----------------------------------------------
最后输出结果,其中p = 1.0 / i++ 可以通过rand() % ++i < 1得到,如果满足条件,我们选择第i行。
【转】
扩展:原问题可简化为:如何从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个数中仍然以等概率抽取其中的一个。