什么,抢红包也居然存在最优策略?这篇文章研究了统计学视角下「手气王」的分布规律,以及微信红包分配算法的逆向。为了珍贵的研究数据,楼主派发了约 2000 个红包,总耗时 3 个月。
在开启分析之旅前,大伙不妨先思考两个问题:
台球店老板在粉丝群发红包,手气最佳者可以免台费,是否存在最佳策略?
酸穷大学生在分配小组任务的时候,采用红包抽签方案。发1毛钱小金额红包,每个人都只能拿几分钱,这样的抽签是否公平?
9cfc143d112b09af245f0149debd59c0_720.jpg换言之,微信红包的分配是否是公平的?先抢和后抢有没有区别?手气王到底服从什么样的分布规律?要回答这些问题,我们必须分析出微信红包的分配算法。
幸运的是,我们可以站在前人的肩膀上。最早透露微信红包算法的是当时的支付后台架构负责人方乐明[^1],后来也有网友在此基础上分析它的具体算法[^2][^5],工程实现[^3][^4]等。这些文章都提到了一个基础算法——二倍均值法。
微信红包的分配过程是动态的,也就是说每发出去一枚红包,微信后台就会根据 当前剩余人数 和 剩余金额 计算出下一次应该发多少钱,然后依次类推,直到红包发完为止。具体每次发多少钱,是从 0 到 剩余金额➗剩余人数✖️2 这个区间内随机抽取的。
知道了这个算法,我们可以推导出两个结论
每个人分得的红包金额的数学期望是相等的:换言之,微信红包还算公平(话说要是平均每个人拿到的钱不相等那还像话么)
越晚抢红包的人,分得红包金额的方差越大:这就涉及到今天讨论的手气王问题了。手气王是什么,是极值呀。极值一般发生在波动较大的地方,也就是我们可以认定,越晚抢的人,越容易拿到手气王。
下面做一个简单的数学证明(WARNING:下面大量公式出没,非战斗人员可跳过,直接看算法实现部分)
设红包总金额为 M,总人数为 N,Xk 为第 k 个人抢到的金额,Rk 为第 k 个人抢红包之前的剩余金额,nk 为剩余人数。于是
R1=M,Rk+1=Rk−Xknk=N−k+1 对于第 k 个人,Xk 服从如下均匀分布:
Xk∣Rk∼U(0,nkRk×2) 使用数学归纳法证明结论一:
对于第1个人,Xk∼U(0,NM×2),可以得到第一个人的期望
E[X1]=20+N2M=NM 假设对于所有 j<k,都有 E[Xj]=NM。根据全期望公式
E[Xk]=E[E[Xk∣Rk]]=E[21×N−k+12Rk]=E[N−k+1Rk]=N−k+1E[Rk] 而剩余金额 Rk 是总金额减前面 k−1个人拿去的钱:
Rk=M−j=1∑k−1Xj 代入上式,可得:
E[Xk]=N−k+11E[M−j=1∑k−1Xj]=N−k+11(M−j=1∑k−1E[Xj])=N−k+11(M−(k−1)NM)=N−k+1N−k+1⋅NM=NM 证明结论二: 上文已经证明了期望相等,那么根据方差公式 Var(X)=E[X2]−E[X]2,只需要证明二阶矩 E[Xk+12]>E[Xk2] 即可。
根据定义,Xk∼U(0,nk2Rk),直接用均匀分布的期望平方公式,可得
E[Xk2∣Rk]=31(nk2Rk)2=3nk24Rk2 两边同时求期望:(全期望公式)
E[Xk2]=3nk24E[Rk2]⟹E[Rk2]E[Xk2]=3nk24(1) 接下来是 Rk+1 与 Rk 之间的递推关系: 根据定义:
Rk+1⟹Rk+12⟹E[Rk+12∣Rk]=Rk−Xk=Rk2−2RkXk+Xk2=Rk2−2RkE[Xk∣Rk]+E[Xk2∣Rk] 把上文的结果 E[Xk∣Rk]=nkRk,E[Xk2∣Rk]=3nk24Rk2代入得到:
E[Rk+12∣Rk]⟹E[Rk+12]=Rk2−2Rk(NkRk)+3nj24Rk2=Rk2(1−nk2+2nk24)=Rk2⋅3nk23nk2−6nk+4=E[Rk2]⋅3nk23nk2−6nk+4(2) 所以我们由 (1),(2) 可以推出
E[Xk+12]⟹E[Xk2]E[Xk+12]=3(nk−1)24E[Rk+12]=3(nk−1)24⋅E[Rk2]⋅3nk23nk2−6nk+4=3nk24E[Rk2]3(nk−1)24⋅E[Rk2]⋅3nk23nk2−6nk+4=3nk2−6nk+33nk2−6nk+4>1 所以 E[Xk+1]2>E[Xk2]⟺Var(Xk+1)>Var(Xk) ,证毕。
各位看官可能要被绕晕了,不过不要紧,证明不考(狗头
能不能再给力一点啊老师.jpg 同学,真正的逆向才刚刚开始......
话说数学很单纯,但业务很复杂。要是微信真按这个算法来,恐怕要被用户按在地上问候了(虽然现在也是这样)这个算法的问题出在哪里呢?
钱是整数不是分数,程序猿涉及金钱业务的大忌。千万把离散的钱变成连续的,不然用户会看着 1.14514 元陷入沉思。
既然钱是离散的,那么恐怕就不能从 0 开始取吧,否则用户会开出个 0 元的红包。
假如 2 分钱的红包分给 2 个人。按算法第一个人可以拿到 22×2=2 分钱,那么最后一个人呢?没有什么限制的话,只能喝西北风了。
既然微信针对离散的情形做了优化,那么究竟是什么样的呢?是先用连续抽取再变成离散,还是从一开始就是离散抽取?为了保证每个人抽取至少有1分钱,微信是怎么做的?
很遗憾,这个模型到现在还是黑盒状态。但是我们可以从一个神奇的现象中管中窥豹。
这个现象就是——末位红包抽屉原理[^6]。
咳咳,这名字不是我取的,来源于@毕导 (顺便一提这篇公众号文章写得很棒,基本上把大金额情况下的结论解释清楚了,推荐大家都观摩一下)
末位红包抽屉原理指的是,当 n+1 分钱的红包被 n 个人抢时,永远是最后一位拿到 2 分钱,而前面的人通通都是 1 分钱。也就是说永远是最后一位手气最佳。
Pasted image 20260220215713.png诸位如果不信的话,可以拿亲朋好友测试一下,保证你可以拿手气王:
Pasted image 20260220210449.png诶?楼主你不是说,每个人拿到钱的期望是一样的么,怎么到这里就是错的了呢?
这是因为,刚刚讨论的是连续的情况,而这里金额单位是分,也就是离散的情况。这便是问题的复杂和有趣之处了——在离散的情况下,刚刚推导的结论完全不成立,需要重新建立一个算法。
能不能再给力一点啊老师.jpg 为了彻底搞清楚微信是怎么发红包的,楼主打入了某红薯平台,潜入了十数个跨年红包群,然后在里面嘎嘎发红包,在小妹妹面前当金主爸爸(划掉)。
Pasted image 20260220211514.png(顺便提一嘴,期间还遇到了两个被微信封杀的诈骗群。红包群只要是收入群费的,一律鉴定为诈骗......)
然后楼主派发了 200 个 10 人份的红包(金额0.19元),共计发了约 2000 份红包。我敢说,这200 份数据是全互联网最大的一组微信红包数据集,没有之一。
Pasted image 20260220212113.png我们来画出 200 次抢红包中,手气王是如何分布的:
Pasted image 20260220214630.png 非常的 Amazing 啊,第一个抢的同学居然没有一次是手气王!更加神奇的是,最后一位抢的包揽了几乎一半的手气王,这是不是有些太夸张了点?没错,这就是小金额微信红包的现状。小金额的情况下,后抢优势被远远放大了。
而且注意到,第一位永远是 0.01 元。但根据二倍均值法,应该取得到 ⌊100.19⌋×2=0.02 元才对呀,这是为什么呢?
我们有理由相信,微信为了保证下一位至少有 0.01 元,针对随机上界作出了调整。于是我们可以立即写出三种不同的分配算法:
这三种算法都符合「末位红包抽屉原理」,那应该如何鉴别算法之间的好坏呢?对于每个算法,我们可以跑 2000 次蒙特卡罗模拟,计算出模拟情况下的手气分布。下面是这些算法和真实分布的比较:
原版算法算法一算法二算法三可以看出,原版算法错得相当离谱。算法二、算法三都不符合柱状图的基本规律。只有算法一勉强能看……但是也是错的,因为根本通过不了卡方检验。
于是我思考了一个问题,这个抽取,真的是均匀的吗?
下面是一张数据透视表,表头是拿到的金额(分),第 n 行是顺序为 n 的人拿到的金额。
Pasted image 20260221010339.png 可以看到,第一位全部都只拿到 1 分是没有问题的,但是第二位理论上拿1、2、3分钱的概率相同,但是居然呈现了 2:1:1 的趋势,这样的结果已经不能用统计误差来解释了。也就是说,我们之前默认的均匀抽取这个大前提被推翻了。
这样的不均匀抽取,是如何产生的呢?我猜测,微信在做抽取的时候是从0开始的连续均匀抽样,抽取的数字小于1时,会自动变为1。这也就解释了为什么恰好是2:1:1
手搓的示意图 嗯,很合理,于是算法四诞生了:
我们来看看算法四的表现:
算法四 已经和真实数据差异不大了,下降随后上升的趋势也复现得很完美。
能不能再给力一点啊老师.jpg 如你所见,微信红包算法并没有被完美复现。因为当你做一个卡方拟合优度检验的时候,会发现真实算法长这样,概率只有5.5%。(恰好不能被拒绝,嘿嘿嘿)
X^2 | 自由度 | p-value |
|---|
15.211 | 8 | 0.05516 |
虽然没有完美复现,但至少也算是探索了一把微信红包的底层机制。还要再进一步复原的话,就只能问张小龙了
Pasted image 20260221002634.png 回答一下开头提的问题:
Q:台球店老板在粉丝群发红包,手气最佳者可以免台费,是否存在最佳策略? A:当然有,越后抢越好。最好把人数和金额都在电脑里跑一遍,甚至可以确定在第几位抢最合适(前提是你手稳得一批)
Q:酸穷大学生在分配小组任务的时候,采用红包抽签方案。发1毛钱小金额红包,每个人都只能拿几分钱,这样的抽签是否公平? A:不公平,越后抢的人越容易手气最佳。最好使用 Python 或者 R 跑个随机数,这个是最公平的。
最后祝大伙们马年吉祥,财源滚滚!
30b6c043db6463c1d4a3129b306aba64_720.jpg[^1]: 揭秘微信红包:架构、抢红包算法、高并发和降级方案 https://www.cnblogs.com/8hao/p/5383143.html 微信红包金额分配的算法 https://timyang.net/architecture/wechat-red-packet/ 微信红包实现原理 https://www.phppan.com/2015/04/weixin-hongbao/ 微信红包的架构设计简介 https://www.zybuluo.com/yulin718/note/93148 微信红包的随机算法是怎样实现的? - 沉简的回答 https://www.zhihu.com/question/22625187/answer/85530416 微信红包先抢和后抢差距居然这么大!春节抢红包的大数据分析 https://mp.weixin.qq.com/s/5YV4QsK6zXzx-_CdaeLutA