【C++ 随机数分布类型 】深入探索C++随机数分布:原理、应用与实践(一)

简介: 【C++ 随机数分布类型 】深入探索C++随机数分布:原理、应用与实践

1. 引言

在编程的世界中,随机性(Randomness)经常被提及。无论是为了测试、模拟还是为了增加某种不可预测性,随机数都是我们日常工作中的重要工具。但是,真正理解随机数的生成和分布需要深入到计算机科学和数学的交叉领域。在这一章中,我们将探讨C++标准库中的随机数生成与分布,并从心理学的角度来看待它们。

1.1 C++标准库中的随机数生成与分布

在C++中,随机数的生成不仅仅是调用一个函数那么简单。它涉及到一系列的生成器和分布,这些都在头文件中定义。这个库提供了一种灵活而强大的方式来生成随机数,满足了各种应用的需求。

例如,当我们想要生成一个随机整数时,我们可能会这样做:

#include <random>
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> distr(1, 6);
int random_number = distr(gen);

在上面的代码中,我们首先创建了一个随机设备rd,然后使用它来初始化一个Mersenne Twister生成器gen。接下来,我们定义了一个均匀分布distr,范围从1到6,最后我们使用这个分布来生成一个随机整数。

但为什么我们需要这么多步骤来生成一个随机数呢?这背后的原因与心理学中的一个观点相吻合:人们对于随机性的直觉往往是错误的。我们很难真正理解随机性,而计算机在生成随机数时也面临着相似的挑战。为了获得真正的随机性,我们需要使用复杂的算法和方法。

1.2 随机数分布的重要性

当我们谈论随机数时,我们实际上是在谈论两件事:随机数的生成和随机数的分布。生成是关于如何产生随机数的,而分布是关于这些数字如何分布的。

想象一下,如果你在一个骰子游戏中使用一个不均匀的骰子,那么某些数字可能会比其他数字出现得更频繁。这就是为什么我们需要考虑分布的原因。在C++中,我们有多种分布可以选择,如均匀分布、正态分布等。

从心理学的角度看,人们对于随机事件的期望往往是均匀分布的。但实际上,许多随机事件遵循其他类型的分布。例如,人们的身高遵循正态分布,而不是均匀分布。因此,了解不同的分布类型可以帮助我们更好地模拟和理解现实世界。

“随机性不是不按某种顺序,而是按照我们不能确定的顺序。” - 西尼加·塔列布(Nassim Nicholas Taleb)

2. C++随机数生成器简介

在深入探讨随机数分布之前,我们首先需要理解随机数是如何在C++中生成的。这不仅仅是一个简单的过程,而是涉及到一系列的生成器和算法。

2.1 基本的随机数生成器

在C++中,随机数生成器是用于产生随机序列的对象。这些生成器基于特定的算法,可以产生伪随机数,这意味着它们实际上是由算法确定的,但看起来像是随机的。

以下是一些常见的随机数生成器:

  • std::default_random_engine: 默认的随机数生成器。
  • std::mt19937: 基于Mersenne Twister算法的32位随机数生成器。
  • std::mt19937_64: 基于Mersenne Twister算法的64位随机数生成器。
  • std::ranlux24std::ranlux48: 基于RANLUX算法的随机数生成器。

每种生成器都有其特点和应用场景。例如,Mersenne Twister生成器被广泛认为是高质量的随机数生成器,但它的状态非常大,可能不适合内存受限的环境。

2.2 为什么需要分布?

你可能会问,为什么我们不能直接使用随机数生成器产生的数值呢?答案是,生成器产生的数值通常是在一个非常大的范围内,例如从0到4,294,967,295。而我们通常需要的随机数是在一个特定的范围内,例如从1到6。这就是分布发挥作用的地方。

分布可以看作是一个函数,它接受生成器产生的原始随机数,并将其转换为我们需要的范围和形状。例如,均匀分布会确保每个数值在指定范围内都有相同的概率被选中。

“在混乱中寻找模式,是人类的天性。” - 卡尔·塞根(Carl Sagan)

从心理学的角度看,我们的大脑总是试图找到模式和规律,即使在随机数据中也是如此。这就是为什么我们需要确保我们的随机数真的是随机的,而不是某种隐藏的模式。

2.3 从底层源码看随机数生成

让我们深入探讨一下随机数生成器是如何工作的。以Mersenne Twister为例,它是一个基于线性反馈移位寄存器的算法。它的工作原理是使用一系列的位操作和模运算来产生随机数。

方法名称 功能描述 返回值
seed() 设置随机数生成器的种子
operator() 生成一个随机数 随机数
min() 返回生成器可以产生的最小值 最小值
max() 返回生成器可以产生的最大值 最大值

当我们调用operator()方法时,生成器会使用其内部状态和算法来产生一个新的随机数。这个数值通常是在一个非常大的范围内,所以我们需要使用分布来将其转换为我们需要的范围。

3. 均匀分布

均匀分布是最基础的随机数分布,它确保在指定的范围内每个数值都有相同的出现概率。在C++标准库中,我们主要使用std::uniform_int_distributionstd::uniform_real_distribution 来实现整数和浮点数的均匀分布。

3.1 std::uniform_int_distributionstd::uniform_real_distribution 的基本介绍

3.1.1 std::uniform_int_distribution

这个分布用于生成一个在指定闭区间 [a, b] 内的整数值。例如,如果我们想生成一个在 [1, 6] 之间的整数,可以想象这就像掷一个六面的骰子。

示例:

#include <random>
#include <iostream>
int main() {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> distr(1, 6); // 定义在 [1, 6] 之间的分布
    for (int i = 0; i < 10; ++i) {
        std::cout << distr(gen) << ' '; // 生成并打印10个随机数
    }
    return 0;
}

在这个示例中,我们使用了Mersenne Twister算法 (std::mt19937) 作为随机数生成器。这是一个广泛使用的高质量的随机数生成器。

3.1.2 std::uniform_real_distribution

这个分布用于生成一个在指定半开区间 [a, b) 内的浮点数值。这意味着生成的数值可以等于 a 但永远不会达到 b。

示例:

#include <random>
#include <iostream>
int main() {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_real_distribution<> distr(0.0, 1.0); // 定义在 [0.0, 1.0) 之间的分布
    for (int i = 0; i < 10; ++i) {
        std::cout << distr(gen) << ' '; // 生成并打印10个随机数
    }
    return 0;
}

从心理学的角度来看,人们对于随机性的直觉往往是有偏见的。例如,当我们掷一个公正的骰子时,我们可能期望每个数字在长时间内出现的次数大致相同。但在短时间内,可能会出现连续掷出相同数字的情况。这并不意味着骰子是有偏的或者随机数生成器是有问题的,这只是随机性的本质。正如心理学家Daniel Kahneman在《思考,快与慢》(Thinking, Fast and Slow)中所说,人们对随机性的直觉往往是错误的。

3.2 使用场景与示例

均匀分布在许多应用中都非常有用。例如,模拟骰子掷出的结果、生成随机颜色代码或在游戏中随机选择一个玩家。

示例:

想象一个简单的游戏,玩家需要猜测计算机生成的1到10之间的数字。我们可以使用std::uniform_int_distribution来生成这个数字。

#include <random>
#include <iostream>
int main() {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> distr(1, 10);
    int secretNumber = distr(gen);
    int guess;
    std::cout << "猜测一个1到10之间的数字: ";
    std::cin >> guess;
    if (guess == secretNumber) {
        std::cout << "恭喜你,猜对了!";
    } else {
        std::cout << "很遗憾,正确的数字是 " << secretNumber << ".";
    }
    return 0;
}

3.3 注意事项

  1. 选择合适的随机数生成器:虽然C++提供了多种随机数生成器,但不是所有的生成器都适合所有的应用。例如,std::mt19937 是一个高质量的随机数生成器,但它可能比其他简单的生成器慢。
  2. 初始化随机数生成器:使用 std::random_device 或当前时间来初始化随机数生成器,确保每次运行程序时都能得到不同的随机数序列。
  3. 理解分布的范围:确保你理解了所选分布的范围,特别是 std::uniform_real_distribution 的半开区间特性。
  4. 避免偏见:正如前面提到的,人们对随机性的直觉可能是有偏见的。当使用随机数时,要确保理解随机性的本质,并避免因短期的不规律性而得出错误的结论。

在深入探讨随机数分布的实现时,我们可以查看C++标准库的源代码。这可以帮助我们更好地理解这些分布是如何工作的,以及它们是如何确保生成的数字确实是随机的。

4. 正态分布 (std::normal_distribution)

正态分布(Normal Distribution),也被称为高斯分布(Gaussian Distribution),是自然和社会科学中最常见的概率分布之一。在C++中,我们可以使用std::normal_distribution来生成符合正态分布的随机数。

4.1 原理与特点

正态分布是一个连续的概率分布,其函数形状为一个对称的钟形曲线。正态分布的两个主要参数是均值(mean)和标准差(standard deviation)。均值描述了分布的中心位置,而标准差描述了分布的宽度或数据的离散程度。

从心理学的角度来看,正态分布描述了许多自然现象的随机变化,如人的智商、身高或考试分数。这是因为许多独立的因素影响了这些测量,当这些因素叠加在一起时,它们形成了一个正态分布。

“在自然中,我们经常看到正态分布,因为它是中心极限定理的一个结果。” - Sir Francis Galton

4.1.1 正态分布的数学形式

正态分布的概率密度函数(Probability Density Function, PDF)为:

[ f(x) = \frac{1}{\sigma \sqrt{2\pi}} e{-\frac{(x-\mu)2}{2\sigma^2}} ]

其中,\(\mu\) 是均值,\(\sigma\) 是标准差。

4.2 使用场景与示例

考虑一个场景,你正在为一个游戏设计一个角色的属性,如力量、智慧和敏捷性。你希望大多数角色的属性都是平均的,但也有一些角色的属性非常高或非常低。这是一个典型的正态分布的应用场景。

#include <iostream>
#include <random>
int main() {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::normal_distribution<> d(50, 10);  // 均值为50,标准差为10
    for (int i = 0; i < 10; ++i) {
        std::cout << d(gen) << "\n";  // 生成符合正态分布的随机数
    }
    return 0;
}

在上述代码中,我们使用了均值为50、标准差为10的正态分布来生成角色的某一属性。大多数生成的值将接近50,但也有可能生成40以下或60以上的值。

4.3 注意事项

  1. 当使用std::normal_distribution时,确保你理解了均值和标准差的意义,并根据你的应用场景正确地设置它们。
  2. 正态分布生成的值是无界的,这意味着理论上它可以生成任何浮点数。在实际应用中,你可能需要对生成的值进行裁剪或转换,以确保它们在一个合理的范围内。

“大多数人都是平均的,这是正态分布的美妙之处。” - Carl Friedrich Gauss

方法 描述 适用场景
std::uniform_int_distribution 生成均匀分布的整数 当所有值的概率都相等时
std::uniform_real_distribution 生成均匀分布的浮点数 当所有值的概率都相等时
std::normal_distribution 生成正态分布的浮点数 当大多数值围绕一个中心值聚集时

从底层源码的角度来看,std::normal_distribution 使用了Box-Muller变换或其他方法来从均匀分布的随机数生成正态分布的随机数。

5. 伯努利与二项分布

在我们的日常生活中,许多事件都有两种可能的结果。例如,当我们抛硬币时,结果可能是正面或反面。这种只有两种可能结果的随机试验被称为伯努利试验 (Bernoulli trial)。在C++中,我们可以使用 std::bernoulli_distributionstd::binomial_distribution 来模拟这种试验。

5.1 std::bernoulli_distribution (伯努利分布)

伯努利分布描述了一个试验只有两种可能结果的情况。例如,抛硬币的正面或反面。

示例:

#include <iostream>
#include <random>
int main() {
    std::default_random_engine generator;
    std::bernoulli_distribution distribution(0.5);  // 0.5 的概率为 true,0.5 的概率为 false
    int count_true = 0;
    for (int i = 0; i < 1000; ++i) {
        if (distribution(generator)) {
            count_true++;
        }
    }
    std::cout << "Number of true: " << count_true << std::endl;
}

在上述代码中,我们模拟了1000次抛硬币的试验,并统计了正面出现的次数。

从心理学的角度来看,人们对于50-50的结果往往有一种直观的公平感。这也是为什么我们经常用抛硬币来做决策。正如心理学家Daniel Kahneman在《思考,快与慢》(Thinking, Fast and Slow) 中所说,人们对于随机事件的结果往往有一种固有的期望。

5.2 std::binomial_distribution (二项分布)

二项分布描述了在给定次数的独立伯努利试验中成功的次数。例如,抛10次硬币得到正面的次数。

示例:

#include <iostream>
#include <random>
int main() {
    std::default_random_engine generator;
    std::binomial_distribution<int> distribution(10, 0.5);  // 10次试验,每次试验成功的概率为0.5
    int count_success = distribution(generator);
    std::cout << "Number of successes in 10 trials: " << count_success << std::endl;
}

在上述代码中,我们模拟了10次抛硬币的试验,并统计了10次中正面出现的次数。

从心理学的角度来看,人们对于连续的随机事件结果往往有一种连续性的偏见。例如,如果一个人连续抛出了几次正面,他可能会错误地认为下一次抛出反面的概率会增加。这种现象在心理学中被称为赌徒谬误 (Gambler’s Fallacy)。

5.2.1 伯努利分布与二项分布的关系

伯努利分布是二项分布的一个特例,当试验次数为1时,二项分布就退化为伯努利分布。

分布类型 试验次数 成功概率 示例
伯努利分布 1 p 抛一次硬币
二项分布 n p 抛n次硬币

从底层源码的角度来看,std::bernoulli_distributionstd::binomial_distribution 都是基于随机数生成器来实现的。但它们的实现细节和算法可能会有所不同,这取决于C++标准库的具体实现。


【C++ 随机数分布类型 】深入探索C++随机数分布:原理、应用与实践(二)https://developer.aliyun.com/article/1467679

目录
相关文章
|
1月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
142 77
|
1月前
|
存储 C++
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
59 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
1月前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
68 19
|
1月前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
50 13
|
1月前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
48 12
|
1月前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
46 10
|
1月前
|
算法 C++
【C++数据结构——图】最小生成树(头歌实践教学平台习题) 【合集】
【数据结构——图】最小生成树(头歌实践教学平台习题)目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:【合集】任务描述 本关任务:编写一个程序求图的最小生成树。相关知识 为了完成本关任务,你需要掌握:1.建立邻接矩阵,2.Prim算法。建立邻接矩阵 上述带权无向图对应的二维数组,根据它建立邻接矩阵,如图1建立下列邻接矩阵。注意:INF表示无穷大,表示整数:32767 intA[MAXV][MAXV];Prim算法 普里姆(Prim)算法是一种构造性算法,从候选边中挑
43 10
|
2天前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
1月前
|
编译器 数据安全/隐私保护 C++
【C++面向对象——继承与派生】派生类的应用(头歌实践教学平台习题)【合集】
本实验旨在学习类的继承关系、不同继承方式下的访问控制及利用虚基类解决二义性问题。主要内容包括: 1. **类的继承关系基础概念**:介绍继承的定义及声明派生类的语法。 2. **不同继承方式下对基类成员的访问控制**:详细说明`public`、`private`和`protected`继承方式对基类成员的访问权限影响。 3. **利用虚基类解决二义性问题**:解释多继承中可能出现的二义性及其解决方案——虚基类。 实验任务要求从`people`类派生出`student`、`teacher`、`graduate`和`TA`类,添加特定属性并测试这些类的功能。最终通过创建教师和助教实例,验证代码
50 5
|
1月前
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
40 5