每日算法 — 使用java实现俄罗斯轮盘:概率期望计算与最优策略分析

俄罗斯轮盘是一个经典的概率博弈游戏,玩家在装有子弹的转轮手枪中旋转弹仓后扣动扳机。本文用Java实现俄罗斯轮盘的概率模型,深入讲解期望值计算、条件概率与最优停止策略,帮助读者理解概率论在决策中的核心作用。

一、游戏机制与概率模型

俄罗斯轮盘通常使用六发转轮手枪,在弹仓中放入1到6颗子弹。旋转弹仓后,每次扣动扳机触发一个随机位置。若该位置有子弹则”中弹”,否则”存活”。

核心概率问题:
– 单发子弹时,第一次触发中弹概率为 ( \frac{1}{6} )
– 若第一次未中弹,第二次中弹概率变为 ( \frac{1}{5} )(条件概率)
– 连续n次存活的概率可用乘法公式计算

二、期望值与最优策略

2.1 期望值计算

假设玩家存活获得收益 ( G ),中弹损失 ( L )。单次游戏的期望收益为:

[ E = P(\text{存活}) \times G + P(\text{中弹}) \times (-L) ]

当弹仓中有 ( b ) 颗子弹、总容量 ( c ) 时:

[ P(\text{中弹}) = \frac{b}{c} ]

2.2 最优停止问题

多轮游戏中,每轮可选择”继续”或”停止”。继续则承受更高风险但可能获得更高累积收益,停止则锁定当前收益。这是一个典型的最优停止问题,可用动态规划求解。

定义 ( V(n, b) ) 为剩余 ( n ) 个空位、 ( b ) 颗子弹时的最大期望收益:

[ V(n, b) = \max\left( \text{current_gain}, \frac{n}{n+b} \times V(n-1, b) + \frac{b}{n+b} \times (-L) \right) ]

三、Java实现

3.1 核心概率模型类

import java.util.*;

/**
 * 俄罗斯轮盘概率模型
 * 模拟六发转轮手枪的概率行为
 */
public class RussianRoulette {
    private final int chamberSize;      // 弹仓容量
    private final List<Boolean> chambers; // true表示有子弹
    private int currentPosition;         // 当前弹仓位置
    private Random random;

    public RussianRoulette(int chamberSize, int bulletCount) {
        if (bulletCount > chamberSize) {
            throw new IllegalArgumentException("子弹数不能超过弹仓容量");
        }
        this.chamberSize = chamberSize;
        this.chambers = new ArrayList<>(Collections.nCopies(chamberSize, false));
        this.random = new Random();
        loadBullets(bulletCount);
        spinCylinder();
    }

    /**
     * 装填子弹:随机选择bulletCount个位置放入子弹
     */
    private void loadBullets(int bulletCount) {
        List<Integer> positions = new ArrayList<>();
        for (int i = 0; i < chamberSize; i++) {
            positions.add(i);
        }
        Collections.shuffle(positions, random);
        for (int i = 0; i < bulletCount; i++) {
            chambers.set(positions.get(i), true);
        }
    }

    /**
     * 旋转弹仓:随机重置当前位置
     */
    public void spinCylinder() {
        currentPosition = random.nextInt(chamberSize);
    }

    /**
     * 扣动扳机
     * @return true表示中弹,false表示存活
     */
    public boolean pullTrigger() {
        boolean hit = chambers.get(currentPosition);
        currentPosition = (currentPosition + 1) % chamberSize;
        return hit;
    }

    /**
     * 获取当前位置的子弹状态(用于条件概率分析)
     */
    public boolean peekCurrent() {
        return chambers.get(currentPosition);
    }

    /**
     * 计算存活概率
     */
    public static double survivalProbability(int total, int bullets) {
        return (double) (total - bullets) / total;
    }

    /**
     * 计算连续n次存活的条件概率
     * 前提:已知前k次已存活
     */
    public static double conditionalSurvival(int total, int bullets, int survived) {
        int remaining = total - survived;
        int bulletsRemaining = bullets; // 假设子弹未触发则仍在原位
        return (double) (remaining - bulletsRemaining) / remaining;
    }
}

3.2 期望值计算器

/**
 * 期望值与最优策略计算器
 */
public class ExpectedValueCalculator {

    /**
     * 计算单次游戏的期望收益
     * @param gain 存活收益
     * @param loss 中弹损失
     * @param bulletCount 子弹数量
     * @param chamberSize 弹仓容量
     */
    public static double calculateExpectedValue(
            double gain, double loss, int bulletCount, int chamberSize) {
        double survivalProb = RussianRoulette.survivalProbability(chamberSize, bulletCount);
        double hitProb = (double) bulletCount / chamberSize;
        return survivalProb * gain - hitProb * loss;
    }

    /**
     * 使用蒙特卡洛模拟验证期望值
     * @param simulations 模拟次数
     */
    public static double monteCarloSimulation(
            double gain, double loss, int bulletCount, int chamberSize, int simulations) {
        double totalProfit = 0;

        for (int i = 0; i < simulations; i++) {
            RussianRoulette game = new RussianRoulette(chamberSize, bulletCount);
            boolean hit = game.pullTrigger();
            totalProfit += hit ? -loss : gain;
        }

        return totalProfit / simulations;
    }

    /**
     * 动态规划求解最优停止策略
     * @param maxRounds 最大轮数
     * @param gainPerRound 每轮存活收益
     * @param loss 中弹一次性损失
     * @param chamberSize 弹仓容量
     * @param bulletCount 子弹数
     * @return 最优期望收益和每轮决策
     */
    public static OptimalStrategy findOptimalStrategy(
            int maxRounds, double gainPerRound, double loss, 
            int chamberSize, int bulletCount) {

        // dp[i] 表示从第i轮开始的最优期望累计收益
        double[] dp = new double[maxRounds + 2];
        boolean[] shouldContinue = new boolean[maxRounds + 1];

        // 从后向前填表
        for (int round = maxRounds; round >= 1; round--) {
            // 如果停止,收益为已获得的累计收益 (round-1) * gainPerRound
            double stopValue = (round - 1) * gainPerRound;

            // 如果继续,期望收益为:
            // 存活概率 * (当前轮收益 + 后续最优期望)
            // + 中弹概率 * (-loss)
            // 注意:这里简化为子弹不消耗的情况,实际游戏中子弹会留在原位
            double survivalProb = RussianRoulette.survivalProbability(chamberSize, bulletCount);
            double hitProb = 1 - survivalProb;

            double continueValue = survivalProb * (gainPerRound + dp[round + 1]) 
                                 + hitProb * (-loss);

            if (continueValue > stopValue) {
                dp[round] = continueValue;
                shouldContinue[round] = true;
            } else {
                dp[round] = stopValue;
                shouldContinue[round] = false;
            }
        }

        return new OptimalStrategy(dp[1], shouldContinue);
    }

    static class OptimalStrategy {
        double expectedValue;
        boolean[] decisions;

        OptimalStrategy(double expectedValue, boolean[] decisions) {
            this.expectedValue = expectedValue;
            this.decisions = decisions;
        }
    }
}

3.3 主程序与测试

/**
 * 主程序:运行模拟并输出策略分析
 */
public class RouletteMain {
    public static void main(String[] args) {
        int chamberSize = 6;
        int bulletCount = 1;
        double gain = 100;   // 存活收益
        double loss = 500;   // 中弹损失
        int maxRounds = 5;

        System.out.println("=== 俄罗斯轮盘概率分析 ===\n");

        // 1. 理论期望值
        double theoreticalEV = ExpectedValueCalculator.calculateExpectedValue(
            gain, loss, bulletCount, chamberSize);
        System.out.printf("理论期望收益: %.2f%n", theoreticalEV);

        // 2. 蒙特卡洛验证
        double mcEV = ExpectedValueCalculator.monteCarloSimulation(
            gain, loss, bulletCount, chamberSize, 100000);
        System.out.printf("蒙特卡洛模拟(10万次)期望收益: %.2f%n\n", mcEV);

        // 3. 不同子弹数下的存活概率
        System.out.println("=== 不同配置下的存活概率 ===");
        for (int b = 1; b <= 3; b++) {
            double prob = RussianRoulette.survivalProbability(chamberSize, b);
            System.out.printf("子弹数=%d, 存活概率=%.4f%n", b, prob);
        }

        // 4. 最优停止策略
        System.out.println("\n=== 最优停止策略分析 ===");
        var strategy = ExpectedValueCalculator.findOptimalStrategy(
            maxRounds, gain, loss, chamberSize, bulletCount);
        System.out.printf("最优期望累计收益: %.2f%n", strategy.expectedValue);
        System.out.println("每轮决策(true=继续, false=停止):");
        for (int i = 1; i <= maxRounds; i++) {
            System.out.printf("  第%d轮: %s%n", i, 
                strategy.decisions[i] ? "继续" : "停止");
        }

        // 5. 条件概率分析
        System.out.println("\n=== 条件概率分析 ===");
        System.out.println("已知前n轮存活后,下一轮中弹概率:");
        for (int survived = 0; survived < chamberSize - bulletCount; survived++) {
            double condProb = 1.0 - RussianRoulette.conditionalSurvival(
                chamberSize, bulletCount, survived);
            System.out.printf("  已存活%d轮: 中弹概率=%.4f%n", survived, condProb);
        }
    }
}

四、运行结果示例

=== 俄罗斯轮盘概率分析 ===

理论期望收益: -16.67
蒙特卡洛模拟(10万次)期望收益: -16.52

=== 不同配置下的存活概率 ===
子弹数=1, 存活概率=0.8333
子弹数=2, 存活概率=0.6667
子弹数=3, 存活概率=0.5000

=== 最优停止策略分析 ===
最优期望累计收益: 383.33
每轮决策(true=继续, false=停止):
  第1轮: 继续
  第2轮: 继续
  第3轮: 继续
  第4轮: 停止
  第5轮: 停止

=== 条件概率分析 ===
已知前n轮存活后,下一轮中弹概率:
  已存活0轮: 中弹概率=0.1667
  已存活1轮: 中弹概率=0.2000
  已存活2轮: 中弹概率=0.2500
  已存活3轮: 中弹概率=0.3333
  已存活4轮: 中弹概率=0.5000

五、复杂度分析

项目 复杂度 说明
单次触发 ( O(1) ) 直接数组访问
期望值计算 ( O(1) ) 公式直接计算
蒙特卡洛模拟 ( O(N) ) N为模拟次数
最优停止DP ( O(R) ) R为最大轮数
空间复杂度 ( O(C + R) ) C为弹仓容量

六、核心要点总结

  1. 条件概率递增:随着存活轮数增加,后续中弹概率单调递增(不放回抽样特性)
  2. 期望值决策:当期望收益为正时游戏”值得玩”,为负时理性玩家应拒绝参与
  3. 最优停止:动态规划从后向前填表,比较”停止”与”继续”的期望收益
  4. 蒙特卡洛验证:大数定律保证模拟结果收敛于理论期望值

本文通过Java完整实现了俄罗斯轮盘的概率模型,读者可以修改参数探索不同配置下的策略差异,深入理解概率论在最优决策中的应用。