C++标准库(十):随机数

  • 程序通常需要一个随机数源。在新标准出现之前,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 之间。一个引擎类型的范围可以通过调用该类型对象的 minmax 成员来获得
    • 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
    • 在下面的描述中, 我们可以用 floatdoublelong double 代替 RealT。用一个内置整型类型, 但不包括 bool 类型或任何 char 类型来代替 IntTshortintlonglong longunsigned shortunsigned intunsigned longunsigned 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};
  • ij 是一个权重序列的输入迭代器, 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值