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

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

需求

仿照微信发红包功能,现有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

热门相关:影帝偏要住我家   混在三国当军阀   秾李夭桃   锦桐   懒散初唐