每日算法 — 使用java实现轮盘赌:概率建模与鞅 betting 策略

引言

轮盘赌是赌场中最具代表性的概率游戏之一。标准欧式轮盘包含37个数字槽(0-36),每个数字被分为红色、黑色或绿色(仅0号)。玩家可以押注单个数字、颜色、奇偶或数字范围。本文将用Java实现轮盘赌的核心概率模型,并探讨著名的马丁格尔(Martingale)加倍 betting 策略的数学原理与风险边界。

轮盘赌概率模型

在欧式轮盘中,各押注类型的赔率和真实概率如下:

  • 单个数字:赔率35:1,概率1/37
  • 颜色(红/黑):赔率1:1,概率18/37
  • 奇/偶:赔率1:1,概率18/37
  • 大/小(1-18/19-36):赔率1:1,概率18/37

赌场优势(House Edge)来源于0号的存在。对于颜色押注,期望收益为 (18/37) * 1 + (19/37) * (-1) = -1/37 ≈ -2.7%。

马丁格尔策略分析

马丁格尔策略的核心思想:每次输后将下注额翻倍,直到赢为止。理论上可以回收所有损失并获得初始利润。但该策略面临两个致命问题:

  1. 资金上限:连续N次失败需要 2^N - 1 倍初始资金
  2. 赌桌上限:赌场设有最大下注限制

假设初始资金1000元,初始下注10元,在出现6次连续失败后,累计损失630元,第7次需要下注640元,已超出常见资金阈值。

Java实现

import java.util.Random;

/**
 * 轮盘赌概率模拟器
 * 包含欧式轮盘基础模型与马丁格尔策略回测
 */
public class RouletteSimulator {

    // 欧式轮盘数字定义:0为绿色,1-36中红色与黑色交替
    private static final int[] RED_NUMBERS = {1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36};
    private static final Random random = new Random();

    /**
     * 判断数字颜色:0返回green,其他按RED_NUMBERS判断
     */
    public static String getColor(int number) {
        if (number == 0) return "green";
        for (int red : RED_NUMBERS) {
            if (red == number) return "red";
        }
        return "black";
    }

    /**
     * 模拟单次轮盘旋转,返回0-36的随机数字
     */
    public static int spin() {
        return random.nextInt(37);
    }

    /**
     * 颜色押注的收益计算
     * @param betAmount 下注金额
     * @param betColor 押注颜色 "red" 或 "black"
     * @param result 轮盘结果数字
     * @return 净收益(正为赢,负为输)
     */
    public static double colorBet(double betAmount, String betColor, int result) {
        String resultColor = getColor(result);
        if (resultColor.equals(betColor)) {
            return betAmount; // 赔率1:1,净收益等于下注额
        }
        return -betAmount;
    }

    /**
     * 单个数字押注的收益计算
     * @param betAmount 下注金额
     * @param betNumber 押注数字 0-36
     * @param result 轮盘结果数字
     * @return 净收益
     */
    public static double numberBet(double betAmount, int betNumber, int result) {
        if (betNumber == result) {
            return betAmount * 35; // 赔率35:1
        }
        return -betAmount;
    }

    /**
     * 马丁格尔策略模拟
     * @param initialBankroll 初始资金
     * @param baseBet 初始下注额
     * @param maxRounds 最大轮数
     * @param tableLimit 赌桌最大下注限制
     * @return 最终资金
     */
    public static double martingaleStrategy(
            double initialBankroll, 
            double baseBet, 
            int maxRounds,
            double tableLimit) {
        double bankroll = initialBankroll;
        double currentBet = baseBet;
        int consecutiveLosses = 0;

        System.out.printf("马丁格尔策略模拟:初始资金%.0f,基础下注%.0f,上限%.0f%n", 
                initialBankroll, baseBet, tableLimit);

        for (int round = 1; round <= maxRounds && bankroll > 0; round++) {
            // 检查资金是否足够当前下注
            if (currentBet > bankroll) {
                System.out.printf("第%d轮:资金不足,当前资金%.0f < 所需下注%.0f,策略破产%n", 
                        round, bankroll, currentBet);
                break;
            }

            // 检查是否超过赌桌上限
            if (currentBet > tableLimit) {
                System.out.printf("第%d轮:触及赌桌上限%.0f,重置下注%n", round, tableLimit);
                currentBet = baseBet;
                consecutiveLosses = 0;
            }

            // 固定押注黑色
            int result = spin();
            String color = getColor(result);
            double profit = colorBet(currentBet, "black", result);
            bankroll += profit;

            if (profit > 0) {
                // 赢了,重置下注
                System.out.printf("第%d轮:结果%d(%s),赢%.0f,资金%.0f,重置下注%n",
                        round, result, color, profit, bankroll);
                currentBet = baseBet;
                consecutiveLosses = 0;
            } else {
                // 输了,加倍下注
                consecutiveLosses++;
                System.out.printf("第%d轮:结果%d(%s),输%.0f,资金%.0f,连续%d次失败,下次下注%.0f%n",
                        round, result, color, currentBet, bankroll, consecutiveLosses, currentBet * 2);
                currentBet *= 2;
            }
        }

        return bankroll;
    }

    /**
     * 蒙特卡洛模拟:评估长期期望收益
     * @param roundsPerSession 每会话轮数
     * @param sessions 模拟会话数
     */
    public static void monteCarloSimulation(int roundsPerSession, int sessions) {
        double totalProfit = 0;
        int bustCount = 0; // 破产次数

        for (int s = 0; s < sessions; s++) {
            double bankroll = 1000;
            double bet = 10;
            for (int r = 0; r < roundsPerSession && bankroll >= bet; r++) {
                int result = spin();
                double profit = colorBet(bet, "black", result);
                bankroll += profit;
                if (profit < 0) {
                    bet *= 2;
                    if (bet > bankroll) {
                        bustCount++;
                        break;
                    }
                    if (bet > 1000) { // 假设赌桌上限
                        bet = 10;
                    }
                } else {
                    bet = 10;
                }
            }
            totalProfit += (bankroll - 1000);
        }

        System.out.printf("蒙特卡洛结果(%d次会话,每次%d轮):%n", sessions, roundsPerSession);
        System.out.printf("  平均收益: %.2f%n", totalProfit / sessions);
        System.out.printf("  破产概率: %.2f%%%n", (bustCount * 100.0) / sessions);
    }

    public static void main(String[] args) {
        System.out.println("=== 轮盘赌概率演示 ===");
        // 演示单次旋转
        for (int i = 0; i < 10; i++) {
            int result = spin();
            System.out.printf("旋转结果: %d (%s)%n", result, getColor(result));
        }

        System.out.println("\n=== 马丁格尔策略模拟 ===");
        double finalBankroll = martingaleStrategy(1000, 10, 100, 1000);
        System.out.printf("最终资金: %.0f%n", finalBankroll);

        System.out.println("\n=== 蒙特卡洛长期模拟 ===");
        monteCarloSimulation(100, 10000);
    }
}

复杂度分析

  • 单次旋转:O(1) 时间,O(1) 空间
  • 马丁格尔策略模拟:O(N) 时间(N为轮数),O(1) 额外空间
  • 蒙特卡洛模拟:O(S × R) 时间(S为会话数,R为每会话轮数)

策略反思

马丁格尔策略在数学上看似无懈可击,但在实际应用中面临几何级数风险。连续10次失败的概率约为 (19/37)^10 ≈ 0.13%,看似极低,但在大量重复试验中几乎必然发生。本模拟器可用于量化不同资金规模和赌桌限制下的破产概率,帮助理解期望值在长期中的主导作用。