C++ 中使用伪随机数

C++ 中使用伪随机数

使用过 C语言标准库中随机数相关功能的同学肯定还记得 C 库中提供的两个函数:

  • rand()
  • srand()

为了使用这两个函数,需要包含头文件 stdlib.h。这么多年来C语言标准库中随机数生成的相关的函数就只有这两个。并且 rand() 函数只能生成 [0, RAND_MAX] 之间的均匀分布的随机整数,用起来非常不方便。许多 C++ 的程序员在使用到随机数功能时,也只会用 C 标准库中这两个函数。实际上从 C++11 标准起,C++ 标准库中就增加了不少新的随机数生成器。本文就来介绍这些新加进来的随机数函数。

C++ 中新增的随机数功能的声明都放到了头文件 random 中。因此,在使用这些新功能时,需要包含这个头文件。

#include<random>

为了输出随机数,我们需要两个类:
- 随机数引擎:用来产生均匀分布随机数
- 随机数分布:用均匀分布随机数构造我们需要的特点分布的随机数

随机数引擎

在 C++ 11中引入了三类随机数引擎,分别是:

  • linear_congruential_engine
  • mersenne_twister_engine
  • subtract_with_carry_engine

linear_congruential_engine 使用的就是最常见的线性同余法,运算速度较快,是使用最广泛的随机数引擎。因此我们多介绍些。熟悉了这个引擎的使用,其他引擎也就自然的会用了。

线性同余法的公式如下:

xi+1=(axi+c)modm

类声明如下:

template<
    class UIntType, 
    UIntType a, 
    UIntType c, 
    UIntType m
> class linear_congruential_engine;

a,c,m 这三个参数的选择非常的重要。如果选的不好,生成的随机数统计特性就会很差。好在 C++ 11 标准给我们推荐了几组比较好的值。

typedef linear_congruential_engine<unsigned int, 16807, 0, 2147483647>
    minstd_rand0;
typedef linear_congruential_engine<unsigned int, 48271, 0, 2147483647>
    minstd_rand;

因此,大多数时候,我们只要直接使用 minstd_rand0 或 minstd_rand 就可以了。
这两个预定义的引擎生产的随机数的范围都是 [1, 2147483646]。下面的代码先是输出这个引擎可以输出的最大、最小值,然后用这个引擎输出 10 个随机数。这里随机数引擎的种子设为了 123。 真实代码中,我们通常会选用 random_device 作为种子,后面会专门讲讲 random_device。

    minstd_rand engine(123);
    cout << engine.min() << endl;
    cout << engine.max() << endl;
    for(int i = 0; i < 10; i++)
        cout << engine() << endl;

上面的代码只是示意性的,我们通常不会这么用的。

mersenne_twister_engine 是马特赛特旋转演算法。此算法是 Makoto Matsumoto (松本)和 Takuji Nishimura (西村)于1997年开发的,基于有限二进制字段上的矩阵线性再生。可以快速产生高质量的伪随机数,修正了古老随机数产生算法的很多缺陷。但是计算速度较慢。这个算法的参数很多,C++11 标准中替我们选好了两组参数,多数情况下我们直接用就好了。

    typedef std::mersenne_twister_engine<std::uint_fast32_t, 32, 624, 397, 31, 
                             0x9908b0df, 11, 
                             0xffffffff, 7, 
                             0x9d2c5680, 15, 
                             0xefc60000, 18, 1812433253> mt19937;

    typedef std::mersenne_twister_engine<std::uint_fast64_t, 64, 312, 156, 31,
                             0xb5026f5aa96619e9, 29,
                             0x5555555555555555, 17,
                             0x71d67fffeda60000, 37,
                             0xfff7eee000000000, 43, 6364136223846793005> mt19937_64;

subtract_with_carry_engine 使用的是 lagged Fibonacci 法,这种方法运算速度最快,但是生成的随机数的统计特性比另外两种要差一些。通常只是在我们需要快速生成大量随机数,而对随机数的统计特性要求较低是才使用。下面是两组预先定义好的参数。

    typedef std::subtract_with_carry_engine<std::uint_fast32_t, 24, 10, 24> ranlux24_base;
    typedef std::subtract_with_carry_engine<std::uint_fast64_t, 48, 5, 12> ranlux48_base;

random_device

这里把 random_device 单独拿出来讲讲。random_device 其实也是个随机数引擎,只不过这个引擎与其他的引擎有些区别。其他的引擎实际上都是伪随机数引擎,这个 random_device 可以是真随机数,当然前提是所在的系统支持生产真随机数。(对于新一点 Intel CPU,有个汇编指令叫做 RDRAND,这个指令通过读取CPU内部一个特殊的电阻热噪声取得硬件真随机数。) random_device 也并不是强制必须生成真随机数,如果硬件不支持,或者有其他什么考虑,也可以生成伪随机数。但是即使生产的是伪随机数,也要比其他的引擎生成的伪随机数有更好的统计特性,也就是说更随机。但是这也是有代价的,通常 random_device 生成随机数的速度会较慢。因此,常用的做法是用 random_device 生成一个随机数作为其他引擎的随机数种子。代码类似下面这样:

    std::random_device rd;  //Will be used to obtain a seed for the random number engine
    std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()

随机数分布器

随机数分布器的作用是将均匀分布随机数转化为我们需要的特定分布的随机数。下面先以均匀分布随机数分布器为例。均匀分布随机数分布器有两种,一个输出整数、一个输出浮点数:

    template< class IntType = int > class uniform_int_distribution;
    template< class RealType = double > class uniform_real_distribution;

均匀分布随机数分布器有两个参数 a 和 b,用来确定输出的随机数的范围,这个区间是前闭后开的 [a,b) ,这点需要特别注意。下面是个例子:

void test19937()
{
    std::mt19937 gen(1); //Standard mersenne_twister_engine seeded with 1
    std::uniform_real_distribution<> dis(1, 2);
    for (int n = 0; n < 10; ++n)
    {
        //Use dis to transform the random unsigned int generated by gen into a double in [1, 2)
        std::cout << dis(gen) << ' '; //Each call to dis(gen) generates a new random double
    }
}

除了均匀分布之外, C++11 标准中还提供了多种其他分布的随机数。一一介绍的话篇幅就太长了,我列了个表格,大家可以参考。

名称说明
uniform_int_distribution均匀分布
uniform_real_distribution均匀分布
bernoulli_distributionbernoulli分布
binomial_distribution二项分布
negative_binomial_distributionhttps://en.wikipedia.org/wiki/Negative_binomial_distribution
geometric_distributiongeometric distribution
poisson_distributionpoisson distribution
exponential_distributionexponential distribution
gamma_distributiongamma distribution
weibull_distributionweibull distribution
extreme_value_distributionhttps://en.wikipedia.org/wiki/Generalized_extreme_value_distribution
normal_distributionnormal distribution
lognormal_distributionlognormal distribution
chi_squared_distributionchi squared distribution
cauchy_distributioncauchy distribution
fisher_f_distributionFisher’s F-distribution
student_t_distributionStudent’s t-distribution
piecewise_constant_distributionreal values distributed on constant subintervals
piecewise_linear_distributionreal values distributed on defined subintervals

随机数引擎适配器

所谓随机数引擎适配器也是个随机数引擎。但是这个引擎必须以另一个随机数引擎作为数据源才能工作。 C++11 标准中给出了三种随机数引擎适配器:
- discard_block_engine
- independent_bits_engine
- shuffle_order_engine

这三种适配器的具体原理没必要仔细研究。我们只需要知道用这三种适配器再配合其他的引擎,可以构造一些经典的随机数算法,下面这三个是标准里给我们预备好的。

typedef discard_block_engine<ranlux24_base, 223, 23> ranlux24;
typedef discard_block_engine<ranlux48_base, 389, 11> ranlux48;
typedef shuffle_order_engine<minstd_rand0, 256> knuth_b;

辅助功能

除了上面介绍的这些,还有两个辅助的类。

  • generate_canonical
  • seed_seq

这两个类通常用不到。这里也就不介绍了。需要用到时可以查看下面的文档:

en.cppreference.com/w/cpp/numeric/random

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值