蓄水池采样算法-Reservoir Sampling

前言

在刷Leetcode的过程种,遇到过不少类似的问题:给出一个链表,如何从中随机获取一个节点?
直观的解法是把链表转换为List,或者获取其长度,再用Random解决。那么假如不能使用额外空间以及不允许事先获取其长度呢?一边扫描一边随机采样,这就是Reservoir Sampling能做到的。
事实上,Reservoir Sampling可以用来解决n个元素里面随机抽取k个,乃至于支持不平均的随机权重,不过先让我们看看一个最简单的例子吧。

举例分析

[1->2->3]包含3个元素,够简单了吧。每个元素应该有1/3的几率被抽中。当然了事先我们不知道有3个元素。
首先指向1。假如后面没有了,那这自然没别的选择。而我们看到后面还有一个2.
假如只考虑1->2,那么也就是有1/2的几率往后走一步:
a) 留在1。那么问题在于1只能看到2,而2后面还可能有东西,所以需要两个指针,一个遍历链表,一个指向我们所取值的位置。这样,我们就能看到3,而到了3,每个元素概率变为1/3,也就是说假如继续留在1的概率是x,那么1/2 * x = 1/3,x=2/3,也就是说1/3的概率这时候进到2.
b)进到2,这样3来了以后,留在2的几率也应该是1/3。这有两种可能:一是第一步1进2,然后留在2;二是第一步留在1,然后再进到2。假如留在2的概率是x,那么有1/2*x+1/2*(1-2/3)=1/3,x=1/3.
而1和2的概率搞定了,那3的概率自然也搞定了。

推广I

现在我们尝试将其推广到一般情况。假如我们已经有办法从n个里面随机挑了,也就是说每个元素几率是1/n,现在来了一个新元素,要想办法让每个元素几率成为1/(n+1)。
根据上面的分析,假如现在还留在1,那么本身概率是1/n,然后要继续留下来的概率要为n/(n+1),这样最终留下了的概率才是1/n * n/(n+1) = 1/(n+1)。
对于2,受到1的影响,同样假如留下的概率是x,有x/n + 1/(n(n+1)) = 1/(n+1)得x=(n-1)/(n+1)。
假如对于i,其留下的概率是(n+1-i)/(n+1),没留下即往前一步的概率则是i/(n+1)。
可以验证:对于i+1,那么取值为它的概率P=(1/n)*P(留在i+1)+(1/n)*P(没留在i)=(1/n)*P(留在i+1)+i/(n(n+1))=1/(n+1)可得x=(n-i)/(n+1),符合之前的公式。

算法

题目
整理一下之前的内容,我们需要一个指针指向当前选择的节点i,一个指针来遍历并记录当前长度n,然后每当长度增加时进行一个判断,即(n+1-i)/(n+1)概率不动,否则i指向下一个节点。当遍历完成时,返回指向的节点即可。时间复杂度为O(n)。代码如下:

class Solution {

    private final ListNode head;
    private final Random random = new Random();

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

    public int getRandom() {
        int count = 1, i = 1;
        ListNode res = head, cur = head;
        while (cur.next != null) {
            count++;
            boolean stay = hit((double) (count - i) / count);
            if (!stay) {
                res = res.next;
                i++;
            }
            cur = cur.next;
        }
        return res.val;
    }

    private boolean hit(double chance) {
        return random.nextDouble() <= chance;
    }
}

推广II

上面说的是从n个抽1个元素,现在尝试推广到k个元素(1<=k<=n)。
显然每个元素应该有k/n的几率被抽中。操作如下:

  • 首先抽出1~k;
  • 对于k+1,以k/(k+1)的概率选择它,然后再与前k个中随机的一个元素置换;
  • 对于k+i,以k/(k+i)的概率选择它,然后再与前k个中随机的一个元素置换;
  • 持续进行直至k+i=n。前k个元素即为所求。

k=1正是我们之前分析的情况,当然这里具体操作还是不一样,不过本质还是一致的。
假设我们已经处理好了k+i-1,现在来到k+i。预期结果是每个元素有k/(k+i)的几率被选择。
那么对于之前的元素x,其在这一轮后被选择的概率为
P=P(x之前就被选择)*P(x这一轮没有被换出去)
=[k/(k+i-1)] * [1-P(x这轮被换出去)]
=[k/(k+i-1)] * [1-P(选中k+i)*P(与x置换)]
=[k/(k+i-1)] *[1-k/(k+i)*(1/k)]=k/(k+i)
使用这种方法得到的k=1的代码:

    public int getRandom() {
        int count = 1;
        ListNode res = head, cur = head;
        while (cur.next != null) {
            count++;
            cur = cur.next;
            boolean chosen = hit((double) 1 / count);
            if (chosen)  res = cur;
        }
        return res.val;
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • 在一个给定长度的数组中随机等概率抽取一个数据很容易,但如果面对的是长度未知的海量数据流呢?蓄水池采样(Reserv...
    Astolfo阅读 635评论 0 0
  • 解决问题:在不知道数据规模n的前提下,实现以的概率去数据进行采样思路:首先设计容量为k的容器C,把前k个元素放入,...
    HamletSunS阅读 357评论 0 0
  • 给定一个数据流,数据流长度N很大,且N直到处理完所有数据之前都不可知,请问如何在只遍历一遍数据(O(N))的情况下...
    清晨我上马阅读 354评论 0 0
  • 问题描述 给出一个数据流,这个数据流的长度很大或者未知(内存无法一次性容纳下),并且对该数据流中数据只能访问一次。...
    wengle阅读 471评论 0 1
  • 问题描述: “给出一个数据流,这个数据流的长度很大或者未知。并且对该数据流中数据只能访问一次。请写出一个随机选择算...
    网虫子阅读 714评论 0 0