Java带权重随机采样采用前缀和+二分查找:先构建权重前缀和数组,再生成[0,总权重)随机数并二分定位区间,高效省内存,适用于装备掉落等场景。

在 Java 中实现带权重的随机采样,核心思路是将概率映射为“权重区间”,再通过生成一个总权重范围内的随机数,查找其落入哪个区间——这本质上是前缀和 + 二分查找的经典应用。它比重复添加元素到列表(如把 70% 的权重展开成 70 个相同项)更省内存、更高效,尤其适合装备掉落这类权重固定但种类较多的场景。
1. 准备权重与物品映射关系
先定义装备等级及其对应权重(可为整数或浮点数)。推荐用整数避免浮点误差,比如:普通(50)、稀有(30)、史诗(15)、传说(5)。注意权重不要求归一化,总和也不必为 100。
用两个平行数组或一个对象数组保存数据:
String[] items = {"普通", "稀有", "史诗", "传说"};
int[] weights = {50, 30, 15, 5};
2. 构建前缀和数组
前缀和数组 prefixSum[i] 表示前 i+1 个权重的累计和。它让每个物品对应一段连续整数区间:
立即学习“Java免费学习笔记(深入)”;
- 普通:[0, 49] → 长度 50
- 稀有:[50, 79] → 长度 30
- 史诗:[80, 94] → 长度 15
- 传说:[95, 99] → 长度 5
代码实现:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
int[] prefixSum = new int[weights.length];
prefixSum[0] = weights[0];
for (int i = 1; i < weights.length; i++) {
prefixSum[i] = prefixSum[i - 1] + weights[i];
}
// 结果:[50, 80, 95, 100]
3. 生成随机数并二分定位
生成 [0, totalWeight) 范围内的随机整数(totalWeight = prefixSum[prefixSum.length - 1]),然后在 prefixSum 中找第一个 ≥ 该随机数的位置——这就是命中物品的下标。
Java 提供 Arrays.binarySearch,但需注意其返回值逻辑:找到则返回索引,未找到返回 -(insertionPoint) - 1。更清晰的做法是手写二分或使用 TreeSet.floorEntry(若改用 TreeMap 存储累积边界)。
简洁安全的手写二分示例:
Random rand = new Random();
int total = prefixSum[prefixSum.length - 1];
int r = rand.nextInt(total); // [0, total)
<p>// 二分查找第一个 >= r 的 prefixSum 索引
int left = 0, right = prefixSum.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (prefixSum[mid] <= r) {
left = mid + 1;
} else {
right = mid;
}
}
int index = left; // 即命中的装备下标
String dropped = items[index];</p>4. 封装为可复用工具方法
把上述逻辑封装成泛型方法,支持任意类型物品和整数权重:
public static <T> T weightedRandom(T[] items, int[] weights, Random rand) {
if (items.length == 0 || weights.length != items.length)
throw new IllegalArgumentException();
<pre class='brush:java;toolbar:false;'>int[] prefix = new int[weights.length];
prefix[0] = weights[0];
for (int i = 1; i < weights.length; i++) {
prefix[i] = prefix[i-1] + weights[i];
}
int total = prefix[prefix.length - 1];
int r = rand.nextInt(total);
int lo = 0, hi = prefix.length;
while (lo < hi) {
int m = lo + (hi - lo) / 2;
if (prefix[m] <= r) lo = m + 1;
else hi = m;
}
return items[lo];}
// 使用示例: String result = weightedRandom(items, weights, new Random());
不复杂但容易忽略:权重全为 0 时需额外校验;若需线程安全,建议复用同一个 Random 实例(如 ThreadLocalRandom.current());实际项目中可进一步封装为 Builder 模式支持动态权重更新。

















