蓄水池抽样算法

目录

一、什么是蓄水池抽样算法

二、蓄水池抽样算法的证明

三、例题


一、什么是蓄水池抽样算法

        我们现在来考虑一种出现在大数据流中的随机抽样问题,即:当内存无法加载全部数据时,如何从包含未知大小的数据流中随机选取k个数据,并且要保证每个数据被抽取到的概率相等。

        蓄水池抽样算法就是用来解决该类问题的算法。

二、蓄水池抽样算法的证明

K=1

也就是说,我们每次只能读一个数据。

假设数据流含有N个数,我们知道如果要保证所有的数被抽到的概率相等,那么每个数抽到的概率应该为 \frac{1}{N}

那如何保证呢?

先说方案:每次只保留一个数,当遇到第 i 个数时,以 \frac{1}{i} 的概率保留它,{\color{Red} }\frac{i-1}{i}的概率保留原来的数。

举例说明: 1 - 10

  • 遇到1,概率为 1,保留第一个数。······因为第一次时,第一个数一定会被选择
  • 遇到2,概率为 \frac{1}{2},这个时候,1和2各 \frac{1}{2} 的概率被保留。 ······要么选择之前保留的1,要么选择新来的2,此时我们假设选择的仍然是之前保留的1
  • 遇到3,3被保留的概率为 \frac{1}{3},(之前剩下的数假设1被保留),\frac{2}{3} 的概率 1 被保留,(此时1被保留的总概率为 \frac{2}{3} * \frac{1}{2} = \frac{1}{3})。
  • 遇到4,4被保留的概率为 \frac{1}{4},(之前剩下的数假设1被保留),\frac{3}{4} 的概率 1 被保留,(此时1被保留的总概率为 \frac{3}{4} * \frac{2}{3} * \frac{1}{2} = \frac{1}{4})。

以此类推,每个数被保留的概率都是\frac{1}{N}


 当 K=m(m>1)

也就是说,我们每次能读m个数据。

对于K>1的情况,我们可以采取类似的策略:

假设数据流中总共有N个数据,要保证每条数据被抽取到的概率相等,那么每个数被抽取到的概率必然是\frac{K}{N}

  • 第一次,对于前k个数 n_{1},n_{2},n_{3},...,n_{k},我们一定会保留下来,所以他们的概率都为1。
  • 对于第k+1个数n_{k+1},以\frac{k}{k+1} 的概率保留它(这里只是指本次保留下来),那么前k个数中的任意一个数被保留的概率可以表示为:P(1~k中某个数被保留) = P(上一轮这个数被保留) * P(第k+1个数被丢弃) + P(上一轮这个数被保留) * P(第k+1个数没有被丢弃) * P(且前k个数中这个数没有被替换),                                                                                                                   即P(1~k中某个数被保留) = 1*\frac{1}{k+1}+1*\frac{k}{k+1}*\frac{k-1}{k}=\frac{k}{k+1}
  • 对于第k+2个数n_{k+2},以\frac{k}{k+2}的的概率保留它(这里只是指本次保留下来),那么前k+1个数中的任意一个数被保留的概率可以表示为:P(1~k+1中某个数被保留) = P(上一轮这个数被保留) * P(第k+2个数被丢弃) + P(上一轮这个数被保留) * P(第k+2个数没有被丢弃) * P(且前k个数中这个数没有被替换),                                                                                                            即P(1~k+1中某个数被保留) = \frac{k}{k+1}*\frac{2}{k+2}+\frac{k}{k+1}*\frac{k}{k+2}*\frac{k-1}{k}=\frac{k}{k+2}
  • ... ...
  • 对于第i(i>k)个数 n_{i},以 \frac{k}{i} 的概率保留它,那么前i-1个数中的某一个数被保留的概率为       P = \frac{k}{i-1}*\frac{i-k}{i} + \frac{k}{i-1}*\frac{k}{i}*\frac{k-1}{k}=\frac{k}{i}

即对于前k个数全部保留,对于第i(i>k)个数,以 \frac{k}{i} 的概率保留第i个数,并以 \frac{1}{k} 的概率与前面已选择的k个数中的任意一个替换。

证明完毕,上述的数学证明就是蓄水池抽样算法思想的体现。

三、例题

给你一个单链表,随机选择链表的一个节点,并返回相应的节点值。每个节点 被选中的概率一样 。

实现 Solution 类:

  • Solution(ListNode head) 使用整数数组初始化对象。
  • int getRandom() 从链表中随机选择一个节点并返回该节点的值。链表中所有节点被选中的概率相等。

 Demo

class Solution {
public:
    ListNode* head;
    Solution(ListNode* head) {
        this->head = head;
    }
    
    int getRandom() {
        ListNode* cur = head;
        int ans = 0;
        int num = 1;
        while(cur != nullptr)
        {
            if(rand() % num == 0)
            {
                ans = cur->val;
            }
            cur = cur->next;
            num++;
        }
        return ans;
    }
};

给定一个可能含有重复元素的整数数组,要求随机输出给定的数字的索引。 您可以假设给定的数字一定存在于数组中。

注意:
数组大小可能非常大。 使用太多额外空间的解决方案将不会通过测试。

示例:

int[] nums = new int[] {1,2,3,3,3};
Solution solution = new Solution(nums);

// pick(3) 应该返回索引 2,3 或者 4。每个索引的返回概率应该相等。
solution.pick(3);

// pick(1) 应该返回 0。因为只有nums[0]等于1。
solution.pick(1);
class Solution {
private:
    vector<int> nums;
public:
    Solution(vector<int>& nums):nums(nums) {}
    
    int pick(int target) {
        int ans = 0;
        int count = 0;
        for(int i=0;i<nums.size();++i)
        {
            if(nums[i] == target)
            {
                ++count;
                if(rand()%count == 0)   ans = i;
            }
        }
        return ans;
    }
};

 

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值