C++ random库

参考博客 C++11 random库

rand函数的问题

C库函数rand生成的是均匀分布的伪随机数,每个随机数的范围在0和一个系统相关的最大值(至少为32767)之间。
rand函数的问题是:很多程序需要不同范围的随机数,一些应用需要随机浮点数,以及非均匀分布的随机数。为了解决这些问题,通常会转换rand生成的随机树的范围、类型或者是分布,转换的操作常常会引入非随机性。

在c++11标准中,定义在头文件random中的随机数库通过一组协作的类来解决这些问题,主要用到的是两个类:
随机数引擎类(random-number engines)、随机数分布类(random-number distribution)。

其中,一个引擎类可以生成unsigned随机数列,一个分布使用一个引擎类生成指定类型的,在给定范围内的,服从指定概率分布的随机数。

随机数引擎和分布

随机数引擎是函数对象类,他们定义了一个运用运算符,该运算符不接受参数,并返回一个随机的unsigned整数。可以通过调用一个随机数引擎对象来生成原始随机数。

//定义一个名为e的default_random_engine的对象。然后调用对象e获得100个随机数
default_random_engine e;
    for(int i=0;i<100;i++){
        cout<<e()<<endl;
    }

分布类型和引擎

用分布类型的对象可以得到在指定范围内的数,

  //生成0到9之间(包括9)均匀分布的随机整数
    uniform_int_distribution<unsigned >u(0,9);
    //生成无符号随机整数
    default_random_engine e;
    //调用
    for(int i=0;i<10;i++){
        //将u作为随机数源
        //每个调用返回在指定范围内并服从均匀分布的值
        cout<<u(e)<<endl;
    }

上面的程序中,我们将 u 定义为 uniform_int_distribution 。这种类型生成均匀分布的 unsigned 值。当我们定义一个这种类型的对象时,可以提供想要的最小值和最大值。在上面这段代码中,u(0,9) 表示我们希望得到 0 到 9 之间(包含)的数。随机数分布类会使用包含的范围,从而我们可以得到给定整形的每个可能值。

类似引擎类型,分布类型也是函数对象类。分布类型定义了一个调用运算符,它接受一个随机数引擎作为参数。分布对象使用它的引擎参数生成随机数,并将其映射到指定的分布。

传递给分布对象的是引擎对象本身,也就是 u(e),如果我们将调用写为 u(e()),含义就变为将 e 生成的下一个值传递给 u,这会导致一个编译错误。我们传递的是引擎本身,而不是他生成的下一个值,原因是某些分布可能需要调用引擎多次才能得到一个值。

使用引擎生成一个数值序列

随机数发生器有一个特性,也就是即使生成的树看起来是随机的,但是对于一个给定的发生器,每次运行程序它都会返回相同的数值序列。序列不变这一事实在 调试 的时候十分有用,但是另一方面,使用随机数发生器的程序也必须考虑到这一特性。

下面介绍一个例子,需要一个函数生成一个 vector,包含 100 个均匀分布在 0 到 9 之间的随机数。一种错误的方法是使用下面的代码:

vector<unsigned >bad_randVec()
{
    default_random_engine e;
    uniform_int_distribution<unsigned >u(0,9);
    vector<unsigned >ret;
    for(size_t i = 0;i<100;i++)
        ret.push_back(u(e));
    return ret;
}
// 但是 每次调用这个函数都会返回相同的 vector
vector<unsigned >v1(bad_randVec());
vector<unsigned >v2(bad_randVec());
// 将会打印输出 equal
cout << ((v1==v2) ? "equal" : "not equal") << endl;

上面这段代码会输出 equal,因为 vector v1 和 v2 具有相同的值。

正确的定义方法是 将引擎和关联的分布对象定义为 static 的:

vector<unsigned >good_randVec()
{
    // 由于我们希望引擎和分布对象保持状态,因此应该将他们定义为
    // static 的,从而每次调用都生成新的数
    static default_random_engine e;
    static uniform_int_distribution<unsigned > u(0,9);
    vector<unsigned > ret;
    for(size_t i = 0; i<100;i++)
        ret.push_back(u(e));
    return ret;
}

由于 e 和 u 都是 static 的,因此它们在函数调用之间会保持住状态。第一次调用会使用 u(e) 生成的序列中的前 100 个随机数,第二次调用会获得接下来 100 个。以此类推。

注意,一个给定的随机数发生器已知会生成相同的随机数序列。一个函数如果定义了局部的随机数发生器,应该将其(包括引擎和分布对象)定义为 static 的。否则,每次调用函数都会生成相同的序列。

设置随机数发生器种子

随机数发生器会生成相同的随机数序列这一特性在调试中很有用。但是,一旦我们的程序调试完毕,我们通常希望每次运行程序都会生成不同的随机结果,可以通过提供一个种子(seed)来达到这个目的。种子就是一个数值,引擎可以利用它从序列中一个新位置重新开始生成随机数。

为引擎设置种子有两种方式:

1、在创建引擎对象时提供种子
2、调用引擎的 seed 成员

// 几乎肯定是生成随机整数 vector 的错误方法
// 每次调用这个函数都会生成相同的 100 个数
default_random_engine e1;       // 使用默认种子
default_random_engine e2(2147483646);       // 使用给定的种子值
// e3 和 e4 将会生成相同的序列,因为他们使用了相同的种子
default_random_engine e3;
e3.seed(32767);             //调用 seed 设置为一个新种子值
default_random_engine e4(32767);    // 将种子值设置为 32767
for(size_t i = 0;i != 10; i++)
{
    if (e1() == e2())
        cout<<"unseeded match at iteeration: "<<i<<endl;
    if (e3() != e4())
        cout<<"seeded differs at itertion: "<<i<<endl;
}

这段代码看不到输出。

设置种子最常用的方法是调用系统函数 time ,这个函数定义再头文件 ctime 中,它返回一个特定时刻到当前经过了多少秒。函数 time 接受单个指针参数,它指向用于写入时间的数据结构。如果此指针为空,则函数简单的返回时间:

default_random_engine e1(time(0));  // 稍微随机些的种子

但是,由于 time 返回以秒计的时间,因此这种方式只适用于生成种子的间隔为秒级或更长的应用。

其他随机数分布

生成随机实数

程序常常需要一个随机浮点数源。特别是程序经常需要 0 到 1 之间的随机数。

可一定以一个 uniform_real_distribution 类型的对象,并让标准库来处理从随机整数到随机浮点数的映射。与处理 uniform_int_distribution 一样,在定义对象时,我们指定最小值和最大值。

default_random_engine e;        // 生成无符号随机整数
    // 0 到 1 (包含)的均匀分布
uniform_real_distribution<double >u(0,1);
for(size_t i =0;i<10;i++)
	cout<<u(e)<<endl;
	

此外,当我们对分布函数不指定默认生成的类型参数时,程序会自动赋予一个类型,生成浮点值得分布类型默认生成 double 类型,生成整型值的分布类型默认生成 int 类型,如下:

uniform_real_distribution<>u(-1,1); // 默认生成 double 值

生成非均匀分布的随机数

除了生成上面的均匀分布,C++ 11 还规定了可以生成 20 种不同的分布类型,比如 均匀分布uniform,正态分布normal,二项分布binomial,泊松分布poisson,学生分布 student 等等,相关函数可以查看相应的函数(具体可以参考 C++ Primer 781页)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值