巧用rand(),但是避免自身缺陷,生成随机整数

1 生成位于[0,n-1)个k不同的随机顺序的随机整数

1.1 思路:

  • 1 按序产生数组
  • 2 随即交换顺序

1.2 实现代码:

#include<cstdio>
#include<cstdlib>
#include<ctime>
using namespace std;

#define N 10000 //产生数的范围不超过N
#define K 9999

//生成[0,N-1)之间K个随机顺序的随机整数
int main(int argc, char const *argv[])
{
	int *randnum = new int[N];

//按序产生数组
	for (int i = 0; i < N; ++i)
	{
		randnum[i] = i;
	}

//随即交换顺序
	srand((unsigned)time(NULL));//随机数种子
	for (int i = 0; i < N; ++i)
	{
		int p = i + rand() % (N - i);//生成[0,n-1)范围的随机数
		int tmp = randnum[i]; //交换顺序
		randnum[i] = randnum[p];
		randnum[p] = tmp;

	}

	for (int i = 0; i < N; ++i)//打印输出
	{
		printf("%d ", randnum[i]);
	}
	return 0;
}

2 避免rand函数的缺陷生成随机数[0,n)范围内的随机整数

2.1 思路

初始版本:由于rand()返回的是伪随机数,可以用rand()%n表示[0,n)范围内的随机数。

缺陷:

  • 但是当商是小整数时,许多C++系统环境的伪随机数生成器所产生的余数并不是绝对随机的。例如当n等于2时,rand()%n连续结果就将在0和1之间选择。
  • 当n的值非常大是,那么RAND_MAX(rand()返回的最大值)不会被均匀地被n除尽,一些余数出现的频率将会比其他的大。
    • 例如:假设RAND_MAX是32767(对于任何系统环境,RAND_MAX最小的允许值)且n是20000。这样rand()将会有两个不同的值能令rand()%n等于10000(即10000和30000),但是rand()仅有一个值能让rand()%n等于15000(就是15000)。因此,简单实现产生10000将会是15000的两倍。

要避免这些缺陷,将这些可以利用的随机数分成长度精确相等的”存储桶“,我们能够计算一个随机数并返回相应的“存储桶”的编号。由于“存储桶”的长度相同,因此某些随机数根本没有可能进入任何”存储桶“中。因此,我们将反复求随机数,直到获得一个适合的为止。

原始版本是是取rand()的余数作为随机数,现在,我么用rand()/n得到商bucket_size,然后用rand()/bucket_size(商)反过来得到除数(n)作为随机数。

b u c k e t _ s i z e = R A N D _ M A X / n bucket\_size = RAND\_MAX/n bucket_size=RAND_MAX/n, 在具有性质 n ∗ b u c k e t _ s i z e ≤ R A N D _ M A X n*bucket\_size\leq RAND\_MAX nbucket_sizeRAND_MAX的全部整数中,bucket_size是最大的一个

“存储桶”0将将对应区间[0,bucket_size)中的值;“存储桶”1将将对应区间[bucket_size,bucket_size*1)中的值。。。。

现在重新看看这个问题,假设RAND_MAX是32767(对于任何系统环境,RAND_MAX最小的允许值)且n是20000。等于10000就会有一个(即10000),等于15000的值也只有一个(即15000)。

最后再来看两个具体的示例加深理解。假设假设RAND_MAX是7(方便讲解)且n是3。那么将区间均匀划分为了[0,2)、[2,4)、[4,6)三个区间,每个区间分别对应随机数0、1、2,于是每个随机数出现的频率都相同了。

假设假设RAND_MAX是7(方便讲解)且n是5。那么将区间均匀划分为了[0,1)、[1,2)、[2,3)、[3,4)、[4,5)五个区间(大于5的值将被舍弃),每个区间分别对应随机数0、1、2、3、4、5,于是每个随机数出现的频率都相同了。

这样实现了真正意义上的随机。

2.2 实现代码

int nrand(int n){
    if (n <= 0 || n > RAND_MAX)
        throw domain_error("Argument to nrand is out of range");

    const int bucket_size = RAND_MAX / n;
    int r;

    do r = rand() / bucket_size;
    while (r >= n);//一直循环到找到一个在n内的值

    return r;

}

3 继续优化rand()

一般情况下,RAND_MAX会等于可能存在的最大整数。但是某些系统换种中RAND_MAX会比可能存在的最大的整数小得多。例如RAND_MAX等于32767(215-1)而可能存在的最大整数等于2147483647(231-1)。

当函数nrand参数大于RAND_MAX时,函数将会失效。现在修改程序,无论参数是否大于RAND_MAX,nrand函数都有效。

int nrand_improved(int n, const int max = RAND_MAX) {

    if (n <= 0)
        throw domain_error("Argument to nrand is out of range");

    if (n <= max)
    {
        const int bucket_size = max / n;
        int r;

        do r = rand() / bucket_size;
        while (r >= n);

        return r;
    }
    else
    {
        const int buckets = n / max;
        const int remainder_rand = n % max == 0 ? 0 : nrand_improved(n % max);
        const int lost = n % max == 0 ? buckets : buckets + 1;

        return nrand_improved(max) * buckets + remainder_rand + nrand_improved(lost);
    }
}

检测示例:

#include <iostream>
using std::cin; using std::cout;
using std::endl;
using std::istream;
using std::ostream;
#include <string>
using std::string;

#include <iterator>
using std::inserter;

#include <exception>
using std::domain_error;

int nrand_improved(int n, const int max = RAND_MAX) {

    if (n <= 0)
        throw domain_error("Argument to nrand is out of range");

    if (n <= max)
    {
        const int bucket_size = max / n;
        int r;

        do r = rand() / bucket_size;
        while (r >= n);

        return r;
    }
    else
    {
        const int buckets = n / max;
        const int remainder_rand = n % max == 0 ? 0 : nrand_improved(n % max);
        const int lost = n % max == 0 ? buckets : buckets + 1;

        return nrand_improved(max) * buckets + remainder_rand + nrand_improved(lost);
    }
}

int main(int argc, char** argv)
{
    for (int i = 0; i < 100; i++){
        cout << nrand_improved(100, 3) << endl;
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁星蓝雨

如果觉得文章不错,可以请喝咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值