背景
在 n 个数中,如何让抽到每一个数的概率相等
可以产生一个随机数,n 个数则在 [1,n] 中产生一个随机数即可
但如果涉及到外部数据,即一次并不能全部读取 n 个数(n 未知);例如一共
1000 个数据,一次只能加载 10 个,那么再每 10 个加载一次,总共加载 100
次的过程中如何保证最终取到的值满足 1/n
使用水塘抽样算法可以解决
在这里,还分为 K = 1(只抽一个)和 K > 1(抽取多个)
K = 1
分片读取,第 i 个数的概率应该是 1/ i
论证公式
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public static int poolSampling(final List<List<Integer>> listList) { final Random random = new Random();
int count = 0; int res = 0; for (final List<Integer> list : listList) { for (final Integer i : list) { count++; if (random.nextInt(count) == 0) { res = i; } } } return res; }
|
K > 1
和 K = 1 思路一致,区别在于 1/i 的概率升级为
k/i
论证公式
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static int[] poolSampling(final List<List<Integer>> listList, final int k) { final Random random = new Random();
int count = 0; final int[] res = new int[k]; for (final List<Integer> list : listList) { for (final Integer i : list) { count++; final int resIndex = random.nextInt(count); if (resIndex < k) { res[resIndex] = i; } } } return res; }
|
s
参考
随机算法:水塘抽样算法 -
知乎 (zhihu.com)
398.
随机数索引 - 力扣(LeetCode) (leetcode-cn.com)
382.
链表随机节点 - 力扣(LeetCode) (leetcode-cn.com)