蓄水池算法
1.规则
假设有k个中奖位置, 且i 从 1开始
1.当 i <= k 时,直接进入中奖区域
2.当 i > k 时,在 [1, i] 范围取一个随机数
- 如果 rand <= k,则 luckList[rand] = arr[i]
- 如果 rand > k,则取路人甲区域
2.证明
证明:所有元素的中奖率时 k / n
假设有1000个球,5个中将位置,即证明每个元素的中奖概率是 5 / 1000
1.对于第三个球来说,它的中奖概率是:
3号入场:1
4号入场:1
5号入场: 1
6号入场: 1 * 5/6
7号入场:1 * 5/6 * 6/7
i 号入场:1 * 1 * 5/6 * 6/7 * ... * (i - 1) / i = 5 / i
n号入场:5 / n
2.对于第8个球来说,它的中将概率是:
8号入场:5/8
9号入场:5/8 * 8/9
10号入场:5/8 * 8/9 * 9/10
i号入场:5/8 * 8/9 * 9/10 * ... * (i - 1) / i = 5 / i
n号入场:5 / n
3.代码
public class Lottery<T> {
// 中奖位有多少
int luckCnt = 0;
// 保存中将人的名单
List<T> luckList = new ArrayList<>();
// 当前已经有多少人进行过抽奖
int applyCnt = 0;
public Lottery(int luckCnt) {
this.luckCnt = luckCnt;
}
public void add(T item) {
applyCnt++;
if (applyCnt <= luckCnt) {
luckList.add(item);
return;
}
int rand = new Random().nextInt(applyCnt);
if (rand < luckCnt) {
luckList.set(rand, item);
}
}
public static void main(String[] args) {
int[] ids = new int[1000];
for (int i = 0; i < 100000; i++) {
Lottery<Integer> lottery = new Lottery<>(5);
for (int j = 0; j < 1000; j++) {
lottery.add(j);
}
List<Integer> luckList = lottery.luckList;
luckList.forEach(id -> {
ids[id]++;
});
}
System.out.println(Arrays.toString(ids));
}
}
4.分布式
规则:
- 让3台机器去扛总流量,每台机器依然有k个抽奖位置
- 用户随机出来要给数字,如果 1 <= rand <= 1000 则去红色机器;如果 2001 <= rand <= 7000,则去绿色机器。每台机器的前5个代表中奖
- 结算时,在[1, 7000]随机出一个数字,看看落在哪台机器上,就代表这台机器上面有用户中奖,具体哪个用户中奖呢?再从[1, 5]随机出一个数字
概率:以红色机器为例,5/1000 * 1000/7000 * 1/5 * (5次) = 5/7000