前言

在刷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;

}

蓄水池采样算法的python实现_蓄水池采样算法-Reservoir Sampling相关推荐

  1. 蓄水池采样算法的python实现_蓄水池采样算法的python实现_蓄水池抽样及实现

    蓄水池抽样(Reservoir Sampling)是一个很有趣的问题,它能够在o(n)时间内对n个数据进行等概率随机抽取,例如:从1000个数据中等概率随机抽取出100个.另外,如果数据集合的量特别大 ...

  2. 蓄水池采样算法的python实现_蓄水池抽样及实现

    蓄水池抽样(Reservoir Sampling)是一个很有趣的问题,它能够在o(n)时间内对n个数据进行等概率随机抽取,例如:从1000个数据中等概率随机抽取出100个.另外,如果数据集合的量特别大 ...

  3. 蓄水池采样算法的python实现_蓄水池抽样算法(Reservoir Sampling)

    蓄水池抽样算法(Reservoir Sampling) 许多年以后,当听说蓄水池抽样算法时,邱simple将会想起,那个小学数学老师带他做"小明对水池边加水边放水,求何时能加满水" ...

  4. Python+Django+Mysql简单在线课程推荐系统 基于用户、项目、内容的协同过滤推荐算法 SimpleOnlineCourseCFRSPyth python实现协同过滤推荐算法实现源代码下载

    Python+Django+Mysql简单在线课程推荐系统 基于用户.项目.内容的协同过滤推荐算法 SimpleOnlineCourseCFRSPyth python实现协同过滤推荐算法实现源代码下载 ...

  5. Python+Django+Mysql个性化二手车推荐系统 汽车推荐系统 基于用户、项目、内容的协同过滤推荐算法 WebCarCFRSPython python实现协同过滤推荐算法实现源代码下载

    Python+Django+Mysql个性化二手车推荐系统 汽车推荐系统 基于用户.项目.内容的协同过滤推荐算法 WebCarCFRSPython python实现协同过滤推荐算法实现源代码下载 一. ...

  6. 人工鱼群算法python代码_人工鱼群算法python_鱼群算法 - Brillou的个人空间 - OSCHINA - 中文开源技术交流社区......

    本算法是参照李晓磊博士的论文实现的,详细的算法原理可阅读<一种新型的智能优化方法_人工鱼群算法_李晓磊> 算法基于鱼群的生存行为:在一片水域中,鱼存在的数目最多的地方就是本水域中富含营养物 ...

  7. 正向最大匹配算法 python代码_中文分词算法之最大正向匹配算法(Python版)

    最大匹配算法是自然语言处理中的中文匹配算法中最基础的算法,分为正向和逆向,原理都是一样的. 正向最大匹配算法,故名思意,从左向右扫描寻找词的最大匹配. 首先我们可以规定一个词的最大长度,每次扫描的时候 ...

  8. matlab算法用python做_机器学习笔记—朴素贝叶斯算法实现(matlab/python)

    原理知道一百遍不如自己动手写一遍,当然,现在基本上不需要自己来写算法的底层code了,各路大神们已经为我等凡夫俗子写好了,直接调用就行. 这里介绍在MATLAB中和Python中应用贝叶斯算法的小例子 ...

  9. 排序算法python实现_合并排序算法– Java,C和Python实现

    排序算法python实现 Merge sort is one of the most efficient sorting algorithms. It works on the principle o ...

  10. 中文分词算法python代码_中文分词算法之最大正向匹配算法(Python版)

    最大匹配算法是自然语言处理中的中文匹配算法中最基础的算法,分为正向和逆向,原理都是一样的. 正向最大匹配算法,故名思意,从左向右扫描寻找词的最大匹配. 首先我们可以规定一个词的最大长度,每次扫描的时候 ...

最新文章

  1. 一个很棒的PHP缓存类,收藏下
  2. yili邮箱服务器配置,手把手教 个人SMTP服务器的配置 -电脑资料
  3. 小程序开发语言python_小程序是用什么语言开发的?5种最佳语言分享
  4. php excel 设置常规_php实现的操作excel类详解
  5. Leetcode445 两数相加||(单链表)
  6. linux下source filename,./filename,. filename,......
  7. 习题4.5 顺序存储的二叉树的最近的公共祖先问题 (25 分)
  8. 一些提高工作效率的黑科技软件
  9. Swift -- AVPlayerViewController播放本地视频并且通知重复播放
  10. GAN网络理解与实现
  11. MyEclipse10破解
  12. 线程的终止的4种方式
  13. php 将图片裁剪成圆形图片,[PHP]把图片切成圆形
  14. 数字温度传感器DS18B20中文资料(含读写程序)-
  15. 固态硬盘是什么接口_SATA M.2 PCIe?一分钟教你认识固态硬盘接口
  16. 在CentOS 7.6(1810)下自定义自己的登录欢迎信息(修改motd文件)
  17. Ubuntu安装虚拟机
  18. 程序员编程技术迅速提高终极攻略
  19. python function gamma_Python math gamma()用法及代码示例
  20. 2019最新前端薪资报告来啦!前端的工资到底有多高?其实真相是这样的......

热门文章

  1. Multisim基础 共阴极数码管是com_k,共阳极数码管是com_a
  2. 罗振宇跨年演讲4小时讲了啥?记住这4733字
  3. Docker修改默认网段
  4. RC电路充放电时间的计算
  5. BES2300x笔记(4) -- TWS组对与蓝牙配对(Peer or Pair傻傻分不清)
  6. php zip压缩包下载
  7. Win10系统下配置virtualenv
  8. 【深度学习概念】感受野
  9. 政务系统信息网络安全的风险评估
  10. 条形码标签,实现产品追溯的最佳工具