开发者社区 > 博文 > 买彩票能中大奖?用Java盘点常见的概率悖论
分享
  • 打开微信扫码分享

  • 点击前往QQ分享

  • 点击前往微博分享

  • 点击复制链接

买彩票能中大奖?用Java盘点常见的概率悖论

  • 王苑沣
  • 2023-09-07
  • IP归属:北京
  • 14600浏览

    引言

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

    《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的代码论证过程

    // todo


    文章数
    1
    阅读量
    365

    作者其他文章