抢红包随机金额算法(均衡随机)

7 篇文章 0 订阅

最优算法在文末,欢迎参考。
编写抢红包随机算法功能,通常金额是红包支付后立马算好的,而不是抢一个实时随机一个红包金额,避免并发情况下降低性能。

需求

仿照微信发红包功能,现有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元上下,以下是模拟:

第几人下限随机金额上限随机金额上限随机金额算法理论平均金额实际随机金额
10.0120.00100 / 10 * 210.0012.25
20.0119.5087.75 / 9 * 29.766.87
30.0120.2280.88 / 8 * 210.1211.22
40.0119.9069.66 / 7 * 29.9610.01
50.0119.8859.65 / 6 * 29.950.85
60.0123.5258.80 / 5 * 211.7719.56
70.0119.6239.24 / 4 * 29.814.23
80.0123.3435.01 / 3 * 211.689.85
90.0125.1625.16 / 2 * 212.5912.59
1012.57
  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小松聊PHP进阶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值