需求:在n份的调查中随机获取m份样本(m<=n),设计程序完成。并且m个样本中没有重复。
第一种解决方案:
根据概率论的知识:我们假设(n=5,m=2),我们选择数字0的概率为:2/5。可以用下面语句实现:
if((rand() % 5) < 2)
当我们在 选择0的概率下选择1的概率为1/4,在没有 选择0的概率下选择1的概率为2/4,可以分别通过下面语句实现。
//选择0 那么 m--,同时n--
if((rand() % (n-1) < m-1)
//没有选择0 只需要n--
if((rand() % n < m)
所以,我们只需要通过遍历样本的n元素,然后分别判断概率即可获得,m个随机数。实现代码如下:
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int main()
{
srand((unsigned)time(NULL));
int m = 5;
int n = 20;
for(int i=0; i<n; i++)
{
if(rand()%(n-i) < m)
{
cout<<i<<endl;
m--;
}
}
return 0;
}
程序输出如下:
分析:该算法需要遍历整个集合的n个样本,时间复杂度为O(n),空间复杂度为O(m)。
第二种解决方案:
可以使用C++模板库中的set集合,来实现无重复元素的插入,思路比较清晰,实现如下:
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <set>
using namespace std;
int main()
{
srand((unsigned)time(NULL));
int m = 5;
int n = 20;
set<int> S;
while(S.size() < m)
S.insert(rand() % n);
set<int>::iterator it;
for(it=S.begin(); it!=S.end(); ++it)
cout<<*it<<" ";
cout<<endl;
return 0;
}
程序运行结果:
分析:该算法使用STL的关联容器set集合,而set使用红黑树实现的,红黑树又能保证在最坏的情况下每次插入新元素只需要 O(logm) 的时间,而遍历集合需要 O(m) ,所以需要 O(m log m) 的时间复杂度,但是所需要数据结构的开销比较大。
第三种解决方案:
思路:因为需要的是m(m<=n)个元素,所以只需要将前m个元素的顺序打乱(通过产生一个随机数,交换他俩的位置来实现),然后排序输出,前m个元素的内容就可以。
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <set>
#include <algorithm>
using namespace std;
const int m = 5;
const int n = 20;
int main()
{
srand((unsigned)time(NULL));
int *x = new int[n];
for(int i=0; i<n; ++i)//初始化n个元素值
x[i] = i;
int ran;
int tmp;
for(int i=0; i<m; ++i)
{
ran = i + rand()%(n-1-i);//产生一个随机数[i, n-1),包括它本身。
tmp = x[i];
x[i] = x[ran];
x[ran] = tmp;
}
sort(x, x+m);
for(int i=0; i<m; i++)
cout<<x[i]<<" ";
cout<<endl;
return 0;
}
程序输出:3 8 12 14 17 [Finished in 0.1s]
分析:初始化n个元素需要O(n)的时间及空间,以及O(m log m)的排序时间,所以的效率不如第一种方法。
三种方法的总结:
第一种需要遍历整个n所以适合当m较大时,即(m>n/2)
第二种方案的复杂度跟m关系很大,所以适合当m较小的时候。
第三种方案:两种都比较适合,比如说我们需要n为100万时,m为n-10时,我们只需要生成10个数,然后把剩下的数字排序输出就可以。
参考:《编程珠玑》第十二章 取样问题。
开始研究boost。++