概述
目录
- 随机数引擎和分布
- 分布类型和引擎
- 伪随机数
- 设置随机数发生器种子
- 随机数分布
- 均匀分布
- 伯努利分布
- 泊松分布
- 正态分布
- 抽样分布
- 随机数引擎
- 程序通常需要一个随机数源。在新标准出现之前,C 和 C++ 都依赖于一个简单的 C 库 函数
rand
来生成随机数。此函数生成均匀分布的伪随机整数, 每个随机数的范围在 0 和一个系统相关的最大值(至少为 32767) 之间 rand
函数有一些问题: 很多程序需要不同范围的随机数。一些应用需要随机浮点数。一些程序需要非均匀分布的数。而程序员为了解决这些问题而试图转换rand
生成的随机数的范围、类型或分布时, 常常会引入非随机性- 定义在头文件 random 中的随机数库通过一组协作的类来解决这些问题:随机数引擎类 (random-number engines) 和 随机数分布类 (random-number distribution)
#include <random>
随机数引擎和分布
- 随机数引擎类定义了一个调用运算符,该运算符不接受参数并返回一个随机
unsigned
整数。我们可以通过调用一个随机数引擎对象来生成原始随机数:- 调用一个
default_random_engine
对象的输出类似rand
的输出。随机数引擎生成的unsigned
整数在一个系统定义的范围内, 而rand
生成的数的范围在 0 到RAND_MAX
之间。一个引擎类型的范围可以通过调用该类型对象的min
和max
成员来获得 - C++ 程序不应该使用库函数
rand
, 而应使用default_random_engine
类和恰当的分布类对象
- 调用一个
default_random_engine e; // 生成随机无符号牧
for (size_t i = 0; i < 10; ++i)
// e() "调用" 对象来生成下一个随机数
cout << e() << " ";
- 标准库定义了多个随机数引擎类, 区别在于性能和随机性质量不同。每个编译器都会指定其中一个作为
default_random_engine
类型,此类型一般具有最常用的特性
分布类型和引擎
- 对于大多数场合, 随机数引擎的输出是不能直接使用的, 这也是为什么早先我们称之为原始随机数。问题出在生成的随机数的值范围通常与我们需要的不符
- 为了得到在一个指定范围内的数, 我们使用一个分布类型的对象。分布类型也是函数对象类。分布类型定义了一个调用运算符, 它接受一个随机数引擎作为参数。分布对象使用它的引擎参数生成随机数, 并将其映射到指定的分布
注意,我们传递的是引擎本身, 而不是它生成的下一个值, 原因是某些分布可能需要调用引擎多次才能得到一个值
// 生成 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) << " "·
当我们说随机数发生器时, 是指分布对象和引擎对象的组合
伪随机数
- 一个给定的随机数发生器一直会生成相同的随机数序列。因此,一个函数如果定义了局部的随机数发生器, 应该将其(包括引擎和分布对象)定义为
static
的。否则, 每次调用函数都会生成相同的序列
// 几乎肯定是生成随机整数 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;
}
// 正确方法是将引擎和关联的分布对象定义为 static 的
// 返回一个 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(2147483646); // 使用给定的种子值
// e3 和 e4 将生成相同的序列, 因为它们使用了相同的种子
default_random_engine e3; // 使用默认种子值
e3.seed(32767); // 调用seed设置一个新种子值
default_random_engine e4(32767); // 将种子值设置为32767
- 选择一个好的种子,与生成好的随机数所涉及的其他大多数事情相同,是极其困难的。可能最常用的方法是调用系统函数
time
。这个函数定义在头文件ctime
中,它返回从一个特定时刻到当前经过了多少秒。函数time
接受单个指针参数, 它指向用于写入时间的数据结构。如果此指针为空, 则函数简单地返回时间;由于time
返回以秒计的时间, 因此这种方式只适用于生成种子的间隔为秒级或更长的应用,如果程序作为一个自动过程的一部分反复运行,将time
的返回值作为种子的方式就无效了;它可能多次使用的都是相同的种子:
default_random_engine e1(time(0)); // 稍微随机些的种子
随机数分布
- 除了总是生成
bool
类型的bernoulli_distribution
外, 其他分布类型都是模板。每个模板都接受单个类型参数, 它指出了分布生成的结果类型。分布类与我们已经用过的其他类模板不同,它们限制了我们可以为模板类型指定哪此类型。一些分布模板只能用来生成浮点数, 而其他模板只能用来生成整数 - 分布模板定义了一个默认模板类型参数。整型分布的默认参数是
int
, 生成浮点数的模板的默认参数是double
- 在下面的描述中, 我们可以用
float
、double
或long double
代替RealT
。用一个内置整型类型, 但不包括bool
类型或任何char
类型来代替IntT
(short
、int
、long
、long long
、unsigned short
、unsigned int
、unsigned long
或unsigned long long
)
- 在下面的描述中, 我们可以用
// 空 <> 表示我们希望使用默认结果类型
uniform_real_distribution<> u(0, 1); // 默认生成 double 值
均匀分布
uniform_int_distribution<IntT> u(m, n); // m, b 默认为 0, `IntT` 对象可以表示的最大值
uniform_real_distribution<RealT> u(x, y); // x, y 默认为 0, 1
- 生成指定类型的, 在给定包含范围内的值。
m
(或x
) 是可以返回的最小值;n
(或y
)是最大值
生成随机实数
- 程序经常需要 0 到 1 之间的随机数。最常用但不正确的从
rand
获得一个随机浮点数的方法是用rand()
的结果除以RAND_MAX
。这种方法不正确的原因是随机整数的精度通常低于随机浮点数, 这样, 有一些浮点值就永远不会被生成了 - 使用新标准库设施, 可以很容易地获得随机浮点数。我们可以定义一个
uniform_real_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) << " ";
伯努利分布
// 以给定概率 p 生成 true
// p 的默认值为 0.5
bernoulli_distribution b(p);
// 分布是按采样大小为整型值 t, 概率为 p 生成的
// t 的默认值为 1, p 的默认值为 0.5
binomial_distribution<IntT> b(t, p);
// 每次试验成功的概率为 p
// p 的默认值为 0.5
geometric_distribution<IntT> g(p);
// k (整型值)次试验成功的概率为p
// k 的默认值为 1, p 的默认值为 0.5
negative_binomial_distribution<IntT> nb(k, p);
- 作为一个这种分布的例子, 我们可以编写一个程序, 这个程序与用户玩一个游戏。为了进行这个游戏, 其中一个游戏者必须先行。我们可以用一个值范围是 0 到 1 的
uniform_int_distribution
来选择先行的游戏者, 但也可以用伯努利分布来完成这个选择
string resp;
default_random_engine e; // e 应保持状态, 所以必须在循环外定义!
bernoulli_distribution b; // 分布对象也应保持状态,在循环外定义;默认是 50 / 50 的机会
// 使用伯努利分布也可以方便的调整先行一方的概率
// bernoulli_distribution b(.55);
do {
bool first = b(e); // 如果为 true, 则程序先行
cout << (first ? "We go first" : "You get to go first") << endl;
// ...
cout << "play again? Enter 'yes' or 'no'" << endl;
} while (cin >> resp && resp[0] == 'y');
泊松分布
// 均值为 double 值 x 的分布
poisson_distribution<IntT> p(x);
// 指数分布, 参数 lambda 通过浮点值 lam 给出
// lam 的默认值为 1.0
exponential_distribution<RealT> e(lam);
// alpha (形状参数)为 a, beta (尺度参数)为 b
// 两者的默认值均为 1.0
gamma_distribution<RealT> g(a, b);
// 形状参数为 a, 尺度参数为 b 的分布
// 两者的默认值均为 1.0
weibull_distribution<RealT> w(a, b);
// a 的默认值为 0.0, b 的默认值为 1.0
extreme_value_distribution<RealT> e(a, b);
正态分布
// 均值为 m, 标准差为 s
// m 的默认值为 0.0, s 的默认值为 1.0
normal_distribution<RealT> n(m, s);
// 均值为 m, 标准差为 s
// m 的默认值为 0.0, s 的默认值为 1.0
lognormal_distribution<RealT> ln(m, s);
// 自由度为 x
// 默认值为 1.0
chi_squared_distribution<RealT> c(x);
// 位置参数 a 和尺度参数 b 的默认值分别为 0.0 和 1.0
cauchy_distribution<RealT> c(a, b);
// 自由度为 m 和 n; 默认值均为 1
fisher_f_distribution<RealT> f(m, n);
// 自由度为 n; n 的默认值均为 1
student_t_distribution<RealT> s(n);
default_random_engine e; // 生成随机整数
normal_distribution<> n(4, 1.5); // 均值 4,标准差 1.5 的正态分布 (默认类型为 double)
vector<unsigned> vals(9); // 9 个元素均为0
for (size_t i = 0; i != 200; ++i) {
unsigned v = lround(n(e)); // 舍入到最接近的整数 (cmath 头文件中)
// 由于使用的是正态分布, 我们期望生成的数中大约 99% 都在 0 到 8 之间
if (v < vals.size()) // 如果结果在范围内
++vals[v]; // 统计每个数出现了多少次
}
抽样分布
discrete_distribution<IntT> d(i, j);
discrete_distribution<IntT> d{il};
i
和j
是一个权重序列的输入迭代器,il
是一个权重的花括号列表。权重必须能转换为double
// b、e 和w 是输入迭代器
piecewise_constant_distribution<RealT> pc(b, e, w);
piecewise_linear_distribution<RealT> pl(b, e, w);
随机数引擎
- 标准库定义了三个类,实现了不同的算法来生成随机数。标准库还定义了三个适配器,可以修改给定引擎生成的序列。引擎和引擎适配器类都是模板。与分布的参数不同, 这些引擎的参数更为复杂,且需深入了解特定引擎使用的数学知识。我们在这里列出所有引擎,以便读者对它们有所了解, 但介绍如何生成这些类型超出了本书的范围
- 标准库还定义了几个从引擎和适配器类型构造的类型。
default_random_engine
类型是一个参数化的引擎类型的类型别名,参数化所用的变量的目的是在通常情况下获得好的性能。标准库还定义了几个类, 它们都是一个引擎或适配器的完全特例化版本。标准库定义的引擎和特例化版本如下:
// 某个其他引擎类型的类型别名, 目的是用于大多数情况
default_random_engine
// minstd_rand0 的乘数为 16807, 模为 2147483647, 增量为 0
// minstd_rand 的乘数为 48271, 模为 2147483647, 增量为 0
linear_congruential_engine
// mt19937 为 32 位无符号梅森旋转生成器
// mt19937_64 为 64 位无符号梅森旋转生成器
mersenne_twister_engine
// ranlux24_base 为 32 位无符号借位减法生成器
// ranlux48_base 为 64 位无符号借位减法生成器
subtract_with_carry_engine
// 引擎适配器, 将其底层引擎的结果丢弃。用要使用的底层引擎、块大小和旧块大小来参数化
// ranlux24 使用 ranlux24_base 引擎, 块大小为 223, 旧块大小为 23
// ranlux48 使用 ranlux48_base引擎, 块大小为 389, 旧块大小为 11
discard_block_engine
// 引擎适配器, 生成指定位数的随机数。用要使用的底层引擎、结果的位数以及保存生成的
// 二进制位的无符号整型类型来参数化。指定的位数必须小于指定的无符号类型所能保存的位数
independent_bits_engine
// 引擎适配器, 返回的就是底层引擎生成的数,但返回的顺序不同。用要使用的底层引擎和要混洗的元素数目来参数化
// knuth_b 使用 minstd_rand0 和表大小 256
shuffle_order_engine
最后
以上就是自然可乐为你收集整理的C++标准库(十):随机数随机数引擎和分布随机数分布随机数引擎的全部内容,希望文章能够帮你解决C++标准库(十):随机数随机数引擎和分布随机数分布随机数引擎所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复