一个小于n的随机数, 即所有的在[0, n) 之间的数出现的概率是相同的。
平时,我写一个小于n的随机数总是:r = rand() % n; 这样产生的数,是个真正的随机数吗?
关键一点是c程序中能够产生的伪随机数的个数是有限个,因为系统中会这样一个常量:RAND_MAX ,也是c库能够产生伪随机数的最大数目。
如果我们用一个连续生成的随机数列,其值都%2, 会发现其值是0, 1相间的。
当n非常大时,所有伪随机数出现 在[0, n) 和 [n, RAND_MAX]连个区间中的分布式不均衡,将导致产生的随机数并不是真正的随机的。
在《Accelerated C++》一书中给出一个完美的解决方案:使用桶机制,将所有的随机数分成n 个桶, 每个桶的bucket_size = RAND_MAX / n;
然后 r = rand() / bucket_size; 看生成伪随机数落在了那个桶里,该桶的编号,就是我们要找的小于n的随机数。
扩展问题: 怎样获取小于n的k个不同的随机数?
程序代码实现如下:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <assert.h>
//非真正随机的随机数生成器[0, n)
int getRandomNum(int n);
//使用分成n个桶,实现真正的[0, n)中随机数
int getRealRandomNum(int n);
//获取k个不同的小于n的随机数
void getKRandomNums(int n, int a[], int k);
//判断数组a中是否存在整数k
int exists(int a[], int len, int k);
int main()
{
int i;
srand((unsigned)time(NULL));
for(i = 1; i < 1000; ++i) {
printf("%d : %d \n", getRandomNum(i), getRealRandomNum(i) );
}
printf("Hello world!\n");
return 0;
}
//非真正随机的随机数生成器[0, n)
int getRandomNum(int n) {
//init the seed of rand()
//srand((unsigned)time(NULL)); // this should be before getRandomNum call.
return rand() % n;
}
//使用分成n个桶,实现真正的[0, n)中随机数
int getRealRandomNum(int n) {
assert(n > 0 && n <= RAND_MAX);
int r;
const int bucket_size = RAND_MAX / n;
do {
r = rand() / bucket_size;
}while(r >= n);
return r;
}
//获取k个不同的随机数
void getKRandomNums(int n, int a[], int k) {
assert(n > 0 && n <= RAND_MAX && k<=n);
int count;
int r;
a[0] = getRealRandomNum(n);
for(count = 1; count < k; ) {
r = getRealRandomNum(n);
if(exists(a, count, r)) continue;
a[count++] = r;
}
}
int exists(int a[], int len, int k) {
int i;
for(i = 0; i < len; ++i) {
if(a[i] == k) return 1;
}
return 0;
}