C++11 随机数的产生

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

随机数的产生

随机数有着广泛的用途。比如测试、游戏、仿真以及安全等领域都需要用到随机数。标准库所提供的多种可供选择的随机数产生器也恰恰反应了随机数应用范 围的多样性。随机数产生器由引擎(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++一般意义上的随机数生成

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容