前言

微信群发红包,大家都玩过。这边想着自己实现一个微信群发红包分配算法。

红包分配的核心逻辑

1.红包数量严格按照用户输入的数量生成,确保分配的红包数量与用户期望一致。

2.红包金额分配过程中不出现负数,每个红包金额均不小于设定的最小值,保障公平性和合理性。

3.总金额校验确保红包分配后,所有红包金额的总和与用户输入的总金额完全一致,无误差或遗漏。

4.每个红包金额尽量随机

代码实现

package demo.code.utils;

import java.util.Arrays;

import java.util.Random;

public class RedEnvelopeDistributor {

/**

* 分发红包算法

*

* @param totalAmount 总金额(单位:分)

* @param num 红包个数

* @return 每个红包金额的数组(单位:分)

*/

public static int[] distribute(int totalAmount, int num) {

// 如果总金额小于红包个数,抛出异常,避免每个红包金额不足1分

if (totalAmount < num) {

throw new RuntimeException("金额小于红包数");

}

// 每个红包能获取的最小金额是总金额平均值的十分之一

int minAmount = totalAmount / num / 10;

// 确保最小金额至少为1分,避免出现金额为0的情况

if (minAmount < 1) {

minAmount = 1;

}

// 存放每个红包金额的数组

int[] result = new int[num];

// 随机数生成器

Random random = new Random();

// 剩余总金额和剩余红包个数

int remainingAmount = totalAmount;

int remainingNum = num;

// 遍历分配前 n-1 个红包

for (int i = 0; i < num - 1; i++) {

// 计算当前可分配的最大金额,保证其他红包至少能分到 minAmount

int maxAmount = (remainingAmount - remainingNum * minAmount) / 2;

// 如果 maxAmount 小于或等于 minAmount,则直接分配 minAmount

// 否则在 [minAmount, maxAmount] 范围内随机分配

int amount = maxAmount <= minAmount ? minAmount : minAmount + random.nextInt(maxAmount - minAmount + 1);

// 将生成的金额分配给当前红包

result[i] = amount;

// 更新剩余金额和剩余红包个数

remainingAmount -= amount;

remainingNum--;

}

// 最后一个红包分配剩余的全部金额,确保金额总数不变

result[num - 1] = remainingAmount;

// 打乱红包金额顺序,让分配结果看起来更随机

shuffle(result);

// 返回分配后的红包金额数组

return result;

}

/**

* 使用 Fisher-Yates 洗牌算法打乱红包金额顺序

* @param shuffleNums 需要打乱的数组

*/

public static void shuffle(int[] shuffleNums) {

Random random = new Random();

// 遍历数组,逐个交换位置

for (int i = 0; i < shuffleNums.length; i++) {

// 生成一个当前索引到数组末尾范围内的随机索引

final int j = i + random.nextInt(shuffleNums.length - i);

// 交换 i 和 j 位置的值

int temp = shuffleNums[i];

shuffleNums[i] = shuffleNums[j];

shuffleNums[j] = temp;

}

}

public static void main(String[] args) {

int totalAmount = 1000; // 总金额 1000分(10元)

int num = 30; // 红包个数 30个

// 调用分发方法,分配红包

int[] envelopes = distribute(totalAmount, num);

// 输出分配结果(单位:分)

System.out.println("红包分配结果(分):" + Arrays.toString(envelopes));

// 计算并输出红包总金额,验证是否与输入金额一致

System.out.println("总金额(分):" + Arrays.stream(envelopes).sum());

// 将分配结果排序后输出,便于观察红包分布情况

Arrays.sort(envelopes);

System.out.println(Arrays.toString(envelopes));

}

}

运行效果

红包分配结果(分):[3, 24, 10, 4, 12, 188, 4, 7, 4, 190, 3, 23, 42, 4, 96, 9, 3, 64, 54, 3, 3, 10, 95, 25, 86, 3, 3, 19, 3, 6]

总金额(分):1000

[3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 6, 7, 9, 10, 10, 12, 19, 23, 24, 25, 42, 54, 64, 86, 95, 96, 188, 190]

代码分析

minAmount红包最小金额

这边一开始是使用1分钱,就是保证红包最少的金额。后面发现效果不是很好,特别出现比较大的金额数量。这边就做了处理,让最小金额是输入金额平均数的10倍。这样范围会比较均衡一些,不会出现大量1分钱。

maxAmount红包最大金额

// 计算当前可分配的最大金额,保证其他红包至少能分到 minAmount

int maxAmount = (remainingAmount - remainingNum * minAmount) / 2;

当前取得红包金额,会影响后续金额数量。有一个前提是要留给后面足够得金额,不能太少。

amount 红包金额

// 如果 maxAmount 小于或等于 minAmount,则直接分配 minAmount

// 否则在 [minAmount, maxAmount] 范围内随机分配

int amount = maxAmount <= minAmount ? minAmount : minAmount + random.nextInt(maxAmount - minAmount + 1);

这边为了处理输入金额和红包个数接近情况,出现负数,那么直接分配最小金额

Fisher-Yates 洗牌算法

这边加入洗牌算法,是为了让红包更加随机。小红包和大红包得分布,不会挤在一起。

总结

简单实现了红包分配算法,这边也有个问题。就是会出现比较多得最小金额个数,这个大家可以优化这个问题