蓄水池算法

180 阅读1分钟

蓄水池算法

1.规则

假设有k个中奖位置, 且i 从 1开始

1.当 i <= k 时,直接进入中奖区域

2.当 i > k 时,在 [1, i] 范围取一个随机数

  • 如果 rand <= k,则 luckList[rand] = arr[i]
  • 如果 rand > k,则取路人甲区域

1648649458224.png

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.分布式

1648652243986.png

规则:

  1. 让3台机器去扛总流量,每台机器依然有k个抽奖位置
  2. 用户随机出来要给数字,如果 1 <= rand <= 1000 则去红色机器;如果 2001 <= rand <= 7000,则去绿色机器。每台机器的前5个代表中奖
  3. 结算时,在[1, 7000]随机出一个数字,看看落在哪台机器上,就代表这台机器上面有用户中奖,具体哪个用户中奖呢?再从[1, 5]随机出一个数字

概率:以红色机器为例,5/1000 * 1000/7000 * 1/5 * (5次) = 5/7000