使用 C 语言验证非均匀概率的离散事件在样本数量足够大时,符合正态分布曲线(通过生成一个PPM格式的图像)

news2024/11/26 18:49:09

我想写本文的原因是看到著名数学科普账号 3Blue1Brown 发布的【官方双语】但是什么是中心极限定理?中提到:不论这个离散型事件的各种情况概率是不是平均的,当数量一定大时,还是会符合正态分布曲线。我就想自己试试看是不是这种情况,因为我觉得中心极限定理和正态分布是一个概率论中很神奇的一部分。

本文使用骰子点数当作离散型事件,求点数之和的概率。先实现均匀分布的状态下的程序,再调整为不均匀分布的概率,完整源代码放在最后,防止因为头文件等问题导致错误。

均匀分布下,点数之和的概率

首先,新建一个数组来存放骰子的点数,如下:

int a[] = {1,2,3,4,5,6};

生成图像使用下面的函数writePPMImage

void writePPMImage(int* data, int width, int height, const char *filename, int maxIterations)
{
    FILE *fp = fopen(filename, "wb");

    // write ppm header
    fprintf(fp, "P6\n");
    fprintf(fp, "%d %d\n", width, height);
    fprintf(fp, "255\n");

    for (int i = 0; i < width*height; ++i) {
        float mapped = pow( std::min(static_cast<float>(maxIterations), static_cast<float>(data[i])) / 256.f, .5f);
        unsigned char result = static_cast<unsigned char>(255.f * mapped);
        for (int j = 0; j < 3; ++j)
            fputc(result, fp);
    }
    fclose(fp);
    printf("Wrote image file %s\n", filename);
}

这个函数的参数:

  1. data是一个数组,其中每个元素对应位图的每个像素颜色信息(Z 字排列),也就是说,一个元素(或者说就是像素)对应点数之和中,有一次这个值
  2. widthheight是生成位图的尺寸。
  3. filename是生成的位图文件。
  4. maxIterations是颜色最大值,也就是白色对应的值,这里我们将其设置为256,因为代码中是当作 8 通道色彩。我们只需要黑白,所以也可以更简洁,直接写成1,然后只用01两个整数值表示黑白即可。

下面直接写出代码,每一步的介绍请看注释:

int main() {
	//设置图片尺寸为1450x1000
    int width = 1450;
    int height = 1000;
    
    //待会需要随机从中选择一个元素,当作骰子的点数
    int a[] = {1,2,3,4,5,6};
    
    //用来存放各种点数之和的数量多数组,这里不要声明空数组,因为一些编译器会给没有值的元素分配一些很奇怪的值,导致运行错误(不像C语言是默认为0)
    int* sumArr = new int[width];
    //用来存放最后输出图像的像素色彩信息的数组
    int* output = new int[width*height];
	
	//样本量为30x1000=30000,也就是取3万次点数之和
    for (int i=0; i<height*30; i++) {
    	//获取到一个随机点数。模6表示随机值范围是0~5,刚好对应前面数组a的每个元素
        int temp = a[random()%6];
        //下面的循环将会累加100次,也就是表示多少个骰子点数之和
        for (int j=0; j<100; j++) {
            temp = temp + a[random()%6];
	        }
	        //给这个值对应的sumArr的元素加1
	        sumArr[temp] = sumArr[temp]+1;
	    }
	    
	    //因为输出图像的时候,条状图是从底部开始的,所以写这样的一个转换
	    for (int i=0; i<width; i++) {
	        for (int j=height-1; j>=height-sumArr[i]; j--) {
	            output[j*width+i]=256;
	        }
	    }
		//输出图像
	    writePPMImage(output, width, height, "output.ppm", 256);
	
	    delete[] sumArr;
	    delete[] output;
	    return 0;
	}

生成的 3 万个样本对应的图像如下:

请添加图片描述

就很近似正态分布曲线了,但是这样太尖了,为了更明显一些,我们来将其“拉宽压扁”。方法是将第二个大的for循环修改成如下:

for (int i=0; i<width; i++) {
        // sumArr[i]/2是为了压缩图像
        for (int j=height-1; j>=height-sumArr[i]/2; j--) {
            //拉宽图像
            for (int k=0; k<10; k++) {
                output[j*width+i*10+k]=256;
        }
    }
}

也就是变成 2x10 个像素表示一个样本,下面全都是按照这种缩放来展示图像。这时候图像如下:

请添加图片描述

这时候和很像标准正态分布的图像了。如果你想真的实现标准正态分布的图像,那就加上计算方差和样本均值的部分,多几步就可以了。

非均匀分布下,点数之和的概率

接下来我们来尝试一下非均匀分布的概率的图像。这个一开始难到我了,我不知道如何让每个值的概率不同,但是很快就反应过来了,这不就是箱子(数组)里抓小球(元素)嘛,那修改一下数组的元素数量和值即可,所以这时候样本空间的数组为:

int a[] = {1,1,1,1,1,2,3,4,5,6};

1有五个,也就是说1的概率为 0.5 ,其余值均为 0.1。

这时候也需要修改一下源代码,不仅是因为元素数量变了,随机值范围也得变,更是要考虑到多种测试的情况,要将其写的通用一些,所以修改成以下样式:

int main() {
    int width = 1700;
    int height = 1000;
    int a[] = {1,1,1,1,1,2,3,4,5,6};
    //count用来统计样本空间的大小,这样就不用手动去下面依次修改了
    int count = sizeof(a)/sizeof(int);
    
    
    int* sumArr = new int[width];
    int* output = new int[width*height];

    for (int i=0; i<height*30; i++) {
        int temp = a[random()%count];
        for (int j=0; j<100; j++) {
            temp = temp + a[random()%count];
        }
        sumArr[temp] = sumArr[temp]+1;
    }
    
    for (int i=0; i<width; i++) {
        // sumArr[i]/2是为了压缩图像
        for (int j=height-1; j>=height-sumArr[i]/2; j--) {
            //拉宽图像
            for (int k=0; k<10; k++) {
                output[j*width+i*10+k]=256;
            }
        }
    }

    writePPMImage(output, width, height, "output.ppm", 256);

    delete[] sumArr;
    delete[] output;
    return 0;
}

这时候生成的图像如下:

请添加图片描述

可以看到,还是符合正态分布曲线的,并没有因为1的概率很大就导致图像发生变化。

那再极限一些呢?如果1的概率高达 99% 呢?

遗憾的是,要使1的概率高达 99%,需要样本空间数组有 500 个元素,这会导致一些资源分配错误,就试试看1的概率为 95% 的情况,那么这个数组如下(这里列出这个数组是为了读者方便可以复制下来自己试试看):

int a[] = {
       	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,    //40个
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,    //40个
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,    //40个
        2,3,4,5,6,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,                          //15个1
    };

这时候这个图像如下:

请添加图片描述

可以看到,最小值1+1=2的样本最多,但是右边还是比较像正态分布的一半的,那如果加大累加次数呢?比如说从 100 次提升到 1000 次(样本数量降低到 1 万次),这时候的图像如下:

请添加图片描述

由于可能性太多,所以这里的图像尺寸为 17000x1000px,有点看不清,所以我裁了图像的部分出来:

裁切出来的图像

可以看到,最终还是符合正态分布曲线的,这也正是中心极限定理。

完整代码

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <algorithm>

using namespace std;

void
writePPMImage(int* data, int width, int height, const char *filename, int maxIterations)
{
    FILE *fp = fopen(filename, "wb");

    // write ppm header
    fprintf(fp, "P6\n");
    fprintf(fp, "%d %d\n", width, height);
    fprintf(fp, "255\n");

    for (int i = 0; i < width*height; ++i) {
        float mapped = pow( std::min(static_cast<float>(maxIterations), static_cast<float>(data[i])) / 256.f, .5f);
        unsigned char result = static_cast<unsigned char>(255.f * mapped);
        for (int j = 0; j < 3; ++j)
            fputc(result, fp);
    }
    fclose(fp);
    printf("Wrote image file %s\n", filename);
}

int main() {
	//输出图像的尺寸
	//图像会随着累加次数右移,所以增加累加次数的时候要把输出图像的宽度扩大一些
    int width = 17000;
    int height = 1000;
    int a[] = {
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,    //40个
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,    //40个
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,    //40个
        2,3,4,5,6,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,                          //15个
    };
    //count用来统计样本空间的大小,这样就不用手动去下面依次修改了
    int count = sizeof(a)/sizeof(int);
    
    //用来存放各种点数之和的数量多数组,这里不要声明空数组,因为一些编译器会给没有值的元素分配一些很奇怪的值,导致运行错误(不像C语言是默认为0)
    int* sumArr = new int[width];
    //用来存放最后输出图像的像素色彩信息的数组
    int* output = new int[width*height];
    
	//样本量为10x1000=10000,也就是取1万次点数之和
    for (int i=0; i<height*10; i++) {
    	//获取到一个随机点数。模6表示随机值范围是0~count,刚好对应前面数组a的每个元素
        int temp = a[random()%count];
        //下面的循环将会累加1000次,也就是表示多少个骰子点数之和
        for (int j=0; j<1000; j++) {
            temp = temp + a[random()%count];
        }
        sumArr[temp] = sumArr[temp]+1;
    }
    
    //因为输出图像的时候,条状图是从底部开始的,所以写这样的一个转换
    for (int i=0; i<width; i++) {
        // sumArr[i]/2是为了压缩图像
        for (int j=height-1; j>=height-sumArr[i]/2; j--) {
            //拉宽图像
            for (int k=0; k<10; k++) {
                output[j*width+i*10+k]=256;
            }
        }
    }

	//输出图像
    writePPMImage(output, width, height, "mandelbrot-serial.ppm", 256);

    delete[] sumArr;
    delete[] output;
    return 0;
}

蛮有意思的,希望能帮到有需要的人~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/533828.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入篇【C++】类与对象:const成员与Static成员

深入篇【C】类与对象&#xff1a;const成员与Static成员 ⏰<const成员>&#x1f553;1.权限&#x1f550;2.规则&#x1f552;3.思考&#xff1a; ⏰<Static成员>&#x1f551;1.概念&#x1f557;2.特性&#x1f555;3.思考&#xff1a; ⏰<const成员> &am…

从零开始 Spring Boot 29:类型转换

从零开始 Spring Boot 29&#xff1a;类型转换 图源&#xff1a;简书 (jianshu.com) PropertyEditor Spring使用PropertyEditor进行String和具体类型之间的转换&#xff1a; public interface PropertyEditor {void setValue(Object value);Object getValue();String getAsT…

第五章 面向对象-7.hashCode()和toString()

hashCode()和toString() hashCode() hashCoed 的特性&#xff1a; &#xff08;1&#xff09;HashCode的存在主要是用于查找的快捷性&#xff0c;如Hashtable&#xff0c;HashMap等&#xff0c;HashCode经常用于确定对象的存储地址&#xff1b; &#xff08;2&#xff09;如果…

华为OD机试真题 Java 实现【统一限载货物数最小值】【2023Q1 200分】

一、题目描述 火车站附近的货物中转站负责将到站货物运往仓库&#xff0c;小明在中转站负责调度 2K 辆中转车(K辆干货中转车&#xff0c;K 辆湿货中转车)货物由不同供货商从各地发来&#xff0c;各地的货物是依次进站&#xff0c;然后小明按照卸货顺序依次装货到中转车&#x…

智能床垫市场调研分析报告

文章目录 一、简介&#xff08;1&#xff09;电动床&#xff08;2&#xff09;气垫床 二、使用人群三、睡姿四、实用性 一、简介 &#xff08;1&#xff09;电动床 电动床之下又分成了分体、连体和床头分体。分体电动床是指床垫与床底座分开的电动床&#xff1b;连体的则是床垫…

数据结构-外部排序-(多路归并排序、败者树、置换选择排序、最佳归并树)

目录 一、外部归并排序 二、败者树 三、置换选择排序 四、最佳归并树 一、外部归并排序 16个块&#xff0c;先每个块读入内存进行排序在输出回来&#xff0c;进行16次读和16次写 两两归并&#xff0c;第一趟如下 在两两归并 时间分析 外部排序时间开销读写外存时间内存排序时…

C语言基础知识:函数的声明和使用

目录 函数的声明 1.定义顺序 2.函数的声明 3.函数的声明格式 多源文件开发 1.为什么要有多个源文件 2.将sum函数写到其他源文件中 3.在main函数中调用sum函数 4.编译所有的源文件 5.链接所有的目标文件 #include 1.#include的作用 2.#include可以使用绝对路径 3.#…

Linux免交互操作

免交互操作 Here DocumentExpect工具 Here Document Here Document概述 使用I/O重定向的方式将命令列表提供给交互式程序或命令&#xff0c;比如 ftp 、cat 或 read 命令。Here Document 是标准输入的一种替代品&#xff0c;可以帮助脚本开发人员不必使用临时文件来构建输入信息…

docker搭建Elasticsearch集群

这里写目录标题 1.拉取es镜像2.配置配置文件3.启动容器4.启动过程中遇到的问题5.查看容器启动情况 1.拉取es镜像 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.17.0版本根据自己需求进行拉取&#xff0c;我这边选择的是7.17.0&#xff0c;不同版本配置可能稍有…

ANR原理篇 - Input超时机制

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 文章目录 系列文章目录前言一、事件分发流程1.1 事件分发流程概览1.2 InputDispatcher 三、ANR触发流程超时重…

ANR原理篇 - service/broadcast/provider超时机制

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 文章目录 系列文章目录前言一、Service超时机制1.1 埋炸弹1.1.1 AS.realStartServiceLocked1.1.2 AS.bumpSer…

三大基础排序算法——我欲修仙(功法篇)

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️我欲修仙】 学习名言&#xff1a;莫等闲、白了少年头&#xff0c;空悲切。——岳飞 系列文章目录 第一章 ❤️ 学习前的必知知识 第二章 ❤️ 二分查找 文章目录 系列文章目录前言&#x1f697;&…

Netty实战(三)

Netty的组件和设计 一、Channel、EventLoop 和 ChannelFuture1.1 Channel 接口1.2 EventLoop 接口1.3 ChannelFuture 接口 二、ChannelHandler 和 ChannelPipeline2.1 ChannelHandler 接口2.2 ChannelPipeline 接口2.3 编码器和解码器2.4 抽象类 SimpleChannelInboundHandler 三…

suricata中DPDK收发包源码分析2

《suricata中DPDK收发包源码分析1》中分析了整体的DPDK收发包框架代码&#xff0c;今天我们继续来深入了解一下一些细节方面的问题。 目录 Q1&#xff1a;收发包线程模式在代码中是怎样确定的&#xff1f; Q2: DPDK库的初始化rte_eal_init在哪里调用的&#xff1f; Q3: 对网…

Linux中LV Status的状态为NOT available

今天下午有现场反馈备份磁盘找不到了&#xff0c;使用lvm方式的。提供了todesk帮忙看下&#xff0c; 首先使用 blkid查看&#xff0c;确实看不到备份磁盘的UUID&#xff0c;使用lvdisplay查看状态&#xff0c;状态不对了 [rootdb1 ~]# lvdisplay --- Logical volume --- …

.Vue3项目初始化

文章目录 1.Vue3项目初始化1.1 创建vue项目1.2 vue 初始化1.3 git 项目管理1.4 配置iconfig.json1.5 element 按需引入1.6 element 主题色的定制1.7 axios的基础配置1.8 router路由的配置 1.Vue3项目初始化 1.1 创建vue项目 npm init vuelatest1.2 vue 初始化 npm install1.…

【2023/05/16】MonteCarlo

Hello&#xff01;大家好&#xff0c;我是霜淮子&#xff0c;2023倒计时第11天。 Share O Beauty,find theyself in love,not in the flattery of thymirror. 译文&#xff1a; 啊&#xff0c;美啊&#xff0c;在爱中找你自己吧&#xff0c;不要到你镜子的诌谀中去寻找。 M…

[遗传学]转座因子的结构与功能

本篇文章主要带你了解:转座因子的发现和分类;原核生物以及真核生物种的转座子;转座作用的分子机制以及转座因子的遗传学效应和应用. &#x1f9ec;转座因子的发现和分类 &#x1f9ec;转座因子的概念 转座因子(transposable element)是在转座酶&#xff08;transposase&#xf…

Class 03 - R语言的 Vectors(向量) 与 lists(列表)

Class 03 - R语言的 Vector与 列表 list R语言语法脚本文件的创建、保存、和修改名称第一个函数使用帮助功能查看函数详细说明语法问题变量与赋值定义变量名称格式调用变量 R中的数据结构Vectors (向量)创建向量查看向量的性质查看数据类型 typeof()查看数据长度 length()检查…

Elasticsearch 核心技术(十):GEO 地理查询(geo_bounding_box、geo_distance、geo_shape)

❤️ 博客主页&#xff1a;水滴技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; &#x1f338; 订阅专栏&#xff1a;大数据核心技术从入门到精通 文章目录 一、地理数据类型1.1、geo_point 地理点类型1.1.1、创建一个含有 geo_point 字…