蓄水池抽样算法

蓄水池抽样(Reservoir Sampling)是一种从数据流中随机选出若干个不重复数据的抽样方法。
这个方法的特点是:

  1. 数据流的长度很大且未知,不能将这些数据全部放进内存。
  2. 数据只能被遍历一次而不能重复遍历。

简单算法

维护一个一个大小为k的蓄水池,最初包含数据流中前k个数据。然后遍历数据流的剩余数据。设数组索引从1开始,当遍历到第 i 个数据时,生成一个随机数字 j,随机数的范围为 1 <= j <= i。

  • 如果 j<=k, 说明这个数据被选中放进蓄水池中,将它放进蓄水池中第 j 个位置中,原来在第j个位置的数据被替换出蓄水池。
  • 如果 j>k,则放弃这个索引为j的数据。

重复上述操作,直至所有数据被遍历。可以证明,每个数据被选中的几率为 1/N。

题目实例

382. 链表随机节点

给定一个单链表,随机选择链表的一个节点,并返回相应的节点值。保证每个节点被选的概率一样。
进阶:
如果链表十分大且长度未知,如何解决这个问题?你能否使用常数级空间复杂度实现?

思路

题目要求返回一个随机结点的值,故定义一个大小为1的蓄水池reservoir,按照上面的方法,当遍历到第 i 个结点时,生成一个随机数 rand,其中 0 <= rand <= i(下标从0开始),如果rand的值为0,就将该结点放进蓄水池中。遍历所有结点,最后得到的结点就是随机结点。

代码

class Solution {
    private ListNode list;
    public Solution(ListNode head) {
        this.list = head;
    }

    public int getRandom() {
        int count = 0;
        ListNode reservoir = this.list;
        ListNode cur = list;
        while(cur != null){
            int rand = (int) (Math.random() * (count+1));
            if(rand == 0)
                resevoir = cur;
            cur = cur.next;
            count++;
        }
        return resevoir.val;
    }
}

398. 随机数索引

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

思路

382. 链表随机节点不同的是,这里生成随机数的区间不是0~i,而是应该记录当前出现的值为target的元素个数n,进而生成随机数0<=rand<=n。

代码

    class Solution {
        int[] data;
        public Solution(int[] nums) {
            data = nums;
        }

        public int pick(int target) {
            int i = 0;
            while(data[i] != target)
                i++;
            int reservoir = i, count = 0;
            while(i < data.length){
                if(data[i] == target){
                    int rand = (int)(Math.random() * (count+1));
                    if(rand == 0)
                        reservoir = i;
                    count++;
                }
                i++;
            }
            return reservoir;
        }
    }

最优算法

该算法改进了上述简单算法,该算法计算在下一个数据进入蓄水池之前应该丢弃多少个数据。关键点在于这个数字遵循一个几何分布,于是可以在常数时间内计算得出。

(* S has items to sample, R will contain the result *)
ReservoirSample(S[1..n], R[1..k])
  // fill the reservoir array
  for i = 1 to k
      R[i] := S[i]

  (* random() generates a uniform (0,1) random number *)
  W := exp(log(random())/k)

  while i <= n
      i := i + floor(log(random())/log(1-W)) + 1
      if i <= n
          (* replace a random item of the reservoir with item i *)
          R[randomInteger(1,k)] := S[i]  // random index between 1 and k, inclusive
          W := W * exp(log(random())/k)

该算法的时间复杂度为O(k(1+log(n/k)))。
参考资料:https://en.wikipedia.org/wiki/Reservoir_sampling#cite_note-vitter-1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值