首先,在这里祝各位小伙伴们鼠年大吉!!
今天是大年初三,Harris在这里用一个简单的话题,开启鼠年的博客之旅。
大家每逢佳节,最少不了的活动,就是“抢红包”。自打微信推出了电子红包之后,中国人的节日,又多了一种庆祝方式,那就是红包雨。这种将传统活动用科技的方式封装的做法,是Harris比较赞成的。把传统溶于新时代,为我们的传统春节带来了新鲜的空气。
但是,我们在群里发红包的时候,可能有的小伙伴会纳闷儿,这个所谓的随机金额,到底是怎么算的呢?它到底是不是公平的呢?
有的小伙伴和我说,这还不简单么,每一个人打开红包的时候,都按照当前剩余的余额为范围,随机生成一个数字就好了。
但是呢,这样的话,随着红包被打开的越来越多,金额的总数也会越来越少。那么,可以抢到的红包也就越来越小,这虽然遵循我们常规所认为的“先来后到”,但是并不公平。
那么要实现绝对的公平,该怎么办呢?
1. 随机分割
其实,这种方法很直观就能想到。我们把一个红包想象成一个香肠,假设有N
个人来分这个香肠,那么我们只要随机切N-1
刀,把每一块分别分给每一个人即可。
红包也是一样的道理,我们可以在红包发出去的同时,就随机分配好金额。等大家来分的时候,再依次分给每一个人就好了。
这种算法思路并不难,而且也能够达到我们的目的,但是由于以下的两点,导致微信没有采用这种算法:
- 算法复杂度高
- 对服务器端的要求高
首先第一点,这个算法的复杂度,肯定是要比现行的微信随机红包算法的复杂度高很多。另外,由于在红包发出去的瞬间,服务器上就需要存储关于这个红包的分配信息,在春节这种红包高峰期的时候,势必会给服务器带来巨大的压力。在分布式系统没有普及之前,第二点都将是一个难以解决的问题。
因此,微信就退而求其次,采用第二种办法。
2. 二倍均值法
实际上,我们每个人抢到的红包,都是在下面这个区间里面去随机一个数值:
微信,正是采用的这种方法。
那么,为什么这个是公平的呢?
假设,发一个100元的红包,10份。那么第一个人的金额,就会在元里面随机。假设第一个人抢到了平均值10块钱,那么还剩90.
第二个人的抢到的,应该是,和之前的一样,以此类推,这个算法是公平的。
但是,并不是每一次都能够理想化地抢到平均值,毕竟我们是要在这个范围内取随机值。假设第一个人只抢到了1块钱,那么第二个人能够抢到的范围是,范围就更大了点,假设第二个人也只抢到1块钱,那么第三个人的范围就是,以此类推,假设前面的9个人都只抢到1块钱,那么最后一个人的范围,就是,实际上就是91块。所以我们可以看到,如果每个人都抢到范围内平均值以下,那么后面的人的随机范围则会越来越大。
那么,我们来看看另外一个极端,如果第一个人抢到20块钱,那么第二个人的范围就是,假设第二个人抢到17元,第三个人的范围就是……
范围会越来越小,但是每个人的红包都不大,都没有超过第一个人,因此第一个人是本次的运气王。
所以,其实这个算法这么看,貌似的确不那么公平,第一个人永远只能抢到二倍均值以下,也就是说,第一个人就是抢不到大红包,而越往后越有可能抢到大红包。
当然,运气王是有可能出现在任意的位置,运气王的位置靠前,则整体不会有很大的红包,运气王靠后的话,后面就有可能出现比前面大很多的红包。
那么这样的算法用代码如何实现呢?
Harris都给大家列出来了,如果感兴趣可以研究研究:
Java:
//代码来源于网络
//发红包算法,金额参数以分为单位
public static List<Integer> divideRedPackage(Integer totalAmount, Integer totalPeopleNum){
List<Integer> amountList = new ArrayList<Integer>();
Integer restAmount = totalAmount;
Integer restPeopleNum = totalPeopleNum;
Random random = new Random();
for(int i=0; i<totalPeopleNum-1; i++){
//随机范围:[1,剩余人均金额的两倍),左闭右开
int amount = random.nextInt(restAmount / restPeopleNum * 2 - 1) + 1;
restAmount -= amount;
restPeopleNum --;
amountList.add(amount);
}
amountList.add(restAmount);
return amountList;
}
public static void main(String[] args){
List<Integer> amountList = divideRedPackage(5000, 30);
for(Integer amount : amountList){
System.out.println("抢到金额:" + new BigDecimal(amount).divide(new BigDecimal(100)));
}
}
C/C++:
#include <stdio.h>
#include <stdlib.h>
void money();
void money(double TotalAmount, int TotalPeople)
{
double restAmount = TotalAmount;
int restPeople = TotalPeople;
for (int i = 0; i < TotalPeople - 1; ++i)
{
int amount = rand() % ((int)((restAmount / restPeople) * 2) * 100);
restAmount -= amount / 100;
--restPeople;
printf("抢到金额:%.2f\n", (double)amount / 100);
}
printf("抢到金额:%.2f\n", (double)restAmount / 100);
}
int main(void)
{
money(50, 30);
return 0;
}
Python:
import random
def money(TotalMoney,TotalPeople):
restMoney = TotalMoney
restPeople = TotalPeople
for i in range(TotalPeople - 1):
mount = round(random.uniform(0.01,(restMoney / restPeople) * 2 - 0.01),2)
restMoney -= mount
restPeople -= 1
print("抢到金额:"+str(mount))
print("抢到金额:"+str(round(restMoney,2)))
money(50,30)
本篇博客就到此结束了,其实生活中处处充满着知识,就看你有没有一颗爱钻研的心!