编程珠玑 12 取样问题
2013-06-29 22:08
246 查看
问题:从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。
代码:
[cpp] view
plaincopy
#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;
}
在n较小时,我们的效率比较高,但是当n非常大,接近2^32时,花费时间越来越多,但是代码非常简洁,高效!
-----------------------------------------------
编程珠玑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个数中仍然以等概率抽取其中的一个。
方法:使用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。
代码:
[cpp] view
plaincopy
#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;
}
在n较小时,我们的效率比较高,但是当n非常大,接近2^32时,花费时间越来越多,但是代码非常简洁,高效!
-----------------------------------------------
编程珠玑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个数中仍然以等概率抽取其中的一个。
相关文章推荐
- 《编程珠玑》--读书笔记12章:取样问题
- 取样问题(编程珠玑)
- 编程珠玑:取样问题
- 编程珠玑: 12章 取样问题 12.1程序的输入包含两个整数m和n,其中m<n。输出是0~n-1范围内m个随机整数的有序列表,不允许重复-------解题总结
- (12)取样问题
- 编程珠玑(2)第十二章学习笔记 取样问题
- 12、编程珠玑笔记十二取样问题
- 编程珠玑 12 取样问题
- 编程珠玑之取样问题
- 取样问题——《编程珠玑》读书笔记
- 《编程珠玑》阅读小记(9) — 取样问题
- 编程珠玑:取样问题
- 编程珠玑第12章(取样问题)学习笔记
- 《编程珠玑》阅读小记(9) — 取样问题
- 编程珠玑(三)取样问题
- 编程珠玑第十二章--应用之取样问题
- 编程珠玑--第12章 取样问题
- 取样问题——《编程珠玑》学习笔记
- 12球问题-12球,1球异常,称3次,得该球且只轻重
- VMware7安装Fedora12的问题