您的位置:首页 > 理论基础 > 数据结构算法

概率取样问题

2010-06-07 18:53 302 查看
  问题:程序输入两个整数m和n,其中m<n。输出是0~n-1范围内的m个随机整数的有序列表,不允许重复,要求每个选择出现的概率相等。

  (1)扫描算法:扫描0~n-1,通过一个概率测试来对每个整数进行选择,可保证输出结果是有序的。考虑m=2,m=5的情况。选择的0的概率为2/5,通过语句if(bigrand()%5)<2来实现。若选择了0,则在剩下的4个数中以1/4的概率选择一个数。若未选择0,则以2/4的概率选择下一个数,这样就能保证恰好能选出m个整数。这里bigrand()返回一个很大的随机整数,远大于m和n。一般来说,如果要从r个剩余整数中选择s个,我们以概率s/r选择下一个整数。

#include <stdio.h>
#include <stdlib.h>
/* 返回一个很大的随机整数 */
int bigrand(){
return RAND_MAX*rand()+rand();
}
/* 返回[t,u]范围内的一个随机整数 */
int randint(int t,int u){
return t+bigrand()%(u-t+1);
}
void genrand(int m,int n){
int i=0;
for(i=0;i<n;++i)
if((bigrand()%(n-i))<m){
printf("%d/n",i);
m--;
}
}
int main(void){
genrand(5,15);
return 0;
}


  显然,算法的运行时间为O(n)。

  (2)基于集合的算法:在一个初始为空的集合里面插入随机整数,直到个数足够,核心问题是如何实现集合S。我们可以考虑有序链表、二叉树等数据结构,但最直接的办法是利用C++标准模板库中的set容器。

#include <iostream>
#include <cstdlib>
#include <set>
using namespace std;
int bigrand(){
return RAND_MAX*rand()+rand();
}
void gensets(int m,int n){
set<int> S;
while(S.size()<m)
S.insert(bigrand()%n);
set<int>::iterator i;
for(i=S.begin();i!=S.end();++i)
cout<<*i<<"/n";
}
int main(void){
gensets(5,15);
return 0;
}


  C++ STL规范要求每次插入操作都在O(logm)时间内完成,而遍历集合则需要O(m)时间。因此整个程序需要O(mlogm)时间(当m相对于n较小时)。但是该数据结构的空间开销比较大。

  当n比较大而m接近于n时,基于集合的算法需要生成大量的随机数,而其中很多随机数都要丢掉(因为之前集合中已经存在这个数了),这导致很多不必要的运行时间开销。Bob Floyd给出了一个算法,使得即使在最坏情况下也只使用m个随机数。如下(C++实现):

void genfloyd(int m,int n){
set<int> S;
set<int>::iterator iter;
for(int j=n-m;j<n;j++){
int t=bigrand()%(j+1);
if(S.find(t)==S.end()) //search t
S.insert(t);  //t not in S
else
S.insert(j);  //t in S
}
for(iter=S.begin();iter!=S.end();++iter)
cout<<*iter<<"/n";
}


  (3)排序算法:生成随机整数的有序子集的另一种方法是把包含整数0~n-1的数组顺序打乱,然后把前m个元素排序输出。

#include <iostream>
#include <cstdlib>
#include <set>
#include <algorithm>
/* 返回一个很大的随机整数 */
int bigrand(){
return RAND_MAX*rand()+rand();
}
/* 返回[t,u]范围内的一个随机整数 */
int randint(int t,int u){
return t+bigrand()%(u-t+1);
}
void genshuf(int m,int n){
int i,j,t;
int *x=new int
;
for(i=0;i<n;++i)
x[i]=i;
for(i=0;i<m;++i){ /* 把数组的元素顺序打乱,只需要前m个元素即可 */
j=randint(i,n-1);
t=x[i]; x[i]=x[j]; x[j]=t;
}
std::sort(x,x+m); /* 对前m个元素排序 */
for(i=0;i<m;++i)
std::cout<<x[i]<<std::endl;
}
int main(void){
genshuf(5,15);
return 0;
}


  算法需要n个元素的内存空间和O(n+mlogm)的时间。

  关键算法设计思想:扫描策略、概率测试(随机数生成)、集合数据结构、搜索策略、排序策略。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息