Java实现4种微信抢红包算法

概述

  1. 保证每个红包最少分得0.01
  2. 保证每个红包金额概率尽量均衡
  3. 所有红包累计金额登于红包总金额

红包算法

剩余金额随机法

  以1010个红包为例,去除每个红包的最小金额后,红包剩余9.9元;

  1. 第一个红包在[0,9.9]范围随机,假设随机得1元,则第一个红包金额为1.1元,红包剩余8.9元。
  2. 第二个红包在[0,8.9]范围随机,假设随机得1.5元,则第二个红包金额为1.6元,红包剩余7.4元。
  3. 第三个红包在[0,7.4]范围随机,假设随机得0.5元,则第三个红包金额为0.6元,红包剩余6.9元。
  4. 以此类推。
public static void main(String[] args) {
    //初始化测试场景
    BigDecimal[][] rrr = {
            {new BigDecimal("0.1"), new BigDecimal("10")},
            {new BigDecimal("1"), new BigDecimal("10")},
            {new BigDecimal("100"), new BigDecimal("10")},
            {new BigDecimal("1000"), new BigDecimal("10")}
    };
    BigDecimal min = new BigDecimal("0.01");
    //测试个场景
    for (BigDecimal[] decimals : rrr) {
        final BigDecimal amount = decimals[0];
        final BigDecimal num = decimals[1];
        System.out.println(amount + "元" + num + "个人抢=======================================================");
        test1(amount, min, num);
    }
}

private static void test1(BigDecimal amount, BigDecimal min, BigDecimal num) {
    BigDecimal remain = amount.subtract(min.multiply(num));
    final Random random = new Random();
    final BigDecimal hundred = new BigDecimal("100");
    BigDecimal sum = BigDecimal.ZERO;
    BigDecimal redpeck;
    for (int i = 0; i < num.intValue(); i++) {
        final int nextInt = random.nextInt(100);
        if (i == num.intValue() - 1) {
            redpeck = remain;
        } else {
            redpeck = new BigDecimal(nextInt).multiply(remain).divide(hundred, 2, RoundingMode.FLOOR);
        }
        if (remain.compareTo(redpeck) > 0) {
            remain = remain.subtract(redpeck);
        } else {
            remain = BigDecimal.ZERO;
        }
        sum = sum.add(min.add(redpeck));
        System.out.println("第" + (i + 1) + "个人抢到红包金额为:" + min.add(redpeck));
    }
    System.out.println("校验每个红包累计额度是否等于红包总额结果:" + (amount.compareTo(sum) == 0));
}

  测试结果如下:可以看出此算法有明显缺陷,即:先领取的红包金额较大,后领取的红包金额较小,这就使得抢红包便的不公平。

0.110个人抢=======================================================1个人抢到红包金额为:0.012个人抢到红包金额为:0.013个人抢到红包金额为:0.014个人抢到红包金额为:0.015个人抢到红包金额为:0.016个人抢到红包金额为:0.017个人抢到红包金额为:0.018个人抢到红包金额为:0.019个人抢到红包金额为:0.0110个人抢到红包金额为:0.01
校验每个红包累计额度是否等于红包总额结果:true
110个人抢=======================================================1个人抢到红包金额为:0.092个人抢到红包金额为:0.283个人抢到红包金额为:0.194个人抢到红包金额为:0.205个人抢到红包金额为:0.156个人抢到红包金额为:0.027个人抢到红包金额为:0.038个人抢到红包金额为:0.019个人抢到红包金额为:0.0110个人抢到红包金额为:0.02
校验每个红包累计额度是否等于红包总额结果:true
10010个人抢=======================================================1个人抢到红包金额为:19.992个人抢到红包金额为:29.583个人抢到红包金额为:38.274个人抢到红包金额为:11.855个人抢到红包金额为:0.116个人抢到红包金额为:0.137个人抢到红包金额为:0.018个人抢到红包金额为:0.019个人抢到红包金额为:0.0310个人抢到红包金额为:0.02
校验每个红包累计额度是否等于红包总额结果:true
100010个人抢=======================================================1个人抢到红包金额为:60.002个人抢到红包金额为:695.543个人抢到红包金额为:229.724个人抢到红包金额为:8.955个人抢到红包金额为:0.296个人抢到红包金额为:4.647个人抢到红包金额为:0.018个人抢到红包金额为:0.699个人抢到红包金额为:0.1210个人抢到红包金额为:0.04
校验每个红包累计额度是否等于红包总额结果:true

二倍均值法(微信红包采用此法)

  还是以1010个红包为例,去除每个红包的最小金额后,红包剩余9.9元,二倍均值计算公式:2 * 剩余金额/剩余红包数

  1. 第一个红包在[0,1.98]范围随机,假设随机得1.9,则第一个红包金额为2.0,红包剩余8元。
  2. 第二个红包在[0,2]范围随机,假设随机的1元,则第二个红包金额为1.1元,红包剩余7元。
  3. 第三个红包在[0,2]范围随机,假设随机的0.5元,则第三个红包金额为0.6元,红包剩余5.5元。
  4. 以此类推。
public static void main(String[] args) {
    //初始化测试场景
    BigDecimal[][] rrr = {
            {new BigDecimal("0.1"), new BigDecimal("10")},
            {new BigDecimal("1"), new BigDecimal("10")},
            {new BigDecimal("100"), new BigDecimal("10")},
            {new BigDecimal("1000"), new BigDecimal("10")}
    };
    BigDecimal min = new BigDecimal("0.01");
    //测试个场景
    for (BigDecimal[] decimals : rrr) {
        final BigDecimal amount = decimals[0];
        final BigDecimal num = decimals[1];
        System.out.println(amount + "元" + num + "个人抢=======================================================");
        test2(amount, min, num);
    }
}


private static void test2(BigDecimal amount,BigDecimal min ,BigDecimal num){
    BigDecimal remain = amount.subtract(min.multiply(num));
    final Random random = new Random();
    final BigDecimal hundred = new BigDecimal("100");
    final BigDecimal two = new BigDecimal("2");
    BigDecimal sum = BigDecimal.ZERO;
    BigDecimal redpeck;
    for (int i = 0; i < num.intValue(); i++) {
        final int nextInt = random.nextInt(100);
        if(i == num.intValue() -1){
            redpeck = remain;
        }else{
            redpeck = new BigDecimal(nextInt).multiply(remain.multiply(two).divide(num.subtract(new BigDecimal(i)),2,RoundingMode.CEILING)).divide(hundred,2, RoundingMode.FLOOR);
        }
        if(remain.compareTo(redpeck) > 0){
            remain = remain.subtract(redpeck);
        }else{
            remain = BigDecimal.ZERO;
        }
        sum = sum.add(min.add(redpeck));
        System.out.println("第"+(i+1)+"个人抢到红包金额为:"+min.add(redpeck));
    }
    System.out.println("校验每个红包累计额度是否等于红包总额结果:"+amount.compareTo(sum));
}

  测试结果如下:此算法很好的保证了抢红包几率大致均等。

0.110个人抢=======================================================1个人抢到红包金额为:0.012个人抢到红包金额为:0.013个人抢到红包金额为:0.014个人抢到红包金额为:0.015个人抢到红包金额为:0.016个人抢到红包金额为:0.017个人抢到红包金额为:0.018个人抢到红包金额为:0.019个人抢到红包金额为:0.0110个人抢到红包金额为:0.01
校验每个红包累计额度是否等于红包总额结果:true
10010个人抢=======================================================1个人抢到红包金额为:6.202个人抢到红包金额为:7.093个人抢到红包金额为:10.624个人抢到红包金额为:18.685个人抢到红包金额为:18.746个人抢到红包金额为:2.327个人抢到红包金额为:15.448个人抢到红包金额为:5.439个人抢到红包金额为:15.1610个人抢到红包金额为:0.32
校验每个红包累计额度是否等于红包总额结果:true
110个人抢=======================================================1个人抢到红包金额为:0.082个人抢到红包金额为:0.053个人抢到红包金额为:0.174个人抢到红包金额为:0.175个人抢到红包金额为:0.086个人抢到红包金额为:0.067个人抢到红包金额为:0.188个人抢到红包金额为:0.109个人抢到红包金额为:0.0210个人抢到红包金额为:0.09
校验每个红包累计额度是否等于红包总额结果:true
100010个人抢=======================================================1个人抢到红包金额为:125.992个人抢到红包金额为:165.083个人抢到红包金额为:31.904个人抢到红包金额为:94.785个人抢到红包金额为:137.796个人抢到红包金额为:88.897个人抢到红包金额为:156.448个人抢到红包金额为:7.979个人抢到红包金额为:151.0110个人抢到红包金额为:40.15
校验每个红包累计额度是否等于红包总额结果:true

整体随机法

  还是以1010个红包为例,随机10个数,红包金额公式为:红包总额 * 随机数/随机数总和,假设10个随机数为[5,9,8,7,6,5,4,3,2,1],10个随机数总和为50

  1. 第一个红包10*5/50,得1元。
  2. 第二个红包10*9/50,得1.8元。
  3. 第三个红包10*8/50,得1.6元。
  4. 以此类推。
public static void main(String[] args) {
    //初始化测试场景
    BigDecimal[][] rrr = {
            {new BigDecimal("0.1"), new BigDecimal("10")},
            {new BigDecimal("1"), new BigDecimal("10")},
            {new BigDecimal("100"), new BigDecimal("10")},
            {new BigDecimal("1000"), new BigDecimal("10")}
    };
    BigDecimal min = new BigDecimal("0.01");
    //测试个场景
    for (BigDecimal[] decimals : rrr) {
        final BigDecimal amount = decimals[0];
        final BigDecimal num = decimals[1];
        System.out.println(amount + "元" + num + "个人抢=======================================================");
        test3(amount, min, num);
    }
}

private static void test3(BigDecimal amount,BigDecimal min ,BigDecimal num){
    final Random random = new Random();
    final int[] rand = new int[num.intValue()];
    BigDecimal sum1 = BigDecimal.ZERO;
    BigDecimal redpeck ;
    int sum = 0;
    for (int i = 0; i < num.intValue(); i++) {
        rand[i] = random.nextInt(100);
        sum += rand[i];
    }
    final BigDecimal bigDecimal = new BigDecimal(sum);
    BigDecimal remain = amount.subtract(min.multiply(num));
    for (int i = 0; i < rand.length; i++) {
        if(i == num.intValue() -1){
            redpeck = remain;
        }else{
            redpeck = remain.multiply(new BigDecimal(rand[i])).divide(bigDecimal,2,RoundingMode.FLOOR);
        }
        if(remain.compareTo(redpeck) > 0){
            remain = remain.subtract(redpeck);
        }else{
            remain = BigDecimal.ZERO;
        }
        sum1= sum1.add(min.add(redpeck));
        System.out.println("第"+(i+1)+"个人抢到红包金额为:"+min.add(redpeck));
    }

    System.out.println("校验每个红包累计额度是否等于红包总额结果:"+(amount.compareTo(sum1)==0));
}

  测试结果如下:此算法随机性较大。

0.110个人抢=======================================================1个人抢到红包金额为:0.012个人抢到红包金额为:0.013个人抢到红包金额为:0.014个人抢到红包金额为:0.015个人抢到红包金额为:0.016个人抢到红包金额为:0.017个人抢到红包金额为:0.018个人抢到红包金额为:0.019个人抢到红包金额为:0.0110个人抢到红包金额为:0.01
校验每个红包累计额度是否等于红包总额结果:true
10010个人抢=======================================================1个人抢到红包金额为:2.352个人抢到红包金额为:14.123个人抢到红包金额为:5.744个人抢到红包金额为:6.615个人抢到红包金额为:0.656个人抢到红包金额为:10.977个人抢到红包金额为:9.158个人抢到红包金额为:7.939个人抢到红包金额为:1.3110个人抢到红包金额为:41.17
校验每个红包累计额度是否等于红包总额结果:true
110个人抢=======================================================1个人抢到红包金额为:0.102个人抢到红包金额为:0.023个人抢到红包金额为:0.124个人抢到红包金额为:0.035个人抢到红包金额为:0.056个人抢到红包金额为:0.127个人抢到红包金额为:0.068个人抢到红包金额为:0.019个人抢到红包金额为:0.0410个人抢到红包金额为:0.45
校验每个红包累计额度是否等于红包总额结果:true
100010个人抢=======================================================1个人抢到红包金额为:148.962个人抢到红包金额为:116.573个人抢到红包金额为:80.494个人抢到红包金额为:32.485个人抢到红包金额为:89.396个人抢到红包金额为:65.607个人抢到红包金额为:20.778个人抢到红包金额为:16.039个人抢到红包金额为:36.7910个人抢到红包金额为:392.92
校验每个红包累计额度是否等于红包总额结果:true

割线法

  还是以1010个红包为例,在(0,10)范围随机9个间隔大于等于0.01数,假设为[1,1.2,2,3,4,5,6,7,8]

  1. 第一个红包得1
  2. 第二个红包得0.2
  3. 第三个红得0.8元。
  4. 以此类推。
public static void main(String[] args) {
    //初始化测试场景
    BigDecimal[][] rrr = {
            {new BigDecimal("0.1"), new BigDecimal("10")},
            {new BigDecimal("1"), new BigDecimal("10")},
            {new BigDecimal("100"), new BigDecimal("10")},
            {new BigDecimal("1000"), new BigDecimal("10")}
    };
    BigDecimal min = new BigDecimal("0.01");
    //测试个场景
    for (BigDecimal[] decimals : rrr) {
        final BigDecimal amount = decimals[0];
        final BigDecimal num = decimals[1];
        System.out.println(amount + "元" + num + "个人抢=======================================================");
        test3(amount, min, num);
    }
}

private static void test3(BigDecimal amount,BigDecimal min ,BigDecimal num){
    final Random random = new Random();
    final int[] rand = new int[num.intValue()];
    BigDecimal sum1 = BigDecimal.ZERO;
    BigDecimal redpeck ;
    int sum = 0;
    for (int i = 0; i < num.intValue(); i++) {
        rand[i] = random.nextInt(100);
        sum += rand[i];
    }
    final BigDecimal bigDecimal = new BigDecimal(sum);
    BigDecimal remain = amount.subtract(min.multiply(num));
    for (int i = 0; i < rand.length; i++) {
        if(i == num.intValue() -1){
            redpeck = remain;
        }else{
            redpeck = remain.multiply(new BigDecimal(rand[i])).divide(bigDecimal,2,RoundingMode.FLOOR);
        }
        if(remain.compareTo(redpeck) > 0){
            remain = remain.subtract(redpeck);
        }else{
            remain = BigDecimal.ZERO;
        }
        sum1= sum1.add(min.add(redpeck));
        System.out.println("第"+(i+1)+"个人抢到红包金额为:"+min.add(redpeck));
    }

    System.out.println("校验每个红包累计额度是否等于红包总额结果:"+(amount.compareTo(sum1)==0));
}

  测试结果如下:此算法随机性较大,且性能不好

0.110个人抢=======================================================1个人抢到红包金额为:0.012个人抢到红包金额为:0.013个人抢到红包金额为:0.014个人抢到红包金额为:0.015个人抢到红包金额为:0.016个人抢到红包金额为:0.017个人抢到红包金额为:0.018个人抢到红包金额为:0.019个人抢到红包金额为:0.0110个人抢到红包金额为:0.01
校验每个红包累计额度是否等于红包总额结果:true
10010个人抢=======================================================1个人抢到红包金额为:19.842个人抢到红包金额为:2.733个人抢到红包金额为:8.954个人抢到红包金额为:14.105个人抢到红包金额为:18.606个人抢到红包金额为:3.667个人抢到红包金额为:9.178个人抢到红包金额为:15.499个人抢到红包金额为:5.6110个人抢到红包金额为:1.85
校验每个红包累计额度是否等于红包总额结果:true
110个人抢=======================================================1个人抢到红包金额为:0.022个人抢到红包金额为:0.283个人抢到红包金额为:0.034个人抢到红包金额为:0.025个人抢到红包金额为:0.116个人抢到红包金额为:0.237个人抢到红包金额为:0.188个人抢到红包金额为:0.099个人抢到红包金额为:0.0310个人抢到红包金额为:0.01
校验每个红包累计额度是否等于红包总额结果:true
100010个人抢=======================================================1个人抢到红包金额为:69.282个人抢到红包金额为:14.683个人抢到红包金额为:373.164个人抢到红包金额为:274.735个人抢到红包金额为:30.776个人抢到红包金额为:30.767个人抢到红包金额为:95.558个人抢到红包金额为:85.209个人抢到红包金额为:10.4410个人抢到红包金额为:15.43
校验每个红包累计额度是否等于红包总额结果:true

PS

微信红包的架构设计简介
java实现qq抢红包_Redis秒杀实战
实用算法原理及简介