C 11 是一个比较重要的版本,它引入了许多新的语言特性和标准库组件。其中,随机数生成的新特性极大地方便了开发人员在程序中生成随机数。
C 11 的随机数生成分为三个层次,包括随机数生成设备、随机数引擎和随机分布。下面分别对它们进行介绍。
产生随机数
C 11 的标准库提供了一个非确定性随机数生成设备,即 std::random_device
。在 Linux 系统中,std::random_device
通过读取 /dev/urandom
设备来产生真随机数;而在 Windows 系统中,std::random_device
通过 rand_s
函数来产生伪随机数。使用 std::random_device
产生随机数时,可以通过调用其 operator()
方法返回一个 min()
到 max()
之间的随机数。
#include <iostream>
#include <random>
int main()
{
std::random_device rd;
for(int n=0; n<20000; n)
std::cout << rd() << std::endl;
return 0;
}
随机数引擎
随机数引擎是一种伪随机数生成器,传入一个种子后,根据种子生成随机数。C 11 标准提供了三种常用的随机数引擎:std::linear_congruential_engine
、std::mersenne_twister_engine
和 std::subtract_with_carry_engine
。其中,std::linear_congruential_engine
是最常用的一种,速度也非常快;std::mersenne_twister_engine
则被称为最好的伪随机数生成器;std::subtract_with_carry_engine
目前还不太清楚。
使用随机数引擎时,可以通过传入一个整型参数作为种子,也可以使用默认值。如果想多次运行产生相同的随机数,可以使用一个确定的数作为种子;如果想每次运行生成不一样的随机数,则建议使用 std::random_device
产生一个随机数作为种子(Linux 下为真随机数,Windows 下为伪随机数)。
下面是一个示例代码,用于输出 10 个随机数:
代码语言:txt复制#include <iostream>
#include <random>
int main()
{
std::random_device rd;
std::mt19937 mt(rd()); // 梅森旋转算法
for(int n = 0; n < 10; n )
std::cout << mt() << std::endl;
return 0;
}
随机分布
STL 标准库还提供各种各样的随机分布,不过我们经常用的比较少,比如平均分布,正太分布…使用也很简单。随机分布是利用一定的算法处理 URBG 的输出,以使得输出结果按照定义的统计概率密度函数分布。
代码语言:txt复制//平均分布
#include <random>
#include <iostream>
int main()
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(1, 6);
for(int n=0; n<10; n)
std::cout << dis(gen) << ' ';
std::cout << 'n';
}
我们使用 std::random_device
生成种子,以防止使用相同的种子而导致每次程序运行生成相同的随机数序列。接着使用 std::mt19937
作为随机数引擎,生成均匀分布。最后,使用循环输出生成的随机数。
//正太分布
#include <iostream>
#include <iomanip>
#include <string>
#include <map>
#include <random>
#include <cmath>
int main()
{
std::random_device rd;
std::mt19937 gen(rd());
// values near the mean are the most likely
// standard deviation affects the dispersion of generated values from the mean
std::normal_distribution<> d(5,2);
std::map<int, int> hist;
for(int n=0; n<10000; n) {
hist[std::round(d(gen))];
}
for(auto p : hist) {
std::cout << std::fixed << std::setprecision(1) << std::setw(2)
<< p.first << ' ' << std::string(p.second/200, '*') << 'n';
}
}
我们使用 std::normal_distribution
生成正态分布。其中,分布的期望为5,标准差为2。使用循环生成10000个随机数,并将每个数四舍五入到最接近的整数。接着使用 std::map
计算每个数出现的次数,并输出直方图。
总结和思考
我们对于随机数生成器,可以选择使用std::random_device
作为种子,来保证生成的随机数更加随机。使用std::mt19937
作为生成器,并结合不同的分布函数,可以生成不同类型的随机数。需要注意的是,在生成器初始化时,需要将种子传入生成器中。
对于分布函数,C 标准库提供了多种分布函数,如std::uniform_int_distribution
用于生成均匀分布的整数,std::normal_distribution
用于生成正态分布的随机数。分布函数需要结合生成器使用,从而生成具有特定分布特征的随机数。
我们在使用随机数生成器和分布函数时,需要考虑生成的随机数的范围和分布情况,以及生成的随机数是否满足要求。在进行模拟和实验时,随机数的质量直接影响着结果的准确性和可靠性。
我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!