C和C++在老版本都依赖一个简单的C库函数rand生成随机数。rand函数生成均匀分布的伪随机整数,每个随机数的范围在0和一个系统相关的最大值之间。
#include <random>
定义在头文件random中的随机数库通过一组协作的类来解决非随机性的问题:随机数引擎(random-number enginers)和随机数分布类(random-number distribution)
引擎 | 类型,生成随机unsigned整数序列 |
分布 | 类型,使用引擎返回服从概率分布的随机数 |
C++程序不应该使用库函数rand,而应使用default_random_engine类和恰当的分布类对象。
随机数引擎和分布
随机数引擎是函数对象类,它们定义了一个调用运算符,该运算符不接受参数并返回一个随机unsigned整数。我们可以通过调用一个随机数引擎对象来生成原始随机数:
#include <random>
default_random_engine e;
for(size_t i = 0; i < 10; ++i)
cout << e() << " ";
标准库定义了多个随机数引擎类,区别在于性能和随机性治疗不同。每个编译器都会指定其中一个作为default_random_engine类型。
Engine e; | 默认构造函数;使用该引擎类型默认的种子 |
Engine e(s); | 使用整型值s作为种子 |
e.seed(s) | 使用种子s重置引擎状态 |
e.min() e.max() | 此引擎可生成的最小值和最大值 |
Engine::result_type | 此引擎生成的unsigned整型类型 |
e.discard(u) | 将引擎推荐u步;u的类型是unsigned long long |
uniform_int_distribution<unsigned> u(0, 9);
default_random_engine e;
for( size_t i = 0; i < 10; ++i)
cout << u(e) << " ";
随机数分布类会使用包含的范围,从而可以得到给定整型类型的每个可能值。类似引擎类型,分布类型也是函数对象类。分布类型定义了一个调用运算符,它接受一个随机数引擎做参数。分布对象使用它的引擎参数生成随机数,并将其映射到指定的分布。
引擎数发生器对于一个给定的发生器,每次运行程序它都会返回相同的数值序列。即一个给定的随机数发生器一直会生成相同的随机数序列。一个函数如果定义了局部的随机数发生器,应该将其(包括引擎和分布对象)定义为static的。否则,每次调用函数都会生成相同的序列:
vector<unsigned> good_randVec()
{
static default_random_engine e;
static uniform_random_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(2147483646);
default_random_engine e2;
e3.seed(32767);
default_random_engine e4(32767);
for(size_t i = 0; i < 100; ++i)
{
if(e1() == e2())
cout << "unseeded match at iteration:" << i << endl;
if(e3() == e4())
cout << "seeded differs at iteration:" << i << endl;
}
#include <ctime>
#include <random>
default_random_engine e(time(0));
其他随机数分布
随机数引擎生成unsigned数,范围内的每个数被生成的概率都是相同的。而应用程序常常需要不同类型或不同分布的随机数。标准库通过定义不同随机数分布对象来满足这些要求。
生成随机实数
default_random_engine e;
uniform_real_distribution<double> u(0, 1);
for(size_t i = 0; i < 10; ++i)
cout << u(e) << " ";
Dist d; | 默认构造函数; 其他构造函数依赖Dist的类型 分布类型的构造函数是explicit的 |
d(e); | 用相同的e连续调用d的话,会根据d的分布式类型生成一个随机数序列; |
d.min() d.max() | 返回d(e)能生成的最小值和最大值 |
d.reset() | 重建d的转态,使得随后对d的使用不依赖d已经生成的值 |
可以通过在模板名之后使用空尖括号来使用默认随机数类型(浮点值默认double,整型默认int)
uniform_random_distribution<> ui(0, 9);
uniform_real_distribution<> ud(0, 1);
生成非均匀分布的随机数
除了正确生成在指定范围内的数之外,C++11标准库还可以生成非均匀分布的随机数。实际上,定义了20中分布类型。
作为一个例子,我们将生成一个正态分布的值的序列,并画出值的分布。由于normal_distribution生成浮点值,我们的程序使用头文件cmath的lround函数将每个随机数舍入到最接近的整数。我们将生成200个数,以均值4为中心,标准差为1.5。
#include <random>
#include <cmath>
default_random_engine e;
normal_distribution<> n(4, 1.5);
vector<unsigned> vals(9);
for( size_t i = 0; i != 200; ++i)
{
unsigned v = lround(n(e));
if( v < vals.size())
++vals[v];
}
for( size_t j = 0; j != vals.size(); ++j)
cout << j << ":" << string(vals[j], "*") << endl;
bernoulli_distribution类
我们注意到一个分布不接受模板参数,即bernoulli_distribution。因为它是一个普通类,而非模板。此分布总是返回一个bool值。它返回true的概率是一个常数,默认值为0.5.
string resp;
default_random_engine e;
bernoulli_distribution b;
do {
bool first = b(e);
cout << ( first ? "We go first"
: "You get to go first") << endl;
cout << ((play(first)) ? "sorry, you lost"
: "congrats, you won") << endl;
cout << "play again? Enter 'yes' or 'no'" << endl;
}while( cin >> resp && resp[0] =='y');