在这本书的第12章有个随机抽样的问题,大概是这样的:
从n个数中,随机取出m个数,要求不重复,每个数被选中的概率相等。注意这里的n是固定的。如果是不固定的,其实就是蓄水池抽样问题。
算法一:
如果每次从r个剩余的数中选s个,那么我们就以s/r的概率选择下一个数。比如m=3, n=10. 以3/10的概率选择1,如果1被选,则
m=2, n=9 , 那么以2/9的概率选择2,否则m=3, n=9, 就以3/9=1/3的概率选择2。依次类推。
void randomSelect(int m,int n,std::vector<int>& v){
if(n<=m){
for(int i=1;i<=n;i++){
v.push_back(i);
}
return;
}
srand(time(0));
int select=m;
int remain=n;
for(int i=1;i<=n;i++){
if(rand()%remain<select){
v.push_back(i);
select--;
}
remain--;
}
return;
}
时间复杂度是O(n),空间复杂度是O(1)
算法二:
用一个set装被选中的元素。每次随机生成0~n-1范围的数字,插入集合。如果集合当前元素个数小于m,继续插入,当等于m时,即选中了m个元素。返回
void randomSelect(int m,int n,set<int>& s){
srand(time(0));
while(s.size()<m){
s.insert(rand()%n);
}
}
时间复杂度:当n很大,而m比较小时,可以近似认为每次采样几乎不重复,这样运行时间就正比与m.故时间复杂度就是O(m)。比上一个算法快些。但是如果m很大时,会出现大量的重复采样,运行时间就大大延长。有个好的办法是当m和n接近时,我们用set的元素表示不选中的元素,则其余就是选中的元素。
算法三:
用大小n的数组保存0~n-1的数字,然后用shuffle函数随机打乱,然后取前m个元素作为选中元素。
void shuffle(int a[],int n){
srand(time(0));
for(int i=0;i<n;i++){
int r=i+rand()%(n-i);
if(i!=r){
int t=a[r];
a[r]=a[i];
a[i]=t;
}
}
}
时间复杂度O(n),空间复杂度O(n)