随机数
在新标准出现之前,C和C++都依赖于一个简单的C库函数rand来生成随机数。
而rand函数界限:在stdlib.h头文件中有宏**#define RAND_MAX 0x7fff**,即rand产生一个0~0x7fff的均匀分布的伪随机整数,每个随机数的范围在0和一个系统相关的最大值(至少为32767)之前
rand函数调用:
- rand()函数的每次调用前都会查询是否调用过srand(seed),是否给seed设定了一个值,如果有那么它会自动调用srand(seed)一次来初始化它的起始值
- 若之前没有调用srand(seed),那么系统会自动给seed赋初始值,即srand(1)自动调用它一次
但是rand函数有一些问题:即使不是大多数,也有很多程序需要不同范围的随机数
**注:**C++程序不应该使用库函数rand,而应该使用default_random_engine类和恰当的分布类对象
定义在头文件random中随机库通过一组协作的类来解决这些问题:随机数引擎类(random-number engines)和随机数分布类(random-number distribution)
随机数库的组成:
- 引擎—类型,生成随机unsigned整数序列
- 分布—类型,使用引擎返回服从特定概率分布的随机数
随机数引擎和分布
随机数引擎是函数对象类,它们定义了一个调用运算符,该运算符不接受参数并返回一个随机unsigned整数。
可以通过调用一个随机数引擎对象来生成原始随机数:
#include<iostream>
#include<vector>
#include<random>
using namespace std;
int main()
{
default_random_engine e; // 生成随机无符号数
for(size_t i=0;i<10;i++)
// e()"调用"对象来生成下一个随机数
cout<<e()<<ends;
return 0;
}
标准库定义了多个随机数引擎类,区别在于性能和随机性质量不同。每个编译器都会指定其中以作为default_random_engine类型。此类型一般具有最常用的特性。
随机数引擎操作:
操作 | 解释 |
---|---|
Engine e; | 默认构造函数;使用该引擎类型默认的种子 |
Engine e(s); | 使用整数值s作为种子 |
e.seed(s) | 使用种子s重置引擎的状态 |
e.min() | 此引擎可生成的最小值 |
e.max() | 此引擎可生成的最大值 |
Engine::result_type | 此引擎生成的unsigned整型类型 |
e.discard(u) | 将引擎推进n步;u的类型为unsigned long long |
分布类型和引擎
为了得到在一个指定范围内的数,我们使用一个分布类型的对象:
#include<iostream>
#include<vector>
#include<random>
using namespace std;
int main()
{
// 生成0到9之前(包含)均匀分布的随机数
uniform_int_distribution<unsigned> u(0,9);
default_random_engine e; // 生成无符号随机整数
for(size_t i=0;i<10;i++)
// 将u作为随机数源
// 每个调用返回在指定范围内并服从均匀分布的值
cout<<u(e)<<ends;
cout<<endl<<"min:"<<e.min()<<ends<<"max:"<<e.max()<<endl;
return 0;
}
在此程序中,u(0,9)表示我们希望得到0到9之前(包含)的数。随机数分布类会使用包含的范围,从而我们可以得到给定整数类型的每个可能值
一个引擎类型的范围可以通过调用该类型对象的min和max成员来获得
类似引擎类型,分布类型也是函数对象类。分布类型定义了一个调用运算符,它接受一个随机数引擎作为参数。分布对象使用它的引擎参数发生随机数,并将其映射到指定的分布。
**注:**当我们说随机数发生器时,是指分布对象和引擎对象的组合
引擎生成一个数值序列
结论: ** 一个给定的随机数发生器会一直生成相同的随机数序列。一个随机数如果定义了局部的随机数发生器,应该将其(包括引擎和分布对象)定义为static**的。否则,每次调用函数都会生成相同的序列
例子:假设需要一个函数生成一个vector,其中包含100个均匀分布在0到9之间的随机数。
错误形式:
// 几乎肯定是生成随机整数vector的错误方法
// 每次调用这个函数都会生成相同的100个数
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,包含100个均匀分布的随机数
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;
}
设置随机数发生器种子
随机数发生器会生成相同的随机数序列这一特性在调试中很有用。但是,一旦我们的程序调试完毕,我们通常希望每次运行程序都会生成不同的随机结果,可以通过提供一个种子(seed)来达到这一目的。种子就是一个数值,引擎可以利用它从序列中一个新位置重新开始生成随机数。
引擎设置种子有两种方式:
- 在创建引擎对象时提供种子
- 调用引擎的seed成员
default_random_engine e1; // 使用默认种子
default_random_engine e2(505); // 使用给定的种子值
default_random_engine e3; // 使用默认种子
e3.seed(505); // 调用seed设置一个新种子值
选择一个好的种子,与好的随机数所涉及的其他大多数事情相同,是及其困难的。可能最常用的方法是调用系统函数time。
time函数定义在头文件ctime中,它返回从一个特定时刻到当前经过了多少秒。函数time接收单个指针参数,它指向用于写入时间的数据结构。如果时间为空,则函数简单的返回时间:
default_random_engine e(time(0)); // 稍微随机些的种子
由于time返回以秒计的时间,因此这种方式只适用于生成种子的间隔为秒级或更长的应用
**注:**如果程序作为一个自动过程的一部分反复运行,将time的返回值作为种子的方式就无效了;它可能多次使用的都是相同的种子
其他随机数分布
随机数引擎生成unsigned数,范围内的每个数被生成的概率是相同的。而应用程序中常常需要不同类型或不同分布的随机数。标准库通过定义不同随机数分布对象来满足这两方面的要求,分布对象和引擎对象协同工作,生成要求的结果。
生成随机实数
程序常常需要一个随机浮点数,尤其是0到1之间的随机数。使用新标准库设施,可以很容易地获得随机浮点数。
比如我们可以定义一个uniform_real_distribution类型的对象,并让标准库来处理从随机整数到随机浮点数的映射。
#include<iostream>
#include<vector>
#include<random>
using namespace std;
int main()
{
default_random_engine e; // 生成无符号随机整数
// 0到1(包含)的均匀分布
uniform_real_distribution<double> u(0,1);
for(size_t i=0;i<10;i++)
cout<<u(e)<<ends;
return 0;
}
分布类型的操作:
操作 | 解释 |
---|---|
Dist d; | 默认构造函数;使d准备好被使用;其他构造函数依赖于Dist的类型;分布类型的构造函数是explicit的 |
d(e) | 用相同的e连续调用d的话,会根据d的分布式类型生成一个随机数序列;e是一个随机数引擎 |
d.min() | 返回d(e)能生成的最小值 |
d.max() | 返回d(e)能生成的最大值 |
d.reset() | 重建d的状态,使得随后对d的使用不依赖于d已经生成的值 |
使用分布的默认结果类型
分布类型都是模板,具有单一的模板类型参数,表示分布生成的随机数的类型
每个分布模板都有一个默认的模板实参。生成浮点值的分布类型默认生成double值,而生成整数型值的分布默认生成int值。
由于分布类型只有一个模板参数,因此当我们希望使用默认随机数类型时要记得在模板之后使用空尖括号
// 空<>表示我们希望使用默认结果类型
uniform_real_distribution<> u(0,1); // 默认生成double值
生成非均匀分布的随机数
除了正确生成在指定范围内的数之外,新标准库的另外一个优势是可以生成非均匀分布的随机数。正态分布normal_distribution类
#include<iostream>
#include<vector>
#include<random>
#include<cmath>
using namespace std;
int main()
{
default_random_engine e; // 生成随机整数
normal_distribution<> n(4,1.5); // 均值4,标准差1.5
vector<unsigned> vals(9); // 9个元素均为0
for(size_t i=0;i!=200;i++) {
unsigned v = lround(n(e)); // 舍入到最接近的整数
if(v<vals.size())
++vals[v]; // 统计每个数出现了多少次
}
for(int i=0;i!=vals.size();i++) {
cout<<i<<":"<<string(vals[i],'*')<<endl;
}
return 0;
}
**注:**由于引擎返回相同的随机数序列,所有我们必须在循环外声明引擎对象。否则,每步循环都会创建一个新引擎,从而每步循环都会生成相同的值。类似的,分布对象也要保持状态,因此也应该在循环外定义
其他随机数分布
均匀分布:
uniform_int_distribution<IntT> u(m,n);
uniform_real_distribution<RealT> u(x,y);
生成指定类型的,在给定包含范围内的值。
m(或x)是可以返回的最小值;n(或y)是最大值。m默认为0;n默认为类型IntT对象可以表示的最大值。x默认为0.0,y默认为1.0
伯努利分布:
bernoulli_distribution b(p);
以给定概率p生成true;p的默认值为0.5
泊松分布:
poisson_distribution<IntT> p(x);
均值为double值x的分布
正态分布:
normal_distribution<RealT> n(m,s);
均值为m,标准差为s;m的默认值为0.0,s的默认值为1.0;