在一些算法中可能运用到一些与随机相关的内容,对随机相关内容有一定了解是必须的。随机数是由随机数生成器生成的。
真随机数与伪随机数
真随机数是用物理现象产生的,是完全随机的。例如抛硬币,抛骰子等都能产生真随机数。真随机数具有不可预测性
真随机数依赖于物理随机数生成器,技术要求较高。
伪随机数是利用算法模拟产生的,结果是确定的,这样的随机数我们认为其是伪随机的。
可以看出真伪随机数差别就在于是否可预测。
在一些重要的场合,例如加密等,涉及到随机的部分通常使用真随机数以保证安全。
C++ 中的真随机数发生器
random_device
是基于硬件的的非确定性均匀分布随机数发生器,该类定义于 C++11,需要 random
头文件,定义于 std
命名空间中。
在 熵池耗尽 前可以高速生成随机数,熵池耗尽后性能急剧下降。所以通常使用 random_device
生成 mt19937
等伪随机数发生器的种子。
如果不支持真随机数生成,标准允许使用伪随机数引擎实现。
引用 cppreference 中的实例:
#include <iostream>
#include <map>
#include <random>
#include <string>
int main()
{
std::random_device rd;
std::map<int, int> hist;
std::uniform_int_distribution<int> dist(0, 9);
for (int n = 0; n != 20000; ++n)
++hist[dist(rd)]; // 注意:仅用于演示:一旦熵池耗尽,
// 许多 random_device 实现的性能就急剧下滑
// 对于实践使用,random_device 通常仅用于
// 播种类似 mt19937 的伪随机数生成器
for (auto [x, y] : hist)
std::cout << x << " : " << std::string(y / 100, '*') << '\n';
}
可能的输出:
0 : ********************
1 : *******************
2 : ********************
3 : ********************
4 : ********************
5 : *******************
6 : ********************
7 : ********************
8 : *******************
9 : ********************
C++ 中的伪随机数发生器
rand
该函数会返回一个位于 [0,RAND_MAX]
范围的随机数,可以通过取模限定返回值范围。其中 RAND_MAX
是系统预定义的一个宏,不能更改。在 linux 环境下为 ,而 windows 系统下为 。windows 环境下这个数是较小的一个数,部分情况下不能支持需求。
rand 比较慢,并且随机效果通常不太好。所以一般不推荐使用。
mt19937
该类定义于 C++11,包含于 random
头文件。随机数范围同 unsigned int
取值范围。随机数质量较高。类似的 mt19937_64
随机数范围扩大到 unsigned long long
。
使用实例:
#include <ctime>
#include <iostream>
#include <random>
using namespace std;
int main() {
mt19937 myrand(time(0)); // 定义时括号内填写种子,也可以不填
cout << myrand() << endl;
return 0;
}
随机数种子
随机数种子决定了整个随机序列,在同一编译器同一种子的情况下,随机生成的序列是同一的。可以使用 time(0)
作为随机数种子。time
函数会返回系统时间。但是注意这个返回值一秒变化一次,如果在对拍等场合会比较低效,因为同一秒调用若干次,得到的数据是相同的。所以可以使用 random_device
进行播种。但是该类又依赖于熵池。建议使用 std::chrono::steady_clock::now().time_since_epoch().count()
函数来得到一个以毫秒计量的时间,以该时间作为随机数种子,注意使用该函数需要包含 chrono
头文件。
随机数分布
有时候,我们希望随机数在一个范围内均匀随机,但是随机数发生器的返回值并不一定满足我们的需求,有几种手段来将随机数限定在希望的范围内。
取模
将返回值进行取模,得到范围内的随机数。
mt19937 Rad();
int rad(int l, int r) {
return Rad() % (r - l + 1) + l;
}
这样有缺陷:得到的结果不一定均匀。因为 Rad
每一个返回值对 r-l+1
取模的结果并不是均匀分布的。
使用 C++ 标准提供的随机数分布
std::uniform_int_distribution
类可以返回一个范围内均匀随机的整数分布。
cppreference 上提供的实例,此程序模拟一个六面体骰子:
#include <iostream>
#include <random>
int main()
{
std::random_device rd; // 随机数引擎的种子源
std::mt19937 gen(rd()); // 以 rd() 播种的 mersenne_twister_engine
std::uniform_int_distribution<> distrib(1, 6);
// 上方的 <> 中填写的是返回值的类型,缺省为 int
// 可以 std::uniform_int_distribution<long long> distrib(L, R);
// 得到一个返回值为 L, R 的一个均匀分布。
// 用 distrib 变换 gen 所生成的随机 unsigned int 为 [1, 6] 中的 int
for (int n = 0; n != 10; ++n)
std::cout << distrib(gen) << ' ';
std::cout << '\n';
}
实际使用可以这样写来避免繁琐的声明:
mt19937 Rad();
int rad(int l, int r) {
return uniform_int_distribution<int>(l, r)(Rad);
}
类似的,随机数分布还有很多种,可能还会用到 uniform_real_distribution
,该类返回一个实数类型的均匀分布,区间左闭右开。