蓄水池抽样算法 Reservoir Sampling

2018-03-05 14:06:40

问题描述:给出一个数据流,这个数据流的长度很大或者未知。并且对该数据流中数据只能访问一次。请写出一个随机选择算法,使得数据流中所有数据被选中的概率相等。

问题求解:如果是长度已知或者有限的问题,那么可以使用朴素的方法,先遍历一遍得到的长度。然后在得到长度后可以使用随机算法得到一个随机的index。

但是本题已经明确指出数据流长度很大或者未知,也就是说只能遍历一次,而且要保证每个数被挑选的概率相等。

标准解法是使用Reservoir Sampling算法,该算法由Knuth的学生在斯坦福读计算机博士时想出来

算法描述:

相关问题:

  • 382. Linked List Random Node

问题描述

问题求解:

public class Solution {
    ListNode head;
    Random rand;

    /** @param head The linked list's head.
        Note that the head is guaranteed to be not null, so it contains at least one node. */
    public Solution(ListNode head) {
        this.head = head;
        this.rand = new Random();
    }

    /** Returns a random node's value. */
    public int getRandom() {
        int k = 1;
        ListNode cur = head;
        List<Integer> reservoir = new ArrayList<>();
        int i = 0;
        while (i < k && cur != null) {
            reservoir.add(cur.val);
            cur = cur.next;
            i++;
        }
        i++;
        while (cur != null) {
            if (rand.nextInt(i) < k) {
                reservoir.set(rand.nextInt(k), cur.val);
            }
            i++;
            cur = cur.next;
        }
        return reservoir.get(0);
    }
}
  • 398. Random Pick Index

问题描述:

问题求解:

如果仅存在一个数,那么将之index返回,如果存在多个数,其index的返回值需要是等概率的,也就是说对于k个相同的数,我们需要每个index的返回概率为1/k。根据蓄水池算法,我们首先要建立一个大小为1的池子,然后对每个出现的target的index以当前出现个数的概率选择他,然后从池中随机挑选一个数来与它交换,由于池中仅有一个值,因此只需要将res的值变为挑选值即可。

public class RandomPickIndex {
    int[] nums;
    Random ran;

    public RandomPickIndex(int[] nums) {
        this.nums = nums;
        ran = new Random();
    }

    public int pick(int target) {
        int res = -1;
        int cnt = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != target) continue;
            if (ran.nextInt(++cnt) == 0) res = i;
        }
        return res;
    }
}
原文地址:https://www.cnblogs.com/hyserendipity/p/8508759.html