美文网首页
C++11 随机数的产生

C++11 随机数的产生

作者: 深红的眼眸 | 来源:发表于2017-04-14 17:44 被阅读0次

    本文根据众多互联网博客内容整理后形成,引用内容的版权归原始作者所有,仅限于学习研究使用,不得用于任何商业用途。

    随机数的产生

    随机数有着广泛的用途。比如测试、游戏、仿真以及安全等领域都需要用到随机数。标准库所提供的多种可供选择的随机数产生器也恰恰反应了随机数应用范 围的多样性。随机数产生器由引擎(engine)和分布(distribution)两部分组成。其中,engine用于产生一个随机数序列或者伪随机数 序列;distribution则将这些数值映射到位于固定范围的某一数学分布中。关于分布的例子有:unifrom_int (所有的整数倍都被以相等的概率产生)以及normal_distribution (分布的概率密度函数曲线呈钟形);每一种分布都处于某一特定的范围之内。例如:

    //distribution将产生的随机数映射到整数1..6
    uniform_int_distribution<int> one_to_six {1,6}; 
    
    default_random_engine re {};        //默认的engine
    

    如果想获得一个随机数,你可以用一个随机引擎为参数调用distribution来产生一个随机数:

    int x = one_to_six(re); // x 是 [1:6]这个范围内的一个随机数
    

    在每次调用的时候都需要提供一个引擎作为参数非常繁琐,所以我们可将引擎和 distribution邦定成一个函数对象,然后直接通过这个函数对象的调用来产生随机数,而不用每次调用都提供参数了。

    auto dice {bind(one_to_six,re)}; //  产生一个新的随机数生成器
    int x = dice(); // 调用dice函数对象,x是一个分布在 [1:6]范围的随机数
    

    多亏在设计它是对一般性和性能的关注。在这方面,一位专家曾在评价标准库中随机数模块时说道:“在扩充的过程中,每一个随机数库想变成什么”。然而,它很 难真正让一个新手感觉到容易上手。在性能方面,我从没有见过随机数的接口成为性能的瓶颈。另外,我也一直会使用一个简单的随机数生器来教新手(具有一定的 基础)。下面的就是这样的一个可以说明问题的例子。

    int rand_int(int low, high);   //按照均匀分布在区间[low: high]中产生一个随机数
    

    然而我们如何实现rand_int()?我们必须在rand_int()中使用dice()之类的函数:

    int rand_int(int low, int high)
    {   
         static default_random_engine re {};
         using Dist = uniform_int_distribution<int>;
         static Dist uid {};
         return uid(re, Dist::param_type{low,high});
    }
    

    关于rand_int()的定义依然是属于“专家级”的,但是应该把关于它的使用安排在C++课程的第一周。
    在这里,我们举一个不太琐碎的关于随机数生成器的例子。这个例子中代码的功能是生成和打印一个正态分布。

    default_random_engine re;   //默认引擎
    
    normal_distribution<double> nd(31 /* mean */,
          8 /* sigma */);
    
    auto norm = std::bind(nd, re);
    
    vector<int> mn(64);
    
    int main()
    {
        for (int i = 0; i<1200; ++i) 
                   ++mn[round(norm())]; // 产生随机数
    
        for (int i = 0; i<mn.size(); ++i) 
               {
            cout << i << '\t';
            for (int j=0; j<mn[i]; ++j) 
                        cout << '*';
    
                    cout << '\n';
        }
    }
    

    我运行了一个支持boost::random的版本并把它编辑到C++0x中,然后得到了下面的结果。

    0
    1
    2
    3
    4 *
    5
    6
    7
    8
    9 *
    10 ***
    11 ***
    12 ***
    13 *****
    14 *******
    15 ****
    16 **********
    17 ***********
    18 ****************
    19 *******************
    20 *******************
    21 **************************
    22 **********************************
    23 **********************************************
    24 ********************************************
    25 *****************************************
    26 *********************************************
    27 *********************************************************
    28 ***************************************************
    29 ******************************************************************
    30 **********************************************
    31 *********************************************************************
    32 **********************************************
    33 *************************************************************
    34 **************************************************************
    35 ***************************************
    36 ***********************************************
    37 **********************************************
    38 *********************************************
    39 ********************************
    40 ********************************************
    41 ***********************
    42 **************************
    43 ******************************
    44 *****************
    45 *************
    46 *********
    47 ********
    48 *****
    49 *****
    50 ****
    51 ***
    52 ***
    53 **
    54 *
    55 *
    56
    57 *
    58
    59
    60
    61
    62
    63
    

    1.随机数由生成器和分布器结合产生

    生成器generator:能够产生离散的等可能分布数值
    分布器distributions: 能够把generator产生的均匀分布值映射到其他常见分布,如均匀分布uniform,正态分布normal,二项分布binomial,泊松分布poisson

    2.分布器利用运算符()产生随机数,要传入一个generator对象作为参数

    std::default_random_engine generator;  
    std::uniform_int_distribution<int> dis(0,100);  
    for(int i=0;i<5;i++)  
    {  
        std::cout<<dis(generator)<<std::endl;  
    }  
    

    如果嫌每次调用都要传入generator对象麻烦,可以使用std::bind,要包含头文件functional
    auto dice = std::bind(distribution,generator)以后就可以直接调用dice()产生复合均匀分布的随机数。但是多次运行上例会发现每次产生的随机数序列都一样,因为没有设定种子(同cstdlib库中的rand和srand关系)

    std::default_random_engine generator;  
    std::uniform_int_distribution<int> dis(0,100);  
    auto dice= std::bind(dis,generator);  
    for(int i=0;i<5;i++)  
    {  
        std::cout<<dice()<<std::endl;  
    }  
    

    3.种子

    除了random_device生成器(真随机数生成器或叫f非确定性随机数生成器)以外(linux中有效,windows下其实也是伪随机),所有在库中定义的随机数引擎都是伪随机数生成器,他们都利用了特定的算法实现,这些生成器都需要一个种子。种子可以是一个数值,或者是一个带有generate成员函数的对象。简单的应用中,用time作种子即可。
    说明:如果不设定种子,那么产生的随机数序列每次都一样,如上代码,产生5个1到6之间的随机数,但是每次都是82 13 91 84 12
    改为如下代码,可以使每次产生的随机数序列不同:

    std::default_random_engine generator(time(NULL));  
    std::uniform_int_distribution<int> dis(0,100);  
    auto dice= std::bind(dis,generator);  
    for(int i=0;i<5;i++)  
    {  
        std::cout<<dice()<<std::endl;  
    }  
    

    4.关于生成器

    C++11标准提供了三个生成器模版类可以实例化为生成器,但需要有一定的数学功底才懂得每个模版参数的意义,可参照算法出处的论文。这三个生成器类模版为:

    • linear_congruential_engine 线性同余法
    • mersenne_twister_engine 梅森旋转法
    • substract_with_carry_engine滞后Fibonacci

    线性同余法举例

    
    template <class UIntType, UIntType a, UIntType c, UIntType m>
    
    class linear_congruential_engine;
    
    第一个参数:生成器类型unsigned int,unsigned long等
    
    第二到第四个参数:是线性同余法公递推公式Nj+i =(AxNj+C) (mod M)里的三个常数值A,C,M
    
    要求:如果m不为0,a,c的值要小于m
    

    如一会介绍的常用生成器:

    typedef linear_congruential<unsigned long, 16807, 0, 2147483647> minstd_rand0;  
    typedef linear_congruential<unsigned long, 48271, 0, 2147483647> minstd_rand;  
    

    可见如果自己实例化模版类很麻烦,需要很强的数序知识,所以有几个常用的几个模版实例化生成器,他们都是需要一个种子参数就可以:

    4.1线性同余法:

    minstd_rand()
    minstd_rand0
    利用适配器变种后的线性同余法
    knuth_b minstd_rand0 with shuffle_order_engine

    4.2梅森旋转法:

    default_random_engine()
    mt19937
    mt19937_64

    4.3滞后Fibonacci法

    ranlux24_base
    ranlux48_base

    利用适配器变种后的滞后Fibonacci法:

    ranlux24 ranlux24_base with discard_block_engine
    ranlux48 ranlux48_base with discard_block_engine

    三个适配器:discard_block_engine shuffle_order_engine independent_bits_engine

    5.关于分布器

    易知,如果只用generator配上seed只能产生离散的等可能分布,产生的数值在generator的min和max之间,并且结果都是UIntType的值。无法很好的控制产生数值的分布区间和分布概率。如果要实现这种功能就要用到分布器。

    • 作用1:改变生成类型,利用模版参数
    • 作用2:改变值区间,利用实例构造函数参数。或其响应的成员函数设置参数。
    • 作用3:改变概率分布,选用不同的分布器类型

    5.1均匀分布:

    uniform_int_distribution 整数均匀分布
    uniform_real_distribution 浮点数均匀分布

    5.2伯努利类型分布:(仅有yes/no两种结果,概率一个p,一个1-p)

    bernoulli_distribution 伯努利分布
    binomial_distribution 二项分布
    geometry_distribution 几何分布
    negative_biomial_distribution 负二项分布

    5.3 Rate-based distributions:

    poisson_distribution 泊松分布
    exponential_distribution 指数分布
    gamma_distribution 伽马分布
    weibull_distribution 威布尔分布
    extreme_value_distribution 极值分布

    5.4正态分布相关:

    normal_distribution 正态分布
    chi_squared_distribution 卡方分布

    cauchy_distribution 柯西分布
    fisher_f_distribution 费歇尔F分布
    student_t_distribution t分布

    5.5分段分布相关:

    discrete_distribution 离散分布
    piecewise_constant_distribution 分段常数分布

    piecewise_linear_distribution 分段线性分布

    参考资料

    【C++11】随机数函数库random
    [C++11]C++11带来的随机数生成器
    【c++11FAQ】随机数的产生
    c++一般意义上的随机数生成

    相关文章

      网友评论

          本文标题:C++11 随机数的产生

          本文链接:https://www.haomeiwen.com/subject/vjqgattx.html