蓄水池抽样算法(Reservoir Sampling)
字数 1346 2025-11-05 23:47:54

蓄水池抽样算法(Reservoir Sampling)

问题描述
蓄水池抽样算法用于解决这样一种场景:当数据流(或链表)的长度N非常大,且无法一次性加载到内存时,如何等概率地随机抽取k个元素(k≥1)。该算法保证每个元素被选中的概率均为k/N,且只需遍历数据一次,空间复杂度仅为O(k)。


核心思想与步骤(以k=1为例)
当只需随机选取1个元素时,算法步骤如下:

  1. 初始化:将结果变量res初始化为第一个元素。
  2. 遍历处理:对于第i个元素(i从1开始计数):
    • 以概率 1/i 决定是否用当前元素替换res。
    • 具体操作:生成一个[0,1)范围内的随机数r,若r < 1/i,则更新res为当前元素。
  3. 终止:遍历结束后,res即为随机选取的结果。

为什么概率是1/N?

  • 第1个元素:被选中的概率 = 1(初始化选中) × [未被第2个元素替换的概率] × ... × [未被第N个元素替换的概率]
    = 1 × (1 - 1/2) × (1 - 1/3) × ... × (1 - 1/N)
    = 1 × (1/2) × (2/3) × ... × ((N-1)/N) = 1/N。
  • 第i个元素:被选中的概率 = 1/i(在第i步被选中) × [未被后续元素替换的概率]
    = 1/i × (1 - 1/(i+1)) × ... × (1 - 1/N) = 1/i × (i/(i+1)) × ... × ((N-1)/N) = 1/N。

推广到k≥1的情况
当需要抽取k个元素时,算法步骤如下:

  1. 初始化:将前k个元素直接放入蓄水池(结果数组res)。
  2. 遍历处理:对于第i个元素(i从k+1开始):
    • 以概率 k/i 决定是否将当前元素加入蓄水池。
    • 若决定加入,则从蓄水池中随机替换掉一个元素(等概率选择1到k的下标)。
  3. 终止:遍历结束后,蓄水池中的k个元素即为随机抽样结果。

为什么每个元素被选中的概率是k/N?

  • 对于前k个元素:
    初始时肯定被选中。第i个元素(i>k)加入时,某个特定初始元素被替换的概率 = (k/i) × (1/k) = 1/i。
    因此该初始元素最终保留的概率 = ∏(i=k+1 to N) (1 - 1/i) = k/N。
  • 对于第i个元素(i>k):
    被选中的概率 = k/i(在第i步被选中) × ∏(j=i+1 to N) [1 - (k/j) × (1/k)](未被后续替换)
    = k/i × ∏(j=i+1 to N) (1 - 1/j) = k/i × (i/N) = k/N。

示例演示(k=2, 数据流[a,b,c,d])

  1. 初始化:res = [a, b](i=2)。
  2. 处理c(i=3):以概率2/3决定是否加入。若加入,随机替换res中的一个位置。
  3. 处理d(i=4):以概率2/4决定是否加入。若加入,随机替换res中的一个位置。
  4. 最终res中的两个元素各自被选中的概率均为2/4=1/2。

关键应用场景

  • 大数据流中的随机抽样(如日志抽样、推荐系统候选集生成)。
  • 无法预知总长度的链表随机节点选取(如随机播放歌曲列表)。

通过这种巧妙的概率设计,蓄水池抽样算法用极小空间实现了海量数据流的公平随机抽样。

蓄水池抽样算法(Reservoir Sampling) 问题描述 蓄水池抽样算法用于解决这样一种场景:当数据流(或链表)的长度N非常大,且无法一次性加载到内存时,如何 等概率 地随机抽取k个元素(k≥1)。该算法保证每个元素被选中的概率均为k/N,且只需遍历数据一次,空间复杂度仅为O(k)。 核心思想与步骤(以k=1为例) 当只需随机选取1个元素时,算法步骤如下: 初始化 :将结果变量res初始化为第一个元素。 遍历处理 :对于第i个元素(i从1开始计数): 以概率 1/i 决定是否用当前元素替换res。 具体操作:生成一个 [ 0,1)范围内的随机数r,若r < 1/i,则更新res为当前元素。 终止 :遍历结束后,res即为随机选取的结果。 为什么概率是1/N? 第1个元素:被选中的概率 = 1(初始化选中) × [ 未被第2个元素替换的概率] × ... × [ 未被第N个元素替换的概率 ] = 1 × (1 - 1/2) × (1 - 1/3) × ... × (1 - 1/N) = 1 × (1/2) × (2/3) × ... × ((N-1)/N) = 1/N。 第i个元素:被选中的概率 = 1/i(在第i步被选中) × [ 未被后续元素替换的概率 ] = 1/i × (1 - 1/(i+1)) × ... × (1 - 1/N) = 1/i × (i/(i+1)) × ... × ((N-1)/N) = 1/N。 推广到k≥1的情况 当需要抽取k个元素时,算法步骤如下: 初始化 :将前k个元素直接放入蓄水池(结果数组res)。 遍历处理 :对于第i个元素(i从k+1开始): 以概率 k/i 决定是否将当前元素加入蓄水池。 若决定加入,则从蓄水池中 随机替换掉一个元素 (等概率选择1到k的下标)。 终止 :遍历结束后,蓄水池中的k个元素即为随机抽样结果。 为什么每个元素被选中的概率是k/N? 对于前k个元素: 初始时肯定被选中。第i个元素(i>k)加入时,某个特定初始元素被替换的概率 = (k/i) × (1/k) = 1/i。 因此该初始元素最终保留的概率 = ∏(i=k+1 to N) (1 - 1/i) = k/N。 对于第i个元素(i>k): 被选中的概率 = k/i(在第i步被选中) × ∏(j=i+1 to N) [ 1 - (k/j) × (1/k) ](未被后续替换) = k/i × ∏(j=i+1 to N) (1 - 1/j) = k/i × (i/N) = k/N。 示例演示(k=2, 数据流[ a,b,c,d]) 初始化:res = [ a, b ](i=2)。 处理c(i=3):以概率2/3决定是否加入。若加入,随机替换res中的一个位置。 处理d(i=4):以概率2/4决定是否加入。若加入,随机替换res中的一个位置。 最终res中的两个元素各自被选中的概率均为2/4=1/2。 关键应用场景 大数据流中的随机抽样(如日志抽样、推荐系统候选集生成)。 无法预知总长度的链表随机节点选取(如随机播放歌曲列表)。 通过这种巧妙的概率设计,蓄水池抽样算法用极小空间实现了海量数据流的公平随机抽样。