从0~n中随机选择m个数。这个也是我上次面试微信的时候,遇到的一个问题,当时比较没什么头绪,想出了一个m*n 的算法,太水了。
其实我们平时会遇到很多这种类似的问题。比如从10亿个QQ账号中选择6亿个账号,用来作为中奖用户。从班里40个人中,选择3个人来打扫卫生等等。生活中会遇到很多这种随机选择的例子。
在编程珠玑的第12章中,就讲了从n个钟随机选择m个数的解法。主要有三种,下面来一一介绍:
直接看算法就好,这个是在计算机程序设计艺术第二卷中有讲到的
int j = m ;
for(int i = 0 ; i != n ; i++){
if(bigRand()%(n-i) < j ){ //bigRand() 是产生大随机数的函数
j--;
printf("%d",j);
}
}
利用从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/