最优算法在文末,欢迎参考。
编写抢红包随机算法功能,通常金额是红包支付后立马算好的,而不是抢一个实时随机一个红包金额,避免并发情况下降低性能。
需求
仿照微信发红包功能,现有n个人抢金额为m的红包,m>=0.01,n>0,m/n不能小于0.01,需保证每个人都能抢到最低为0.01的金额,金额随机,但金额相对均衡。
解决方案
无限制随机(不可取)
假设:10个人抢100元的红包。
操作:前几人无限制随机0.01到最大金额,最后一人兜底剩余金额。
结论:这种算法很不稳定,导致前几人分的很多,后面的人没钱分或者很少分的情况,容易出现极端现象。
function red_envelope($money, $person) {
for($i = 1; $i <= $person; $i++) {
if($person === $i) {
$res[$i] = $money;
} else {
if($money <= 0) {
$res[$i] = 0;
} else {
$res[$i] = bcdiv(mt_rand(1, bcmul($money, 100)),100, 2);
$money = bcsub($money, $res[$i], 2);
}
}
}
return $res;
}
演进:均衡分配(凑合)
假设:3个人抢20元的红包。
操作:甲抢到6.66,乙抢到6.66,丙兜底抢剩余的的6.68。
结论:算法简单,但随机性差点。
function red_envelope($money, $person) {
$div = bcdiv($money, $person, 2);
for($i = 1; $i <= $person; $i++) {
$res[$i] = $person === $i ? $money - $div * ($i - 1) : $div;
}
return $res;
}
演进:递减法(不可取)
假设:10个人抢100元的红包。
操作:第一个人在0.01-99.1之间,预扣的0.09是供其它9个人使用的,假设第一个人抢了70,那么第二个人就是0.01到(100-70-(8 * 0.01))之间的随机数,最后一人兜底剩余金额。
结论:这种算法仍旧不公平,越到后面,金额越少,导致后面的人没钱分或者很少分,容易出现极端现象。
function red_envelope($money, $person) {
for($i = 1; $i <= $person; $i++) {
if($i != $person) {
//剩余金额 = 总金额 - (最小值 * (总人数 - 当前人数))
$residue_money = bcsub($money, bcmul(0.01, bcsub($person, $i, 2), 2), 2);
$res[$i] = bcdiv(mt_rand(1, bcmul($residue_money, 100, 2)), 100, 2);
$money = bcsub($money, $res[$i], 2);
} else {
$res[$i] = $money;
}
}
return $res;
}
演进:无序递减法(不可取)
假设:10个人抢100元的红包。
操作:把上一个方法的顺序打乱。
结论:这种算法仍旧不公平,导致任意某人可能没钱分或者很少分,容易出现极端现象。
$res = red_envelope(100, 10, 0.01);
shuffle($res);
演进:二倍均值法(推荐)
假设:假设10个人抢100元红包。
操作:取0.01~(剩余金额 / 剩余人数 * 2)之间的随机数(2为常数,用于影响结果使其趋向平均值),最后一人兜底剩余金额。
结论:二倍均值法理论上可实现相对均衡的随机金额。
function red_envelope($money, $person) {
$arr = [];
for($i = 0; $i < $person; $i ++) {
$arr[$i] = $money;
if($person !== ($i + 1)) {
$arr[$i] = bcdiv(mt_rand(1, intval($money / ($person - $i) * 200)), 100, 2);
$money = bcsub($money, $arr[$i] , 2);
}
}
return $arr;
}
理想情况下平均每人金额在10元上下,以下是模拟:
第几人 | 下限随机金额 | 上限随机金额 | 上限随机金额算法 | 理论平均金额 | 实际随机金额 |
---|---|---|---|---|---|
1 | 0.01 | 20.00 | 100 / 10 * 2 | 10.00 | 12.25 |
2 | 0.01 | 19.50 | 87.75 / 9 * 2 | 9.76 | 6.87 |
3 | 0.01 | 20.22 | 80.88 / 8 * 2 | 10.12 | 11.22 |
4 | 0.01 | 19.90 | 69.66 / 7 * 2 | 9.96 | 10.01 |
5 | 0.01 | 19.88 | 59.65 / 6 * 2 | 9.95 | 0.85 |
6 | 0.01 | 23.52 | 58.80 / 5 * 2 | 11.77 | 19.56 |
7 | 0.01 | 19.62 | 39.24 / 4 * 2 | 9.81 | 4.23 |
8 | 0.01 | 23.34 | 35.01 / 3 * 2 | 11.68 | 9.85 |
9 | 0.01 | 25.16 | 25.16 / 2 * 2 | 12.59 | 12.59 |
10 | 无 | 无 | 无 | 无 | 12.57 |