Java 实现微信红包分配算法

红包算法分析

在知乎和一些其他的博客中,很多人都提出了自己的观点,我选取其中的一个算法进行分析。比如,有人认为,抢红包的额度是从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次:

此处输入图片的描述

大概可以看出,偏后的人拿到大金额红包的概率会小很多。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值