红包算法分析
在知乎和一些其他的博客中,很多人都提出了自己的观点,我选取其中的一个算法进行分析。比如,有人认为,抢红包的额度是从0.01到剩余平均值*N(N是一个系数,决定最大的红包值)之间,比如一共发了10块钱,发了10个红包:第一个人可以拿到(0.01~1*N)之间的一个红包值,当然为了确保所有人至少有1分钱拿,不能前几个人就把钱拿光了,因此需要有一个判断算法。举个例子,如果每个人都拿了自己的最大值:
public class test{
public static void main(String[] args)
{
float num=10,N=1.9f;//开始将系数设为1.9
int people=10;
for(int i=0;i<10;i++)
{
System.out.println("the number"+people+" can get "+ num/people*N);
num=num-num/people*N;
people--;
}
System.out.println("there remain"+num);
}
}
结果10个人结束后剩余的钱为负数,说明最后一个人达不到他的最大值,如果我们将系数改为2.3:
我们发现 number1 成了负数,显然这也不合适,所以在使用系数的算法时,添加判断金额是否合理是很重要的。
2.2 设置金额的上下限
我们的红包金额至少有0.01,最多有200(单笔红包金额不会超过200),如果随机出来的数据不在这个范围内显然就是错误的,分别设置红包的最小金额和最大金额:
private static final float MINMONEY =0.01f;
private static final float MAXMONEY =200f;
2.3 判断金额是否合法
如果金额超过了最大值或者小于最小值,显然就错了,同时在每个人暂定随机金额后也要判断剩余的金额是否合法:
private boolean isRight(float money,int count)
{
double avg = money/count;
if(avg<MINMONEY){
return false;
}
else if(avg>MAXMONEY)
{
return false;
}
return true;
}
2.4 随机产生红包
现在用随机的方法产生一个在 MINMONEY 到 MAXMONEY 之间的红包,并且在产生了这个红包之后需要分析数值是否合理,剩余的钱是不是变负数了,如果产生的红包不合适,就重新产生分配方案。另外,需要考虑红包的值,如果这次的数据太小,下次就产生一个较大的值,反之亦然。
private float randomRedPacket(float money,float mins,float maxs,int count)
{
if(count==1)
{
return (float)(Math.round(money*100))/100;
}
if(mins == maxs)
{
return mins;//如果最大值和最小值一样,就返回mins
}
float max = maxs>money?money:maxs;
float one = ((float)Math.random()*(max-mins)+mins);
one = (float)(Math.round(one*100))/100;
float moneyOther = money - one;
if(isRight(moneyOther,count-1))
{
return one;
}
else{
//重新分配
float avg = moneyOther / (count-1);
if(avg<MINMONEY)
{
return randomRedPacket(money,mins,one,count);
}else if(avg>MAXMONEY)
{
return randomRedPacket(money,one,maxs,count);
}
}
return one;
}
2.5 实现红包分配
为了避免一个红包占用大量的资金,设定非最后一个红包的最大金额,可以设置为平均值的N倍,基于前面的方法就可以实现红包的分配了。
private static final float TIMES = 2.1f;
public List<Integer> splitRedPackets(float money,int count)
{
if(!isRight(money,count))
{
return null;
}
List<Float> list = new ArrayList<Float>();
float max = (float)(money*TIMES/count);
max = max>MAXMONEY?MAXMONEY:max;
for(int i=0;i<count;i++)
{
float one = randomRedPacket(money,MINMONEY,max,count-i);
list.add(one);
money-=one;
}
return list;
}
2.6 编写主函数
在main函数中实现具体的操作:
public static void main(String[] args) {
RedPacketUtil util = new RedPacketUtil();
System.out.println(util.splitRedPackets(200, 100));
}
}
完整代码获取方式见1.5节,在Xfce终端中运行代码:
javac RedPacketUtil.java
java RedPacketUtil
在实验楼中的运行结果如下图:
三、分析数据
3.1 增加模拟次数
这个红包分配算法是网友们猜测的,暂时不去考虑微信是不是这么做的,我们可以就这个算法分析一下,当模拟次数在500次,1000次时,每个人能获取的金额,我们可以来获取这个计算结果。在上节实验中的完整代码中导入工具类库java.util.Scanner:
import java.util.Scanner;
同时将 main 函数改写一下,其中 num 为模拟的次数,NUM 为人数:
public static void main(String[] args) {
System.out.println("please input a number");
Scanner sc=new Scanner(System.in);
int num = sc.nextInt(),count = num,NUM=20;
float[] index = new float[NUM];
while(num-->0)
{
RedPacketUtil util = new RedPacketUtil();
List<Float> temp = util.splitRedPackets(20, NUM);
for(int i=0;i<temp.size();i++)
{
index[i]+=temp.get(i);
}
}
for(int i=0;i<NUM;i++)
{
System.out.println("the number "+i+" can get "+(index[i]/count));
}
}
我们模拟的是20块红包,分给20个人,运行10次,得到的结果:
可以看到数据没有什么特别的,但是当运行的次数达到1000000次的时候:
做出来的效果像这样的,这只是我们的算法会这样:
3.2 如果将人数增加,又会有什么变化呢?
修改 main 函数中的代码,将人数增加到200人,模拟100000次:
大概可以看出,偏后的人拿到大金额红包的概率会小很多。