一个游戏中的条件概率问题
假设你在进行一个游戏节目。现给三扇门供你选择:其中一扇门后面是一个大奖(比如奥迪R8),另两扇门后面神马都没有。你不是托,所以你的目的当然是拿大奖,但是你显然不知道门后面是啥东东。主持人(虽然ta知道门后面都是啥,但ta就是不告诉你)先让你做第一次选择。在你选择了一扇门后,主持人并没有立刻打开这扇门,而是打开了另外一扇木有奖的门给你看。现在,主持人告诉你,你有一次重新选择的机会。那么,请你思考一下,你是坚持第一次的选择不变,还是改变第一次的选择?哪种做法更有可能中大奖?
这个问题貌似很简单:主持人排除一扇没奖的门之前做选择是三选一,得奖的概率是1/3,而排除之后是二选一,得奖的概率是1/2,显然1/2 > 1/3,所以换了得奖的机会更大。
但其实这个算法是不完全正确的。坚持第一次的选择不变,获奖的概率的确是1/3;但是改变第一次的选择,获奖的概率不是1/2,而是2/3!
你信么?
不信的同学不妨先看看穷举法得到的结果:
A B C
Y N N
1 2 X
2 1 X
2 X 1
注释:A,B,C代表三扇不同的门;Y表示此门有大奖,N表示此门木有奖;1表示第一次的选择,2表示第二次的选择;X表示这扇门被主持人排除而不能把戏演到底。
若是坚持第一次的选择不变,那么就只看带有1的部分:在三个1里面,跟Y一列的(即中奖的)只有一个,因此获奖概率是1/3。
若是改变第一次的选择,那么就只看带有2的部分:在三个2里面,跟Y一列的有两个,因此获奖的概率是2/3,而不是1/2。
这个穷举法有个条件:里面的1不能出现在相同的行(这种情况违反逻辑),也最好不要出现在相同的列,这样才能保证每一个1的概率都相等,不然查个数算概率的基础就错了。
实际上,这是一个“条件概率”的计算。你先任意选择一扇门,每扇门被选中的概率都是1/3,然后基于你的选择这个前提条件,主持人再选择一扇门。倘若你第一次选了一扇没有奖的门,那么,主持人选择一扇没有奖的门的条件概率是1,联合概率是1/3;倘若你第一次选了一扇有奖的门,那么主持人选择一扇没有奖的门的条件概率是1/2,联合概率是1/3*1/2=1/6。所以,不换的中奖概率是1/6+1/6=1/3,换的中奖概率是1/3+1/3=2/3 。
喂神马会是这样?喂神马是2/3而不是1/2?因为除了主持人排除了一扇门这个明显的限制条件之外,还有一个隐含的限制条件:改变第一次的选择。就是说,只要你改变第一次的选择,那么你就不是在剩下的“两”扇门中间做选择,你只有唯一的一扇门可选了。这个时候,你更像是跟之前的自己作对:如果之前你的选择有1/3的概率中奖,那么此时改变选择之后,你就有1/3的概率与大奖擦肩而过;如果之前你的选择有2/3的概率落空,那么此时改变选择之后,你就有2/3的概率把大奖抱回家。(看出来这两句话是一句话的同学,恭喜你!你可以去参加这类游戏了!)
说到这里,不论你明没明白,我都要接着说。没准你再往下看一点就明白了。
如果把三扇门扩大到四扇门会是神马情况?
A B C D
Y N N N
1 2 X 2
2 1 X 2
2 X 1 2
2 2 X 1
用上述数数的方法可知,坚持第一次选择不变的获奖概率是1/4,而改变第一次的选择的获奖概率是3/8,不是1/3=3/9(喂神马这么写?且看下文**处)。很明显,主持人排除一扇门,并且你改变先前的选择之后,你只有(4-1-1)扇可以真正自由选择的门(而不是3扇),它们的概率相等,都是1/(4-1-1)=1/2。若想获奖,则你的第一次选择必须是错误的、没奖的,这个概率为(1-1/4)=3/4。因此,改变第一次的选择并且获奖的概率是(3/4)*(1/2)=3/8。
若继续往下推算,五扇门的情况是:不改变选择的获奖概率是1/5,改变的获奖概率是(1-1/5)*{1/(5-1-1)}=4/15,而不是1/4=4/16。
六扇门的情况是……你自己去问郭芙蓉她爹郭巨侠吧!
另外,你不用担心到27扇门的时候没有英文字母来代表门(注意,我没提代表们),因为当你研究到14扇门的时候,你会发现,原来这就是N扇门的情况啊!这对于试图用上述穷举法继续往下研究的同学来说真是个福音啊!
用数学归纳法可证得(不需要其它的高深数学技能,有性趣的同学可用手自行解决),N扇门的情况是,坚持不变的中奖概率是1/N,改变的中奖概率是(1-1/N)*{1/(N-1-1)}=(N-1)/{N*(N-2)}。其中,N是大于等于3的正整数。(**处)这里另外的一种归纳是:(N-1)/{(N-1)^2-1)}=(N-1)/{N*(N-2)}。
显然,(N-1)/(N-2)>1,所以改变第一次的选择而中奖的概率大于坚持第一次选择的中奖概率。因此,以后你参加这种犯贱的游戏时,原则就是一定要换!
说到这里,如果你还没明白,请回到六扇门那儿重新来过。
把上面的问题再推广一点,我们可以得到一个更平凡的结论:
给你N扇门,其中m个是有奖的,其余没奖,你先选一扇,然后主持人打开另外L扇没有奖的给你看,再让你重新选一扇门。此时,坚持不换门而获奖的概率是m/N;换另外一扇门而获奖的概率是{m*(N-m)}/{N*(N-L-1)}。其中,N,m,L均为正整数,且N>2,(m+L)<N。
怎么得到的?可以这样理解:显然第一次选择的获奖概率是m/N,那么改变第一次的选择意味着抛弃了这m*(m/N)个大奖,还剩下{m-m*(m/N)}个大奖。你要在(N-L-1)个剩下的门中选一个,这时的中奖概率自然是{m-m*(m/N)}/(N-L-1)={m*(N-m)}/{N*(N-L-1)}。
这种情况下,究竟怎样才能获奖就要看(L+1)和m谁大了。当L+1比m大时,改变先前选择而获奖的概率更大;反之,当L+1比m小时,坚持原来的选择中奖概率更高;若L+1=m,则换不换无所谓,概率是相等的。
最后,我再说一点(你能坚持到这里表明你真的很理性),这里还可以再引申出一个更更平凡的推论,即第一次可以选择k扇门,而第二次可以选择j扇门,其中k+j+L<N。
则坚持第一次选择不变的中奖概率是:
1-(1-m/N)^k
而改变第一次选择的中奖概率是:
1-{1-m*z/(N-L-k)}^j,其中,z=(1-m/N)^k
这个时候到底是换还是不换?who特么knows!
好了说了这么多了,喝杯水捋一捋思路,这不口说无凭,要不上个代码测试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | #include <stdio.h> #include <time.h> #include <stdlib.h> / / 有三扇门,其中一扇门里有奖品,三选一,你选择其中一扇门之后 / / 主持人先不揭晓答案,而是从另外两扇门中排除掉一个没有奖品的门 / / 现在主持人问你,要不要换个门,请问你换还是不换? 9 / / 测试(总次数, 换门) void test( int max , int change); / / 入口函数 void main() { / / 各自测试的次数 int count = 20 ; / / 初始化随机种子 srand((unsigned)time(NULL)); / / 不换门测试 printf( "不换门测试:\n" ); test(count, 0 ); / / 换门测试 printf( "换门测试:\n" ); test(count, 1 ); } / / 测试(总次数, 换门) void test( int max , int change) { / / max 次数:测试的总次数 / / change 换门: 0 不换 1 换门 int i; / / 循环因子 int m; / / 目标:本次中奖的目标数字 int c; / / 初选:本次选手初次选择的数字 int x; / / 选择:本次选手最终选择的数字 int p; / / 排除:主持人排除掉没奖的数字 int z = 0 ; / / 中奖:中奖的总次数 / / 循环多次模拟换门测试 for (i = 0 ; i< max ; i + + ) { m = rand() % 3 ; / / 目标:本次中奖的目标数字 c = rand() % 3 ; / / 初选:本次选手初次选择的数字 / / 求出主持人要排除的数字 if (m = = c) { / / 选手选择了一个有奖品的,主持人从另外两个没奖品当中随机排除掉一下 p = rand() % 2 ; / / 产生 false or true switch(c) { case 0 : / / 要排除的是: 2 or 1 p = p? 2 : 1 ; break ; case 1 : / / 要排除的是: 2 or 0 p = p? 2 : 0 ; break ; case 2 : / / 要排除的是: 1 or 0 p = p? 1 : 0 ; break ;; } } else { / / 选手选择了一个没奖品的,主持人排除另一个没奖品的 / / 3 - (m + c) = p / / 3 - ( 0 + 1 ) = 2 / / 3 - ( 0 + 2 ) = 1 / / 3 - ( 1 + 2 ) = 0 p = 3 - (m + c); } / / 决定终选 if (change) { / / 换门 / / x = 3 - (p + c) / / x = 3 - ( 0 + 1 ) = 2 / / x = 3 - ( 0 + 2 ) = 1 / / x = 3 - ( 1 + 2 ) = 0 x = 3 - (p + c); / / 换个门 } else { / / 不换门 x = c; / / 最终选择和初次选择一样 } / / 结果 printf( "第%02d次 初选的是:%d, 目标是:%d, 排除的是:%d, 终选的是:%d" , i + 1 , c, m, p, x); if (m = = x) { / / 中奖了 z + + ; printf( " (中奖了)\n" ); } else { / / 没中奖 printf( "\n" ); } } / / 输出结果 printf( "进行%d次测试,中奖%d次。\n\n" , max , z); } |
另外,当需要进行1000次测试时,就不适合将每一次测试的结果都进行输出了,代码稍微改一下,将C语言的代码发给大家看看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | #include <stdio.h> #include <time.h> #include <stdlib.h> / / 有三扇门,其中一扇门里有奖品,三选一,你选择其中一扇门之后 / / 主持人先不揭晓答案,而是从另外两扇门中排除掉一个没有奖品的门 / / 现在主持人问你,要不要换个门,请问你换还是不换? / / / / 测试(总次数, 换门) void test( int max , int change); / / 入口函数 void main() { / / 各自测试的次数 int i, count = 1000 ; / / 初始化随机种子 srand((unsigned)time(NULL)); for (i = 0 ;i< 5 ;i + + ) { printf( "================================\n" ); / / 不换门测试 printf( "不换门测试:\n" ); test(count, 0 ); / / 换门测试 printf( "换门测试:\n" ); test(count, 1 ); } } / / 测试(总次数, 换门) void test( int max , int change) { / / max 次数:测试的总次数 / / change 换门: 0 不换 1 换门 int i; / / 循环因子 int m; / / 目标:本次中奖的目标数字 int c; / / 初选:本次选手初次选择的数字 int x; / / 选择:本次选手最终选择的数字 int p; / / 排除:主持人排除掉没奖的数字 int z = 0 ; / / 中奖:中奖的总次数 / / 循环多次模拟换门测试 for (i = 0 ; i< max ; i + + ) { m = rand() % 3 ; / / 目标:本次中奖的目标数字 c = rand() % 3 ; / / 初选:本次选手初次选择的数字 / / 求出主持人要排除的数字 if (m = = c) { / / 选手选择了一个有奖品的,主持人从另外两个没奖品当中随机排除掉一下 p = rand() % 2 ; / / 产生 false or true switch(c) { case 0 : / / 要排除的是: 2 or 1 p = p? 2 : 1 ; break ; case 1 : / / 要排除的是: 2 or 0 p = p? 2 : 0 ; break ; case 2 : / / 要排除的是: 1 or 0 p = p? 1 : 0 ; break ;; } } else { / / 选手选择了一个没奖品的,主持人排除另一个没奖品的 / / 3 - (m + c) = p / / 3 - ( 0 + 1 ) = 2 / / 3 - ( 0 + 2 ) = 1 / / 3 - ( 1 + 2 ) = 0 p = 3 - (m + c); } / / 决定终选 if (change) { / / 换门 / / x = 3 - (p + c) / / x = 3 - ( 0 + 1 ) = 2 / / x = 3 - ( 0 + 2 ) = 1 / / x = 3 - ( 1 + 2 ) = 0 x = 3 - (p + c); / / 换个门 } else { / / 不换门 x = c; / / 最终选择和初次选择一样 } / / 结果 / / printf( "第%02d次 初选的是:%d, 目标是:%d, 排除的是:%d, 终选的是:%d" , i + 1 , c, m, p, x); if (m = = x) { / / 中奖了 z + + ; / / printf( " (中奖了)\n" ); } else { / / 没中奖 / / printf( "\n" ); } } / / 输出结果 printf( "进行%d次测试,中奖%d次。\n\n" , max , z); } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?