红包算法
这是我参与「第四届青训营 」笔记创作活动的的第5天。
前几天,在跟着月影学JavaScript课程中,讲了四个小算法,已经发 (shui) 了前面三个,有兴趣的可以去看一下Leftpad快速幂与位运算 | 青训营笔记 - 掘金 (juejin.cn) 、 洗牌算法 | 青训营笔记 - 掘金 (juejin.cn)。
而今天这个算法,了解了可以帮助你更大概率抢到大红包!
红包是随机的吗?
或许你可能会说,当然,这完全靠运气的东西啊,100块有人能抢60,有人只能精准1块钱。
不知道你有没有这种感觉:
-
好像后抢的金额更大?
-
好像抢的金额跟人数有关?
n 个人抢 n + 1 分钱
这里我们假设有 n 个人抢红包,红包金额为 n + 1 分钱。
毫无疑问的是,肯定有 n - 1 个人抢的是 1 分钱,而有一个抢的是 2 分钱。
按理说那个 2 分钱应该是所有人获得的概率是一样的。
但是,现实情况是肯定是最后一个人抢到 2 分钱!!
这里斥 ‘巨资’ 做的实验:
- 2 个人抢 3 分
- 3个人抢 4 分
可以看抢光耗时,这几个都是不一样,暂时就做了这几个实验,我每次都是最后抢的,每次都是 2 分钱(别问,问就是回血),按照随机的说法,我要是想每次都抢到 2 分,概率为:1/2 * 1/2 * 1/3 * 1/3 * 1/3 = 1/108,这么低的概率,那我们是不是可以说 n 个人抢 n + 1分钱,肯定是最后一个人抢 2 分呢。
- 如果各位有兴趣的话,可以试试更多人,是不是也符合这样的定律。
那么是不是对于金额大的,一定最后的人抢的就一定多呢。这也不一定,到这里就得看微信红包实现算法了。
微信红包实现
二倍均值法
在网上查阅了很多,大部分都是说的二倍均值法实现的微信红包。
- 所谓二倍均值法,就是说保证每个人随机到的数为 1分 ~ 两倍平均值-1分,平均值为
当前剩余钱 / 当前剩余未抢的人数
核心代码如下:
for(int i = 0; i < num - 1; i ++){
int ave = money / (num - i);
int temp = 1 + rand() % (2 * ave - 1);
cout << temp << endl;
money -= temp;
}
cout << money; // 最后一个人将剩下的都拿走
这种算法,也刚好满足我们上面的 n 个人抢 n + 1 分钱 的事实。
那么这样一个算法,是否有什么能帮助我们更大概率抢到更大的红包呢。答案是有的。
如何抢到大红包
从上面我们得知,假如100元的红包,5个人抢,第一个人只可能抢到0.01 ~ 39.99,第二个人 0.01 ~ 49.99 ,第三个人 0.01 ~ 66.65 ,第四个人 0.01 ~ 99.95,第五个人 0.01 ~ 99.96,可以看到,只有在后面的,才有可能抢到超级大红包。
这里我们模拟一下,设置四个数组,分别是每个人运气王次数,平均金额数和最大最小金额数,我们来看一下模拟10000次结果如何:
- 3个人抢15
- 5个人抢20
- 10个人抢50
- 20个人抢100
从上面,我们基本可以得出以下几点:
-
如果是希望抢到超级大红包,最后两个概率更大
-
人数比较少的时候,先抢更容易是运气王
-
人数比较多的时候,最好开始就抢或者最后抢,中间运气王的概率是最低的,最后几个抢运气王概率是最大的
-
不管怎么样抢,均值都差不多,也可以说是比较 ‘公平’
-
不要因为等后面几个而红包被抢光...
证明过程
如下是这种规则 先抢后抢均值相等,而后抢的方差更大 的证明:
也可以看看他发的视频:我给自己发了2亿个红包,才发现先抢和后抢差距这么大!_哔哩哔哩_bilibili
附代码:
#include <iostream>
#include <algorithm>
#include <ctime>
#include <cstring>
using namespace std;
const int N = 25; // 最大人数
double money;
int num, cent, loop = 10000;
int nums[N], maxM[N], minM[N], sum[N]; // 运气王次数, 最大红包金额,最小红包金额,金额总和
void fightLuck(){
// 保证每个人随机到的数为 1分 ~ 两倍平均值-1分
int n = num, m = cent, maxNum = 0, index;
for(int i = 0; i < n - 1; i ++){
int ave = m / (n - i), temp = 1 + rand() % (2 * ave - 1);
m -= temp, sum[i] += temp;
maxM[i] = max(maxM[i], temp), minM[i] = min(minM[i], temp);
if(temp > maxNum) maxNum = temp, index = i;
}
sum[n - 1] += m, maxM[n - 1] = max(maxM[n - 1], m), minM[n - 1] = min(minM[n - 1], m);
if(m > maxNum) index = n - 1;
nums[index] ++; //运气王
}
void print(){
cout << endl << " 第 个人 : ";
for(int i = 0; i < num; i ++) printf(" %d \t", i + 1);
cout << endl << "运气王次数: ";
for(int i = 0; i < num; i ++) printf("%d\t", nums[i]);
cout << endl << "平均金额数: ";
for(int i = 0; i < num; i ++) printf("%.2lf\t", (double)sum[i] / 1000000);
cout << endl << "最大金额数: ";
for(int i = 0; i < num; i ++) printf("%.2lf\t", maxM[i] / 100.0);
cout << endl << "最小金额数: ";
for(int i = 0; i < num; i ++) printf("%.2lf\t", minM[i] / 100.0);
}
int main(){
memset(minM, 0x3f, sizeof minM);
srand((unsigned)time(NULL));
cout << "请输入金额和总人数:" << endl;
cin >> money >> num;
cent = money * 100; // 转成 分 钱
if(cent < num) cout << "金额不足" << endl;
else{
while(loop --) fightLuck();
print();
}
return 0;
}
最后
可能现在微信红包具体实现算法与上有所出入,但是就我目前抢的来看得话好像大差不差,如果对你 (抢红包) 有所帮助的话,点个赞再走吧哈哈哈。
如有不当之处,欢迎指出~