俄罗斯轮盘是一个经典的概率博弈游戏,玩家在装有子弹的转轮手枪中旋转弹仓后扣动扳机。本文用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为弹仓容量 |
六、核心要点总结
- 条件概率递增:随着存活轮数增加,后续中弹概率单调递增(不放回抽样特性)
- 期望值决策:当期望收益为正时游戏”值得玩”,为负时理性玩家应拒绝参与
- 最优停止:动态规划从后向前填表,比较”停止”与”继续”的期望收益
- 蒙特卡洛验证:大数定律保证模拟结果收敛于理论期望值
本文通过Java完整实现了俄罗斯轮盘的概率模型,读者可以修改参数探索不同配置下的策略差异,深入理解概率论在最优决策中的应用。