本文根据 MIT 计算机科学离散数学课程整理(Lecture 25)。
赌徒破产问题(Gambler's Ruin)
问题描述
初始为 n 元,对于每一次独立的赌注,都有 p 的概率赢得 1 元,(1-p) 的概率输掉 1 元。当输完 n 元或赢得 m 元(总资产 m+n 元)结束赌注。当 ,求玩家赢得 m 元的概率和玩家输掉 n 元期望赌注次数。
引理
解决问题之前先补充关于一般齐次线性递归式和一般非齐次线性递归式的解法,离散的情况和连续的情况类似,可以类比高等数学中的常系数齐次线性微分方程。
一般齐次线性递归式
若函数 满足递推式:
且有 d 个边界条件:
令 ,代入得特征方程
,解得
如果结果没有重根,则有 ,代入边界条件即可求得
;如果某一个
解为 r 重根,则
都是线性解,
需要写成这些解的线性和。
例如得到特征方程的解为:1,2,2,2,3,4,其中 2 为 3 重根,通解就是
一般非齐次线性递归式
若函数 满足递推式:
先按照齐次线性递归式的方法求出 时的通解,在求出满足非齐次方程的一个特解。非齐次方程的通解是齐次方程的通解加上特解。
Solution
用 W 表示事件:在 n 使用完之前赢得 m 。
D 表示初始美元的值。 表示在初始为 n 的情况下最终赢得 m 的概率。
则有:
可以求得 有如下递推式:
类似于动态规划中划分状态的思路: 可以分为第一次赌注成功的情况和失败的情况,成功的情况赢得 1 元,转换到状态
,失败就转换到状态
。
严格推理证明如下:
用事件 A 表示赢得第一次赌注。
用 S 表示玩家直到输掉 n 元的赌注次数, 表示输掉 n 元期望赌注次数,即:
同理, 可以分为第一次赌注成功的情况和失败的情况,成功的情况转换到状态
,失败就转换到状态
,则有下面递推式:
递推式满足一般齐次线性递归式,
递推式满足一般非齐次线性递归式,均可以转换为先求齐次方程通解,特征方程为:
解得:
公平游戏
先考虑 ,即完全公平游戏的情况,此时解为 2 重根
。
先求齐次方程 通解为:
,代入
解得,
故:
齐次方程 表示为齐次方程通解加上特解:
令特解为: ,代入递推式得到
代入边界条件求得结果为:
从结果来看,公平游戏赢得游戏概率于 m 和 n 有关,对于在高赌注少收益的情况概率高,例如 n=1000,m=100 的情况赢得游戏概率高达 。对于 m 很大的情况,虽然根据推理出的输掉的期望步数很大,然而可以证明如果一直玩下去,输掉 n 赌注的概率为 1。
从极限的角度考虑,
课程中给出关于公平游戏一定会输掉赌注的证明如下:
反证法,假设 ,使得
则有:
,推出了矛盾式。
非公平游戏
对于不公平游戏,,
,解为两个不同的根。
先求齐次方程 ,通解表示为:
代入得到:
非齐次方程 表示为:
令特解为: ,代入递推式得到
代入求得
,最后得到结果:
可以看到,非公平游戏赢得游戏概率于收到 m 的指数级别的影响,会比公平游戏小得多。这里可以放缩分析:由于 ,故
。假设
,n=1000,m=100 的情况赢得游戏概率比
还小,该数据代入期望步数结果约为 18999.44。实际上,对于不公平游戏赌注较大时,期望输掉游戏结果主要取决于 n ,即:
赌徒破产问题中的随机漫步
下面写一个针对赌徒破产问题的脚本:
#!/usr/bin/env python3
from argparse import ArgumentParser
import numpy as np
import matplotlib.pyplot as plt
def SetParser():
parser=ArgumentParser(
prog="Gambler's Ruin.",
description="Simulation of Gambler's Ruin."
)
parser.add_argument("-n","--N",default=0,help="Initial bet in dollars.",type=int)
parser.add_argument("-m","--M",default=0,help="Desired winnings from the bet in dollars.",type=int)
parser.add_argument("-s","--maxsteps",default=100000,help="Maximum number of bets.",type=int)
parser.add_argument("-p","--prob",default=0.5,help="The probability to win the game",type=float)
return parser.parse_args()
def check_args(args):
if args.N<0:
raise ValueError("Initial bet (-n/--N) must be a non-negative integer.")
if args.M<0:
raise ValueError("Desired winnings (-m/--M) must be a non-negative integer.")
if args.maxsteps<0:
raise ValueError("Maximum number of bets (-s/--maxsteps) must be a non-negative integer.")
if not (0<args.prob<=0.5):
raise ValueError("Probability (-p/--prob) must be a float between 0 and 0.5 (exclusive).")
class Gambler_Ruin:
def __init__(self,n,m,s,p):
self.n=n
self.m=m
self.s=s
self.p=p
def Simulate_process(self):
val=self.n
Y=[self.n]
for i in range(self.s):
if val<=0 or val>=self.n+self.m: break
if np.random.rand()<self.p: val+=1
else: val-=1
Y.append(val)
return Y
def Plot(self,Y):
plt.figure(figsize=(10,6))
plt.plot(range(len(Y)),Y,label="dollars over time")
plt.axhline(self.n, color='blue',linestyle='--',label="Initial bet (n)")
plt.axhline(self.n+self.m,color='green',linestyle='--',label="Desired winnings (n+m)")
plt.axhline(0,color='red',linestyle='--',label="Bankruptcy")
if self.p!=0.5:
y_drift=[-(1-2*self.p)*x+self.n for x in range(len(Y))]
plt.plot(y_drift,label="Drift",color="orange",linestyle="--")
plt.title(f"Gambler's Ruin for n={self.n},m={self.m},p={self.p}")
plt.xlabel("number of bets")
plt.ylabel("dollars")
plt.legend()
plt.grid(True)
plt.show()
plt.annotate(f"End Value: {Y[-1]}",xy=(len(Y)-1,Y[-1]),xytext=(len(Y)-1,Y[-1]+5),arrowprops=dict(facecolor='black',arrowstyle='->'))
if __name__ == '__main__':
try:
args=SetParser()
check_args(args)
except ValueError as e:
print(f"Error: {e}")
exit(1)
except SystemExit:
print("Invalid input. Use '--help' for usage instructions.")
exit(1)
try:
g=Gambler_Ruin(args.N,args.M,args.maxsteps,args.prob)
Y=g.Simulate_process()
g.Plot(Y)
except Exception as e:
print(f"Error: {e}")
exit(1)
编写脚本命名为 gambler_ruin.py,添加可执行权限即可。
vi gambler_ruin.py
chmod +x gambler_ruin.py
分别模拟不同的情况:
python gambler_ruin.py -n 1000 -m 100 -s 10000000 -p 0.5
python gambler_ruin.py -n 2000 -m 1000 -s 10000000 -p 0.5
python gambler_ruin.py -n 1000 -m 100 -s 10000000 -p 0.45
python gambler_ruin.py -n 2000 -m 1000 -s 10000000 -p 0.45
在公平游戏中,收益的走势表现为:在 上下随机游走。
在非公平游戏中,收益的走势表现为:在 上下随机游走。
其中, 被称为漂移(drift),收益的上下随机变化特性被称为秋千 (Swing)。
漂移(drift)
在赌徒破产理论中,漂移反应了赌徒的资金变化趋势,反映了游戏的长期期望值是否有偏向。其实,函数斜率绝对值 就表示了损失的期望,因为玩家有 1-p 的概率损失 1,p 的概率损失 -1(赢得 1),故
秋千 (Swing)
由于游戏是随机的,每次下注的结果独立且不确定。即使漂移为零或负值,赌徒的资金仍然会因为随机波动而上下波动,形成所谓的“秋千”。在图像上,秋千表现为赌金曲线的随机起伏,围绕漂移线上下摆动。
资金的变化可以建模为一个离散随机游走过程。若当前资金为 ,第 t+1 次下注后资金变为:
表示下一次赌注的资金变化,有
,
累计 t 次下注后,资金的方差为:
秋千的波动幅度可以用 的标准差来表示,即:
所以,秋千的影响为 的数量级。在非公平游戏中远不如漂移的影响。
秋千的三个特点:
(1)波动范围:赌徒的资金变化会在短期内上下波动,可能赢得所有钱或跌至破产。
(2)无方向性:秋千本身没有长期趋势,仅由随机性驱动。
(3)与漂移关系:漂移影响整体趋势(大体走势主要由漂移主导),但秋千决定短期的具体波动形态。
Update1:关于代码中概率判断的问题
python 中的 random.random() 生成的是范围 的随机整数,注意包括 0 不包括 1。所以判断的时候需要用严格小于 p ,此时赢得赌注样本生成概率才正好是 p。