您的位置:首页 > 其它

关于基本的随机选择算法(n选m)的分析与思考

2014-02-26 00:28 239 查看


问题

从0~n中随机选择m个数。这个也是我上次面试微信的时候,遇到的一个问题,当时比较没什么头绪,想出了一个m*n 的算法,太水了。


引申的类似问题

其实我们平时会遇到很多这种类似的问题。比如从10亿个QQ账号中选择6亿个账号,用来作为中奖用户。从班里40个人中,选择3个人来打扫卫生等等。生活中会遇到很多这种随机选择的例子。


解决方法

在编程珠玑的第12章中,就讲了从n个钟随机选择m个数的解法。主要有三种,下面来一一介绍:


解法1:

直接看算法就好,这个是在计算机程序设计艺术第二卷中有讲到的
int j = m ;

for(int i = 0 ; i != n ; i++){

if(bigRand()%(n-i) < j ){			//bigRand() 是产生大随机数的函数

j--;

printf("%d",j);

}

}



解法2:

利用从n个中选择m个。用set 保存每次选择的随机数,然后下次选择的时候,先判断set当中是否存在了该数值,如果存在则抛弃。当m较小时,可以达到m*logm。(c++ 模板库当中set 的插入可以达到O(logn) )
for(int i= 0 ; i !=m ; ){

int s = rand()%n;

if(set.find(s)!=set.end()){

set.insert(s);

}

}

//输出set

for(set<int>::iterator iter = set.begin(); iter != set.end() ; ++ iter){

cout<<*iter<<" ";

} ####解法3:    将n个数打乱。这样,前m个就是选择的数据了,简单的代码如下。这样其实多花了O(n) 的空间来保存一个数组


int arr[n];

for(int i = 0 ; i != n ; ++ i ){

arr[i] = i ;

}

for(int i = 0 ; i != m ; ++ i ){

int j = generateRand(i,n);  // generateRand(int i,int j) 产生i与j 直接的随机数

swap(i,j);  //交换i 和 j

}



解法应用场景

三种解法各有千秋,解法1的时间复杂度为O(n)。但是有时候,你只是为了选择m个数,但是却花了那么多时间,感觉有点浪费时间了。所以个人觉得当取的m比较接近于n的时候,比较适合用解法1.

但是如果是m 相对于n 来说,小的多的时候,就很适合方法二了。因为当m < n/2 的时候,每次选中抛弃的平均的重复次数,不会超过2次。

方法3的话,如果已经保存了一个数组,那么速度就相当的快,也不会担心多花了O(n)的时间了。


相关的扩展思考:

1.用递归的方式实现解法1,递归如何保证升序降序?
genarateRandom(int n,int m){

if(m <= 0 ) return ;

if(bigrand() % n < m ){

//选择了一个,则下面只需要再选m-1 个

printf("%d",n-1);

genarateRandom(n-1,m-1);

}else{

genarateRandom(n-1,m);

}

}


2.使用方法2,除重复的选择。(Floyd 已经证明过一种方法了)?
Set<int> s ;

for(int j = n - m ; j != n ; ++ j){

int t = bigrand() % (j +1 );

if(s.find(t) == s.end()){

//不存在

s.insert(t);

}else{

s.insert(t+1);

}

}


3.假如不知道n 有多大呢?

请看: http://boyhouzhi.com/2013/11/25/single-traversal-random-selection/

4.如何使某一个子集被选中的概率更高?

5.很显然,会有一个疑问 : bigrand()如何产生呢?

rand() 产生655335 两个rand() 就可以产生 65535*65535 个随机数了。

个人博客地址:http://boyhouzhi.com/2013/12/19/some-random-algorithms/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法