java实现按比重抽奖
java实现按比重抽奖
方案
对于按比重抽奖的更优方案,可以考虑以下几种方法:
- 轮盘抽奖(Roulette Wheel Selection)
- 原理:想象一个旋转的轮盘,每个奖品占据轮盘上与其权重成比例的区域。随机选择一个点并让其“落下”,落在哪个奖品区域就选中哪个奖品。
- 实现:可以通过累加权重来模拟轮盘,然后生成一个随机数来决定指针落在哪个区间。
- 优点:直观易懂,模拟了真实世界的轮盘抽奖。
- 缺点:在奖品和权重非常多时,性能可能不是最优的。
- Alias Method(别名采样法)
- 原理:通过预处理步骤,为每个奖品分配一个或多个“别名”,使得每个奖品或别名被选中的概率都是1/n(n是奖品的数量)。在抽奖时,随机选择一个奖品或别名即可。
- 实现:较为复杂,需要进行预处理来构建别名表。但一旦构建完成,抽奖操作的时间复杂度就是O(1)。
- 优点:抽奖操作非常快,适用于频繁抽奖的场景。
- 缺点:构建别名表的过程可能比较耗时,且当奖品或权重发生变化时需要重新构建。
- Binary Search(二分查找)
- 原理:将奖品按照权重排序(可以使用平衡二叉搜索树如红黑树或AVL树来维护排序),然后生成一个随机数,并使用二分查找来找到对应的奖品。
- 实现:需要维护一个排序的奖品列表或树结构。抽奖时,通过二分查找找到随机数对应的奖品。
- 优点:在奖品数量较多时,性能通常优于简单的累加权重方法。
- 缺点:需要维护排序结构,当奖品或权重发生变化时需要重新排序。
轮盘抽奖
示例
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
@Data
class Prize {
String name;
int weight; // 权重(比重)
public Prize(String name, int weight) {
this.name = name;
this.weight = weight;
}
// 可以添加getter和setter方法...
}
public class LotterySystem {
public static void main(String[] args) {
List<Prize> prizes = new ArrayList<>();
// prizes.add(new Prize("奖品A", 10)); // 假设奖品A的权重是10
// prizes.add(new Prize("奖品B", 10)); // 假设奖品A的权重是10
// prizes.add(new Prize("奖品C", 20)); // 假设奖品B的权重是20
// prizes.add(new Prize("奖品D", 20)); // 假设奖品B的权重是20
// prizes.add(new Prize("奖品E", 30)); // 假设奖品C的权重是30
// prizes.add(new Prize("奖品F", 10)); // 假设奖品A的权重是10
// 计算总权重
int totalWeight = 0;
for (Prize prize : prizes) {
totalWeight += prize.weight;
}
// 执行抽奖
List<Prize> resList = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
Prize winningPrize = drawLottery(prizes, totalWeight);
System.out.println("恭喜您获得:" + winningPrize.name);
resList.add(winningPrize);
}
Map<String, List<Prize>> map = resList.stream().collect(Collectors.groupingBy(Prize::getName));
for (Map.Entry<String, List<Prize>> entry : map.entrySet()) {
System.out.println(entry.getKey() + "-" + entry.getValue().size());
}
}
public static Prize drawLottery(List<Prize> prizes, int totalWeight) {
if (CollectionUtils.isEmpty(prizes) || totalWeight < 1) {
return null;
}
// 打乱奖品列表,确保权重计算不会受到原始顺序的影响
Collections.shuffle(prizes);
// 随机选择一个数字
Random random = new Random();
int randomNumber = random.nextInt(totalWeight) + 1; // 生成1到totalWeight之间的随机数
// 根据随机数选择奖品
int currentWeight = 0;
for (Prize prize : prizes) {
currentWeight += prize.weight;
if (randomNumber <= currentWeight) {
return prize;
}
}
// 如果所有奖品都没有被选中(理论上不应该发生),则返回一个默认奖品或抛出异常
return null; // 或抛出异常
}
}
关键点
-
随机数生成:生成的随机数是介于1和总权重之间的整数,它代表了“抽奖指针”的位置。
-
权重累加:通过累加奖品的权重,我们可以确定每个奖品对应的权重范围。随机数落在哪个范围内,就说明抽中了哪个奖品。
Binary Search(二分查找)
import java.util.Random;
public class WeightedLottery {
// 奖品类,包含奖品名称和权重
static class Prize {
String name;
int weight;
Prize(String name, int weight) {
this.name = name;
this.weight = weight;
}
@Override
public String toString() {
return "Prize{" +
"name='" + name + '\'' +
", weight=" + weight +
'}';
}
}
// 根据奖品列表和权重计算累积权重数组
static int[] calculateCumulativeWeights(Prize[] prizes) {
int totalWeight = 0;
for (Prize prize : prizes) {
totalWeight += prize.weight;
}
int[] cumulativeWeights = new int[prizes.length];
cumulativeWeights[0] = prizes[0].weight;
for (int i = 1; i < prizes.length; i++) {
cumulativeWeights[i] = cumulativeWeights[i - 1] + prizes[i].weight;
}
// 归一化累积权重(可选),使最后一个累积权重为totalWeight
for (int i = 0; i < cumulativeWeights.length; i++) {
cumulativeWeights[i] = cumulativeWeights[i] * totalWeight / cumulativeWeights[cumulativeWeights.length - 1];
}
return cumulativeWeights;
}
// 二分查找确定中奖的奖品
static Prize binarySearchForPrize(Prize[] prizes, int[] cumulativeWeights, int randomNumber) {
int left = 0;
int right = prizes.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (randomNumber < cumulativeWeights[mid]) {
right = mid - 1;
} else {
if (mid == prizes.length - 1 || randomNumber < cumulativeWeights[mid + 1]) {
return prizes[mid];
}
left = mid + 1;
}
}
// 如果没有找到(理论上不会发生,除非randomNumber超出范围),则返回null或抛出异常
return null;
}
public static void main(String[] args) {
Prize[] prizes = {
new Prize("奖品A", 1),
new Prize("奖品B", 2),
new Prize("奖品C", 3)
};
int[] cumulativeWeights = calculateCumulativeWeights(prizes);
Random random = new Random();
int randomNumber = random.nextInt(cumulativeWeights[cumulativeWeights.length - 1]) + 1; // 生成一个1到总权重之间的随机数
Prize winningPrize = binarySearchForPrize(prizes, cumulativeWeights, randomNumber);
System.out.println("中奖奖品: " + winningPrize);
}
}