写点什么

买彩票能中大奖?用 Java 盘点常见的概率悖论 | 京东云技术团队

  • 2023-09-12
    北京
  • 本文字数:3628 字

    阅读完需:约 12 分钟

买彩票能中大奖?用Java盘点常见的概率悖论 | 京东云技术团队

引言

《双色球头奖概率与被雷劈中的概率哪个高?》


《3 人轮流射击,枪法最差的反而更容易活下来?》


让我们用 Java 来探索 ta 们!

悖论 1:著名的三门问题

规则描述:你正在参加一个游戏节目,你被要求在三扇门中选择一扇:其中一扇后面有一辆车;其余两扇后面则是山羊。你选择了一道门,假设是一号门,然后知道门后面有什么的主持人,开启了另一扇后面有山羊的门,假设是三号门。他然后问你:“你想选择二号门吗?请问若想获得车,参赛者应该换二号门吗?



论证:分析需求,拆解为如下代码


/** * <p> 三门问题解决方案 </p> * @author yuanfeng.wang * @since 2023/8/29 */import java.util.Random;
public class ThreeDoorSolution {
public static void main(String[] args) { // 模拟执行1万次,打印获胜的概率 threeDoor(10000); }
/** * 三门问题逻辑拆解 * @param numSimulations 总共执行多少轮游戏 */ private static void threeDoor(int numSimulations) { int switchWins = 0; int stayWins = 0;
Random random = new Random(); for (int i = 0; i < numSimulations; i++) { // 随机确定车所在的门 int carDoor = random.nextInt(3);
// 玩家随机选择一扇门 int playerChoice = random.nextInt(3);
// 主持人随机打开一扇门:要求该门不是玩家选择的,且必须是羊 int openedDoor; do { openedDoor = random.nextInt(3); } while (openedDoor == carDoor || openedDoor == playerChoice);
// 换门后的选择:不能是打开的门,不能是玩家选择的门,则是交换之后的门 int finalChoice; do { finalChoice = random.nextInt(3); } while (finalChoice == playerChoice || finalChoice == openedDoor);
// 计算是否换门获胜 if (finalChoice == carDoor) { switchWins++; }
// 计算不换门获胜 if (playerChoice == carDoor) { stayWins++; } }
// 输出结果 System.out.println("在 " + numSimulations + " 次模拟中:"); System.out.println("换门获胜的概率:" + (double) switchWins / numSimulations); System.out.println("不换门获胜的概率:" + (double) stayWins / numSimulations); }}// 模拟运行,打印结果如下// 在 10000 次模拟中:// 换门获胜的概率:0.6679// 不换门获胜的概率:0.3321
复制代码


结论:三门问题看似一道简单的概率题,几十年来却一直引发巨大争议,持两种不同观点的人基本是五五开;事实上始终选择换门的玩家,获胜的概率 2/3,而保持原方案的胜率只有 1/3

悖论 2:双色球我能中大奖

规则描述:从 1-33 个红色球中随机选出 6 个,再从 1-16 个蓝色球中随机选择 1 个,最终开奖出一注 6+1 组合球,无顺序要求;


  • 一等奖:中 6 红 + 1 蓝

  • 二等奖:中 6 红

  • 三等奖:中 5 红 + 1 蓝

  • 四等奖:中 4 红 + 1 蓝,或只中 5 个红

  • 五等奖:中 3 红 + 1 蓝,或只中 4 个红

  • 六等奖:中 1 蓝


论证:分析玩法,计算一等奖中奖率,从 33 个红球样本中选择 6 个,计算总共的组合数,即数学公式 C(n, m) = n!/((n-m)! * m!),代入计算 C(33, 6) = 33!/((33-6)! * 6!) = 1107568,再乘以 16,最终得出一等奖获奖概率 1/17721088。


分析规则,以下代码展示了开奖一次,购买 N 注时,打印中奖信息的程序,当代入 N=500 万时,多次执行,可以很轻松打印出一等奖



import java.util.*;
/** * <p>双色球随机模拟</p> * @author yuanfeng.wang * @since 2023/8/29 */public class SsqSolution {
private static Random random = new Random();
/** * 开奖的红球 */ private static Set<Integer> winningRedBalls;
/** * 开奖的蓝球 */ private static int winningBlueBall;
// 静态块初始化一组开奖号码 static { // 篮球 01-16 winningBlueBall = random.nextInt(16) + 1;
// 红球 01-33生成6个 winningRedBalls = new HashSet<>(); while (winningRedBalls.size() < 6) { int num = random.nextInt(33) + 1; winningRedBalls.add(num); } }
public static void main(String[] args) { play(500_0000); }
/** * * @param num 运行一次程序只开一次奖,此参数表示总共购买多少注 */ public static void play(int num) { System.out.println("\n本期开奖号码:"); System.out.println("红球:" + winningRedBalls + " 篮球:" + winningBlueBall); for (int i = 0; i < num; i++) { playOnce(); } }
private static void playOnce() { Set<Integer> userRedBalls = getUserSelectedRedBalls(); int userBlueBall = getUserSelectedBlueBall();
int redBallMatch = countMatchingBalls(userRedBalls, winningRedBalls); boolean blueBallMatch = (userBlueBall == winningBlueBall);
if (redBallMatch == 6 && blueBallMatch) { System.out.println("\n恭喜你中了一等奖!"); System.out.println("玩家购买的号码:"); System.out.println("红球:" + userRedBalls + " 蓝球:" + userBlueBall); } else if (redBallMatch == 6) { System.out.println("\n恭喜你中了二等奖!"); } else if (redBallMatch == 5 && blueBallMatch) {// System.out.println("\n恭喜你中了三等奖!"); } else if (redBallMatch == 5 || (redBallMatch == 4 && blueBallMatch)) {// System.out.println("\n恭喜你中了四等奖!"); } else if (redBallMatch == 4 || (redBallMatch == 3 && blueBallMatch)) {// System.out.println("\n恭喜你中了五等奖!"); } else if (blueBallMatch) {// System.out.println("\n恭喜你中了最小奖!"); } else { //没中奖,不打印记录 } }
/** * 返回玩家选择的6个红球,范围1-33,不重复 */ private static Set<Integer> getUserSelectedRedBalls() { Set<Integer> userRedBalls = new HashSet<>(); while (userRedBalls.size() < 6) { int num = random.nextInt(33) + 1; userRedBalls.add(num); } return userRedBalls; }
/** * 玩家选择的1个蓝球,范围1-16 */ private static int getUserSelectedBlueBall() { return random.nextInt(16) + 1; }
/** * 匹配中了几个红球 * @return 中红球个数 */ private static int countMatchingBalls(Set<Integer> userBalls, Set<Integer> winningBalls) { int count = 0; for (int ball : userBalls) { if (winningBalls.contains(ball)) { count++; } } return count; }
}
复制代码


结论:排除其它因素,头奖概率约 1700 万分之 1,这个结论并不直观,例举如下几个进行对比


1.一家祖孙三代人的生日都在同一天的概率约为 27 万分之一


2.小行星撞击地球的概率保守推测是 200 万分之一


3.生出全男或全女四胞胎的概率约为 352 万分之一

悖论 3:三个枪手

描述:三个小伙子同时爱上了一个姑娘,为了决定他们谁能娶这个姑娘,他们决定用枪进行一次决斗。A 的命中率是 30%,B 比他好些,命中率是 50%,最出色的枪手是 C,他从不失误,命中率是 100%。由于这个显而易见的事实,为公平起见,他们决定按这样的顺序:A 先开枪,B 第二,C 最后。然后这样循环,直到他们只剩下一个人。那么 A 第一枪应该怎么打?谁活下来的概率最大?


论证:每个人的目标都是活下来,为了目标寻找最好的策略。以下开始分人讨论


A:


  • 若 A 开枪射杀了 B,则下个开枪是 C,C 会 100%射杀 A,这不是一个好策略

  • 若 A 开枪射杀了 C,则下一轮 B 会有 50%的几率杀掉自己

  • 若 A 开枪未打中,则下一轮可以坐山观虎斗,所以 A 最好的策略看似是故意打空枪更好一些


B:


  • 若 A 已经将 C 射杀,此时 B 与 A 互相射击,B 的生存率高于 A

  • B 只能选择射杀 C,因为只要 C 活着,都会优先射杀 B


C:


  • 先消除威胁大的 B,然后再杀掉 A,只要自己有开 2 枪的机会,直接获胜


结论:需求太复杂,暂未实现生存概率计算😭,欢迎补充悖论 3 的代码论证过程


作者:京东保险 王苑沣

来源:京东云开发者社区 转载请注明来源

发布于: 刚刚阅读数: 3
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
买彩票能中大奖?用Java盘点常见的概率悖论 | 京东云技术团队_京东云_京东科技开发者_InfoQ写作社区