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

问题

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

引申的类似问题

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

解决方法

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

解法1:

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

 
 
  1. int j = m ;
  2. for(int i = 0 ; i != n ; i++){
  3. if(bigRand()%(n-i) < j ){ //bigRand() 是产生大随机数的函数
  4. j--;
  5. printf("%d",j);
  6. }
  7. }
解法2:

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

 
 
  1. for(int i= 0 ; i !=m ; ){
  2. int s = rand()%n;
  3. if(set.find(s)!=set.end()){
  4. set.insert(s);
  5. }
  6. }
  7. //输出set
  8. for(set<int>::iterator iter = set.begin(); iter != set.end() ; ++ iter){
  9. cout<<*iter<<" ";
  10. } ####解法3: 将n个数打乱。这样,前m个就是选择的数据了,简单的代码如下。这样其实多花了O(n) 的空间来保存一个数组
  11. int arr[n];
  12. for(int i = 0 ; i != n ; ++ i ){
  13. arr[i] = i ;
  14. }
  15. for(int i = 0 ; i != m ; ++ i ){
  16. int j = generateRand(i,n); // generateRand(int i,int j) 产生i与j 直接的随机数
  17. swap(i,j); //交换i 和 j
  18. }

解法应用场景

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

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

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

相关的扩展思考:

1.用递归的方式实现解法1,递归如何保证升序降序?

 
 
  1. genarateRandom(int n,int m){
  2. if(m <= 0 ) return ;
  3. if(bigrand() % n < m ){
  4. //选择了一个,则下面只需要再选m-1 个
  5. printf("%d",n-1);
  6. genarateRandom(n-1,m-1);
  7. }else{
  8. genarateRandom(n-1,m);
  9. }
  10. }

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

 
 
  1. Set<int> s ;
  2. for(int j = n - m ; j != n ; ++ j){
  3. int t = bigrand() % (j +1 );
  4. if(s.find(t) == s.end()){
  5. //不存在
  6. s.insert(t);
  7. }else{
  8. s.insert(t+1);
  9. }
  10. }

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/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值