蒙特卡洛模拟求解概率统计中的抽奖问题C语言实现

Posted 进击的alphaCat

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了蒙特卡洛模拟求解概率统计中的抽奖问题C语言实现相关的知识,希望对你有一定的参考价值。

以下是我的正文

0背景重述

这天我在上大物的时候,坐在我右边的两位室友不停地讨论着什么,我的右耳不时地传来“羊”啊“车”啊的词语。由于我的右耳主要负责捕捉大物老师致睡的声音,左耳朵塞着音乐以堵住知识溜走的去路。人只生有两耳,我便没能听明白室友们到底在讨论啥。于是下课后,我一问,哦,原来是这样……

1问题

室友们在讨论一个概率统计的问题。说一个抽奖活动,参赛者站在三扇门面前,每扇门背后可能是一只羊或是一辆特斯拉(这车现在还有人敢要吗)。羊共有两只,车只有一辆。
参赛者在选择一扇门后(不打开),会有一个人告诉他另外两扇门中有羊的那扇,那么问题来了,参赛者该如何进行第二次选择呢?

2初始的思路

我概率统计没咋学过,对这个问题一开始是这么理解的。一开始选择,什么都不知道,那么选中车的概率就应该是
P 特 斯 拉 = 1 3 P_{特斯拉}=\\frac{1}{3} P=31
然后告诉了某扇门后面有只羊,如图选择
那么现在只剩两扇门了,每扇门背后有车的概率岂不是瞬间变成了
P 特 斯 拉 = 1 2 . P_{特斯拉}=\\frac{1}{2}. P=21.
那我更换选择与否,有用吗?不都是面对当前情形二分之一概率的博弈吗?
事实上,这也正是我其中一位室友的思路,在食堂三楼尊贵的教师餐厅(这里谈笑有鸿儒,往来有杰青)吃饭时,他甚至笃信地对另一位认为更换门后概率变为三分之二的室友说:“绝对是二分之一,如果不是,我给你买金蝉子的皮肤。
然而真相呢?如何验证呢?另一位室友急了,“等我晚上回去写程序跑跑看。”(下午他们满课)而热爱C语言编程的我当即拍桌,“我回去就去用大数定理模拟一下。”(我下午34才有课)。

3编程验证

3.1算法思路

这种概率问题,我想到的是用蒙特卡洛模拟,据我的理解,就是依据大数定理,对一个实验重复多次后,某一事件的频率即为其概率。
我的算法思路如下:

Created with Raphaël 2.2.0 模拟开始 初始化随机数组 随机选择一扇门 告知一只🐏的位置 第二次有向选择 两种选择的命中次数累加 次数小于TIMES? 模拟结束 yes no

3.2代码展示

记得先把每个方法看懂,方法命名很好懂的

// 特斯拉与羊
#include<stdio.h>
#include<stdlib.h>
#include<time.h> 
int arr[3];
int FirCatchCount=0;	/*第一次猜中次数*/ 
int AfterChangeCatchCount=0;/*更换后猜中次数*/ 
void InitArray(){ 	// 初始化随机数组
	int carIndex = rand()%3; 
	arr[carIndex]=1; // Tesla
	for (int i=0; i<3; i++){
		if (i==carIndex) continue;
		arr[i]=0; // Sheep
	}
	 
	return;
}

int MyFirstChoose(){	
	int choose = rand()%3;
	return choose;  
}

int FindSheep(int yourChoose){	
	for (int i=0;i<3;++i){
		if (i == yourChoose) continue;
		if (!arr[i]) return i;
	}
}

int MySecondChoose(int firChoose,int sheepIndex){
	for (int i=0;i<3;++i){
		if (i==firChoose||i==sheepIndex) continue;
		return i;
	}
}

void CalculateCount(int firChoose,int secdChoose){
	// 特斯拉标记为1,羊标记为0
	// 所以统计频率时只需要累加数组的值即可
	FirCatchCount+=arr[firChoose];		
	AfterChangeCatchCount+=arr[secdChoose];	
	return;
}

                      /*----程序入口----*/
int main(){
	int TIMES=50000;
	int cnt=0;
	printf("准备开始模拟\\n\\n");
	system("pause"); 
	
		/*蒙塔卡罗模拟*/
	srand((unsigned)time(NULL));	// 播下种子,本段代码最重要的一句,实现真正生成连续随机数 
	do {
		InitArray(); // 初始化随机数组
		int firChoose = MyFirstChoose(); // 进行第一次随机选择 
		int sheepIndex = FindSheep(firChoose); // 告知一只羊的位置 
		int secdChoose = MySecondChoose(firChoose,sheepIndex); // 第二次选择(更换) 
		CalculateCount(firChoose,secdChoose); // 统计次数 
	} while (++cnt<TIMES);
	
	printf("%d次模拟结束。\\n\\n",TIMES);
	system("pause");
	
	printf("坚持第一次选择,选中特斯拉的概率为%lf\\n\\n",FirCatchCount*1.0/TIMES); 
	printf("改变初始选择,选中特斯拉的概率为%lf\\n\\n",AfterChangeCatchCount*1.0/TIMES);
	
	return 0;
} 

我自己在写代码时,主要思考的是如何准确地模拟人做选择、以及上帝视角选羊的过程;但后来发现,如何生成连续的随机数才是更关键的!关于随机数、伪随机数,这里不加赘述,但给一个指路牌,搜索“C语言如何生成连续的随机数”,就可以了解随机数背后的各种机制啦。

3.3运行结果

运行结果

4结果分析

在这里插入图片描述
虽然我不知道发生了什么,但是,从结果来看,更换选择后,中奖的概率是坚持第一次选择的两倍!当时把运行结果发到宿舍的微信群里,正在上课的室友估计也露出了上面的表情……

但是用模拟的结果反推机理,思路竟是那样的清晰。

我的顿悟
第一次时啥也不知道,盲选中特斯拉的概率是 1 3 \\frac{1}{3} 31,这个不会有意见。
然后上帝告诉我们在两扇未选之门中,一扇藏有羊的门的位置,那么我们的选择已经不再是 3 3 3 个,而是 2 2 2 个了。但是,我们第一次选择的那扇门,中奖的概率不会改变,因为它最初是我们用 1 3 \\frac{1}{3} 31 的命中率进行博弈的产物。于是,结果很显然了,更换选择后,有
P = 1 − 1 3 = 2 3 . P=1-\\frac{1}{3} =\\frac{2}{3}. P=131=32.
这类问题的核心就是,无论新增了什么条件(告诉你哪扇门后面是羊啊),我们第一次选择的概率始终不会改变,这会成为一个固有属性一直伴随着它。

这就好比当初在万人之中看上了一个女孩,即使有一天,这个世界上只剩下了她和另一个女孩,她仍是万里挑一的她,而不是被二分之一概率选中的她。(??什么骚话??)

5后话

紧张的思考过后,随着答案的水落石出,我长舒了一口气,关掉了电脑,走出了宿舍,去教学区上课。下沙校区晴天的下午,阳光还没有灼热到使人有打伞的欲望。微笑地走在路上,溶解于来来往往的学生之中。我一边享受着自己用编程解决问题所带来的乐趣,一边看着微信群里室友的消息。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

致谢

感谢ZX、XL在大物课上的讨论,否则就不会有这篇文章
感谢TY给的解题的思路
感谢XL在编程思路上给我的启发

感谢你看到这里!如果觉得还有点意思的话,那我就更开心啦哈哈哈

以上是关于蒙特卡洛模拟求解概率统计中的抽奖问题C语言实现的主要内容,如果未能解决你的问题,请参考以下文章

蒙特卡罗方法

蒙特卡罗算法(Monte Carlo method)

数学模型:4. 蒙特卡罗模拟

蒙特卡罗方法(Monte Carlo method)

随机数生成算法详解,归纳

蒙特卡洛算法