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

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

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


6. 几何与泊松分布

在我们的日常生活中,许多事件的发生都遵循某种概率分布。理解这些分布对于编程和数据分析至关重要。今天,我们将深入探讨两种常见的概率分布:几何分布和泊松分布,并从心理学的角度来看它们如何影响我们的决策。

6.1 几何分布 (std::geometric_distribution)

几何分布(Geometric Distribution)描述了在第一次成功之前需要进行的伯努利试验的次数。例如,如果你连续抛硬币,直到第一次得到正面,那么抛硬币的次数就遵循几何分布。

原理

考虑一个伯努利试验,成功的概率为 p。那么,第一次成功发生在第 k 次试验的概率为 (1-p)^(k-1) * p

从心理学的角度看,人们对于连续失败后的第一次成功通常有很强的期望。这种期望可能会导致人们在面对困难时更加坚持,因为他们相信成功即将到来。

示例

#include <iostream>
#include <random>
int main() {
    std::default_random_engine generator;
    std::geometric_distribution<int> distribution(0.5); // p=0.5
    int count = 0;
    for (int i=0; i<1000; ++i) {
        if (distribution(generator) == 1) {
            count++;
        }
    }
    std::cout << "Number of first-time successes in 1000 trials: " << count << std::endl;
}

在上述代码中,我们模拟了1000次试验,每次试验的成功概率为0.5。我们统计了第一次成功的次数。

6.2 泊松分布 (std::poisson_distribution)

泊松分布(Poisson Distribution)描述了在固定时间间隔或空间内随机事件发生的次数。这种分布在描述稀有事件时特别有用。

原理

考虑一个事件在单位时间内发生的平均次数为 λ。那么,该事件在单位时间内发生 k 次的概率为 (e^(-λ) * λ^k) / k!

心理学家 Daniel Kahneman 在其著作《Thinking, Fast and Slow》(《快思慢想》)中提到,人们往往高估稀有事件的发生概率。这种认知偏差可能是由于稀有事件的显著性和记忆中的可用性所导致的。

示例

#include <iostream>
#include <random>
int main() {
    std::default_random_engine generator;
    std::poisson_distribution<int> distribution(3); // λ=3
    int count = 0;
    for (int i=0; i<1000; ++i) {
        if (distribution(generator) == 2) {
            count++;
        }
    }
    std::cout << "Number of times the event occurred twice in 1000 units of time: " << count << std::endl;
}

在上述代码中,我们模拟了1000个单位时间,每个单位时间内事件的平均发生次数为3。我们统计了事件在单位时间内发生两次的次数。

方法对比

分布类型 参数 描述 示例
几何分布 p (成功概率) 描述在第一次成功之前的试验次数 抛硬币直到得到正面
泊松分布 λ (单位时间内的平均事件数) 描述在固定时间间隔内的事件发生次数 一小时内到达的电话数量

7. 指数分布 (std::exponential_distribution)

指数分布是描述两个连续随机事件之间的时间间隔的概率分布。在现实生活中,我们经常遇到这样的情况,例如:一个呼叫中心接到两个连续电话之间的时间间隔,或者公交车站两辆连续公交车之间的到达时间。

7.1 原理与特点

指数分布的一个关键特性是它具有所谓的“无记忆性”(memorylessness)。这意味着,无论我们已经等待了多长时间,下一个事件发生的期望时间始终保持不变。从心理学的角度看,这与人们对于等待的感知是一致的。例如,当我们在等待公交车时,无论我们已经等待了多长时间,我们总是觉得下一辆公交车可能马上就要到来。

在C++中,std::exponential_distribution 是用来模拟这种分布的。它的构造函数接受一个参数λ(lambda),这个参数是事件的平均发生率。例如,如果一个呼叫中心平均每分钟接到一个电话,那么λ就是1。

std::default_random_engine generator;
std::exponential_distribution<double> distribution(1.0);  // λ = 1.0
double interval = distribution(generator);

上面的代码会生成一个表示两个连续电话之间的时间间隔的随机数。

7.2 使用场景与示例

考虑一个常见的场景:一个服务器处理客户端的请求。如果我们知道这个服务器平均每秒处理10个请求,我们可以使用指数分布来模拟两个连续请求之间的时间间隔。

std::default_random_engine generator;
std::exponential_distribution<double> distribution(10.0);  // λ = 10.0
double interval = distribution(generator);

这里,interval 表示两个连续请求之间的时间间隔。

从心理学的角度看,这种模拟可以帮助我们更好地理解用户的等待体验。如果用户经常需要等待很长时间,他们可能会变得不耐烦。通过模拟,我们可以预测并优化这种等待体验。

7.3 注意事项

  1. 当使用 std::exponential_distribution 时,确保λ值是正的。λ值代表了事件的平均发生率,它不能是负数或零。
  2. 指数分布是一个连续的分布,这意味着它可以生成任何在0到正无穷之间的值。但在实际应用中,你可能需要对这些值进行取整或限制。

7.3.1 深入源码

std::exponential_distribution 的实现基于逆变换采样方法。这种方法的基本思想是先生成一个均匀分布的随机数,然后通过某个函数将其转换为所需的分布。这种方法的优点是速度快,但需要确保使用的函数是单调的。

7.4 技术对比

分布类型 特点 使用场景 参数
均匀分布 (std::uniform_distribution) 生成在指定范围内的随机数 任何需要均匀随机数的场景 范围的最小值和最大值
指数分布 (std::exponential_distribution) 描述两个连续事件之间的时间间隔 事件的时间间隔,如呼叫中心的电话间隔 事件的平均发生率λ

在选择分布时,重要的是要考虑你的具体需求。不同的分布有不同的特点和使用场景,选择合适的分布可以使你的模拟或应用更加准确。

8. 自定义分布

在C++的世界中,虽然标准库为我们提供了丰富的随机数分布,但有时我们可能需要根据特定的需求创建自己的随机数分布。这不仅是一个技术挑战,更是一个对人性的挑战。正如心理学家Carl Rogers所说:“我们不能改变、我们不能超越我们所不知道的。”(我们不能改变或超越我们所不知道的事物)。因此,深入理解如何从底层创建自定义分布是至关重要的。

8.1 为什么需要自定义分布?

在某些特定的应用场景中,标准库提供的分布可能不满足我们的需求。例如,可能需要一个特定形状的分布,或者需要模拟某种现实世界的随机过程。在这种情况下,我们需要自定义分布来满足这些特定的需求。

8.2 创建自定义分布的基本步骤

  1. 定义分布的形状:首先,需要确定分布的形状。这通常涉及到数学和统计学的知识。
  2. 实现分布函数:使用C++代码实现分布函数。这可能涉及到一些数学计算和算法。
  3. 测试和验证:确保自定义分布的实现是正确的,并且满足预期的需求。

8.3 示例:三角形分布 (Triangular Distribution)

三角形分布是一个简单的分布,它在最小值和最大值之间呈三角形。这种分布在某些模拟和统计分析中很有用。

8.3.1 三角形分布的原理

三角形分布的概率密度函数 (PDF, 概率密度函数) 可以表示为:


image.png

image.png

其中,(a) 是最小值,(b) 是最大值,(c) 是峰值所在的位置。

8.3.2 C++实现

class TriangularDistribution {
private:
    double a, b, c;
public:
    TriangularDistribution(double min, double max, double peak) : a(min), b(max), c(peak) {}
    double operator()(std::mt19937& gen) {
        std::uniform_real_distribution<> distr(0.0, 1.0);
        double u = distr(gen);
        if (u < (c - a) / (b - a)) {
            return a + sqrt(u * (b - a) * (c - a));
        } else {
            return b - sqrt((1 - u) * (b - a) * (b - c));
        }
    }
};

8.3.3 使用示例

std::mt19937 gen(std::random_device{}());
TriangularDistribution distr(0.0, 10.0, 5.0);
for (int i = 0; i < 100; ++i) {
    std::cout << distr(gen) << std::endl;
}

这个示例生成了100个遵循三角形分布的随机数,范围在 [0.0, 10.0],峰值在5.0。

8.4 注意事项

  1. 确保分布的正确性:在实现自定义分布时,必须确保分布的形状和特性与预期相符。可以使用统计方法验证分布的正确性。
  2. 性能考虑:在某些应用中,可能需要高效地生成大量的随机数。在这种情况下,需要确保自定义分布的实现是高效的。
  3. 避免过度复杂:正如心理学家William of Ockham所说:“不应当做无必要的假设。”(不应该做不必要的假设)。在设计自定义分布时,应该尽量简单,避免不必要的复杂性。

9. C++版本与随机数分布

在C++的发展历程中,随机数生成和分布的处理经历了多次的改进和扩展。从C++11开始,标准库引入了一个全新的随机数库,为我们提供了更强大、更灵活的随机数生成和分布工具。

9.1 C++11的革命

C++11是一个里程碑,它引入了头文件,为我们提供了一系列的随机数生成器和分布。在此之前,C++程序员通常依赖于C语言的rand()函数和RAND_MAX宏来生成随机数,但这种方法有其局限性,例如随机数的质量不高,分布不均匀等。

9.1.1 为什么C++11的随机库如此重要?

从心理学的角度来看,人类的大脑善于寻找模式。当我们观察到rand()生成的随机数序列中存在某种模式或规律时,我们可能会对其产生怀疑。C++11的随机库通过提供多种随机数生成器和分布,使得生成的随机数更接近真正的随机性,从而满足了我们的心理预期。

“随机性是自然的,但真正的随机性是难以捉摸的。” - Donald Knuth(计算机科学家,C++名著《计算机程序设计艺术》的作者)

9.1.2 示例与注释

考虑以下使用C++11随机库的示例:

#include <random>
#include <iostream>
int main() {
    std::random_device rd;  // 真随机数生成器(真随机数生成器)
    std::mt19937 gen(rd()); // 使用Mersenne Twister算法的伪随机数生成器(伪随机数生成器)
    std::uniform_int_distribution<> distr(1, 6); // 均匀分布(均匀分布)
    for (int i = 0; i < 10; ++i) {
        std::cout << distr(gen) << " "; // 生成1到6之间的随机数
    }
    return 0;
}

在上述示例中,我们首先使用std::random_device来生成一个真正的随机数,然后使用这个随机数来初始化Mersenne Twister伪随机数生成器。最后,我们使用均匀分布来生成1到6之间的随机数,模拟掷骰子的效果。

9.2 C++14, C++17, C++20的进展

虽然C++11为我们提供了一个强大的随机库,但在后续的C++版本中,这个库并没有经历太大的变化。主要的改进集中在其他领域,如模板元编程、并发和并行编程等。

但是,随着C++的发展,社区对随机库的使用和反馈也在不断增加,这为未来的版本提供了改进的可能性。

9.2.1 底层源码分析

当我们深入到C++随机库的底层实现时,我们可以看到其复杂性和精妙之处。例如,Mersenne Twister算法是如何确保其生成的随机数序列具有很长的周期和良好的统计特性的。

“深入理解原理是掌握技术的关键。” - Bjarne Stroustrup(C++的创始人)

9.2.2 方法对比

方法/特性 C++11 C++14 C++17 C++20
<random>
Mersenne Twister
新增的分布

从上表中,我们可以看到在不同的C++版本中,随机库的核心功能和方法保持不变。

结语

在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。

这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。

我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。

目录
相关文章
|
7月前
|
存储 负载均衡 算法
基于 C++ 语言的迪杰斯特拉算法在局域网计算机管理中的应用剖析
在局域网计算机管理中,迪杰斯特拉算法用于优化网络路径、分配资源和定位故障节点,确保高效稳定的网络环境。该算法通过计算最短路径,提升数据传输速率与稳定性,实现负载均衡并快速排除故障。C++代码示例展示了其在网络模拟中的应用,为企业信息化建设提供有力支持。
181 15
|
4月前
|
存储 算法 安全
c++模板进阶操作——非类型模板参数、模板的特化以及模板的分离编译
在 C++ 中,仿函数(Functor)是指重载了函数调用运算符()的对象。仿函数可以像普通函数一样被调用,但它们实际上是对象,可以携带状态并具有更多功能。与普通函数相比,仿函数具有更强的灵活性和可扩展性。仿函数通常通过定义一个包含operator()的类来实现。public:// 重载函数调用运算符Add add;// 创建 Add 类的对象// 使用仿函数return 0;
123 0
|
8月前
|
算法 Serverless 数据处理
从集思录可转债数据探秘:Python与C++实现的移动平均算法应用
本文探讨了如何利用移动平均算法分析集思录提供的可转债数据,帮助投资者把握价格趋势。通过Python和C++两种编程语言实现简单移动平均(SMA),展示了数据处理的具体方法。Python代码借助`pandas`库轻松计算5日SMA,而C++代码则通过高效的数据处理展示了SMA的计算过程。集思录平台提供了详尽且及时的可转债数据,助力投资者结合算法与社区讨论,做出更明智的投资决策。掌握这些工具和技术,有助于在复杂多变的金融市场中挖掘更多价值。
255 12
|
9月前
|
编译器 数据安全/隐私保护 C++
【C++面向对象——继承与派生】派生类的应用(头歌实践教学平台习题)【合集】
本实验旨在学习类的继承关系、不同继承方式下的访问控制及利用虚基类解决二义性问题。主要内容包括: 1. **类的继承关系基础概念**:介绍继承的定义及声明派生类的语法。 2. **不同继承方式下对基类成员的访问控制**:详细说明`public`、`private`和`protected`继承方式对基类成员的访问权限影响。 3. **利用虚基类解决二义性问题**:解释多继承中可能出现的二义性及其解决方案——虚基类。 实验任务要求从`people`类派生出`student`、`teacher`、`graduate`和`TA`类,添加特定属性并测试这些类的功能。最终通过创建教师和助教实例,验证代码
183 5
|
12月前
|
存储 并行计算 安全
C++多线程应用
【10月更文挑战第29天】C++ 中的多线程应用广泛,常见场景包括并行计算、网络编程中的并发服务器和图形用户界面(GUI)应用。通过多线程可以显著提升计算速度和响应能力。示例代码展示了如何使用 `pthread` 库创建和管理线程。注意事项包括数据同步与互斥、线程间通信和线程安全的类设计,以确保程序的正确性和稳定性。
226 5
|
8月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
4月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
94 0
|
4月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
170 0
|
6月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
183 12
|
7月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
131 16