我们都知道,C语言函数库的随机函数应用范围十分有限,RAND_MAX也就2^15那么大,如果单纯的扩展范围,那么精度自然就是问题了,不仅如此,在RAND_MAX不够大的情况下,产生小于指定数的精度也是非常受限的。所以本文:
1:会实现一个最大能到2^30,精确的随机函数。
2:会用不同思路实现不同时间复杂度的等概率不重复随机数列算法。
好了,进入正题:
如何实现一个更大精度的随机函数呢?首先我们知道计算机里的数,其实都是用二进制位表示的,也就是说,我们只需要生成随机二进制位就OK了。
用RAND_MAX * rand() + rand()的方式就可以啦,RAND_MAX * rand()用来构造高位,后面的+rand()用来构造低位,代码如下:
int bigRand(){
return RAND_MAX * rand() + rand();
}
基于bigRand()函数,我们可以实现更精确的产生某个范围的随机函数randInt(),代码如下:
int randInt(int left, int right){
return left + bigRand() % (right - left + 1);
}
left和right指定要生成数的区间范围,包括端点。
接下来,我们实现几个按顺序等概率生成没有重复数序列的代码,如何做到等概率呢?
比如说要以2/5也就是以40%的概率生成一个数:
其实很简答:bigRand() % 5 < 2,因为bigRand()生成数的范围非常大,所以完全可以认为,取模后的结果均匀分布在0-4,小于2就是0和1。这个结论《计算机程序设计艺术》的作者knuth给了概率上的证明。
这个代码,等概率的产生一个有序的随机数列,m为随机数数目,n为最大数:
int genKnuth(int m, int n){
for (int i = 0; i < n; ++i){
if ((bigRand() % (n - i)) < m){
randNums.push_back(i);
m -= 1;
}
}
return 0;
}
可以看到,这个算法复杂度和n成正比,当n非常大的时候效率挺一般的。
所以,有一个更好的算法:每次生成一个随机数,如果这个随机数没有出现过,那么就加入到序列中,如果重复,就继续随机过程,判重的过程我们用c++的set数据结构完成,这样时间复杂度可以降为mlogm级别,通常m远小于n,所以性能更好:
int getSets(int m, int n){
set<int> s;
while (s.size() < m){
s.insert(bigRand() % n);
}
set<int>::iterator it;
for (it = s.begin(); it != s.end(); ++it){
randNums.push_back(*it);
}
return 0;
}
还有人提出了一个很好玩的算法,博主一并实现放在了一起,getShuf()函数,有兴趣的读者可以看看。
全部代码如下:
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <vector>
#include <set>
#include <algorithm>
#include <ctime>
using namespace std;
vector<int> randNums;
int bigRand();
int randInt(int left, int right);
int genKnuth(int m, int n);
int getSets(int m, int n);
int getShuf(int m, int n);
int main(){
srand(time(NULL));
for (int i = 0; i < 5; ++i){
cout << bigRand() << ' ';
}cout << endl;
for (int i = 0; i < 20; ++i){
cout << randInt(100, 200) << ' ';
}cout << endl;
genKnuth(20, 200);
for(int i = 0; i < randNums.size(); ++i){
cout << randNums[i] << ' ';
}cout << endl;
randNums.clear();
getSets(20, 200);
for(int i = 0; i < randNums.size(); ++i){
cout << randNums[i] << ' ';
}cout << endl;
randNums.clear();
getShuf(20, 200);
for(int i = 0; i < randNums.size(); ++i){
cout << randNums[i] << ' ';
}cout << endl;
randNums.clear();
return 0;
}
int bigRand(){
return RAND_MAX * rand() + rand();
}
int randInt(int left, int right){
return left + bigRand() % (right - left + 1);
}
int genKnuth(int m, int n){
for (int i = 0; i < n; ++i){
if ((bigRand() % (n - i)) < m){
randNums.push_back(i);
m -= 1;
}
}
return 0;
}
int getSets(int m, int n){
set<int> s;
while (s.size() < m){
s.insert(bigRand() % n);
}
set<int>::iterator it;
for (it = s.begin(); it != s.end(); ++it){
randNums.push_back(*it);
}
return 0;
}
int getShuf(int m, int n){
int *x = new int[n];
for (int i = 0; i < n; ++i){
x[i] = i;
}
for (int i = 0; i < m; ++i){
int j = randInt(i, n - 1);
swap(x[i], x[j]);
}
sort(x, x + m);
for (int i = 0; i < m; ++i){
randNums.push_back(x[i]);
}
return 0;
}