【算法34】蓄水池抽样算法 (Reservoir Sampling Algorithm)
蓄水池抽样算法简介
蓄水池抽样算法随机算法的一种,用来从 N 个样本中随机选择 K 个样本,其中 N 非常大(以至于 N 个样本不能同时放入内存)或者 N 是一个未知数。其时间复杂度为 O(N),包含下列步骤 (假设有一维数组 S, 长度未知,需要从中随机选择 k 个元素, 数组下标从 1 开始), 伪代码如下:
array R[k]; // result
integer i, j; // fill the reservoir array
for each i in to k do
R[i] := S[i]
done; // replace elements with gradually decreasing probability
for each i in k+ to length(S) do
j := random(, i); // important: inclusive range
if j <= k then
R[j] := S[i]
fi
done
算法首先创建一个长度为 k 的数组(蓄水池)用来存放结果,初始化为 S 的前 k 个元素。然后从 k+1 个元素开始迭代直到数组结束,在 S 的第 i 个元素,算法生成一个随机数 $j \in [1, i]$, 如果 j <= k, 那么蓄水池的第 j 个元素被替换为 S 的第 i 个元素。
算法的正确性证明
定理:该算法保证每个元素以 k / n 的概率被选入蓄水池数组。
证明:首先,对于任意的 i,第 i 个元素进入蓄水池的概率为 k / i;而在蓄水池内每个元素被替换的概率为 1 / k; 因此在第 i 轮第j个元素被替换的概率为 (k / i ) * (1 / k) = 1 / i。 接下来用数学归纳法来证明,当循环结束时每个元素进入蓄水池的概率为 k / n.
假设在 (i-1) 次迭代后,任意一个元素进入 蓄水池的概率为 k / (i-1)。有上面的结论,在第 i 次迭代时,该元素被替换的概率为 1 / i, 那么其不被替换的概率则为 1 - 1/i = (i-1)/i;在第i 此迭代后,该元素在蓄水池内的概率为 k / (i-1) * (i-1)/i = k / i. 归纳部分结束。
因此当循环结束时,每个元素进入蓄水池的概率为 k / n. 命题得证。
算法的C++实现
实现部分比较简单,关键点也有详细的注释,为了验证算法的正确性,对[1,10]的数组,随机选择前五个进行验证,运行10000次后,每个数字出现的频率应该是基本相等的,算法的运行结果也证实了这一点,如下图所示。
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <ctime>
using namespace std; /**
* Reservoir Sampling Algorithm
*
* Description: Randomly choose k elements from n elements ( n usually is large
* enough so that the full n elements may not fill into main memory)
* Parameters:
* v - the original array with n elements
* n - the length of v
* k - the number of choosen elements
*
* Returns:
* An array with k elements choosen from v
*
* Assert:
* k <= n
*
* Author: python27
* Date: 2015/07/14
*/
vector<int> ReservoirSampling(vector<int> v, int n, int k)
{
assert(v.size() == n && k <= n); // init: fill the first k elems into reservoir
vector<int> reservoirArray(v.begin(), v.begin() + k); int i = ;
int j = ;
// start from the (k+1)th element to replace
for (i = k; i < n; ++i)
{
j = rand() % (i + ); // inclusive range [0, i]
if (j < k)
{
reservoirArray[j] = v[i];
}
} return reservoirArray;
} int main()
{
vector<int> v(, );
for (int i = ; i < ; ++i) v[i] = i + ; srand((unsigned int)time(NULL));
// test algorithm RUN_COUNT times
const int RUN_COUNT = ;
int cnt[] = {};
for (int k = ; k <= RUN_COUNT; ++k)
{
cout << "Running Count " << k << endl; vector<int> samples = ReservoirSampling(v, , ); for (size_t i = ; i <samples.size(); ++i)
{
cout << samples[i] << " ";
cnt[samples[i]]++;
}
cout << endl;
} // output frequency stats
cout << "*************************" << endl;
cout << "Frequency Stats" << endl;
for (int num = ; num < ; ++num)
{
cout << num << " : \t" << cnt[num] << endl;
}
cout << "*************************" << endl; return ;
}
运行结果如下:
算法的局限性
蓄水池算法的基本假设是总的样本数很多,不能放入内存,暗示了选择的样本数 k 是一个与 n 无关的常数。然而在实际的应用中,k 常常与 n 相关,比如我们想要随机选择1/3 的样本 (k = n / 3),这时候就需要别的算法或者分布式的算法。
参考文献
【1】 Wikipedia:Reservoir Sampling
【算法34】蓄水池抽样算法 (Reservoir Sampling Algorithm)的更多相关文章
- 【数据结构与算法】蓄水池抽样算法(Reservoir Sampling)
问题描述 给定一个数据流,数据流长度 N 很大,且 N 直到处理完所有数据之前都不可知,请问如何在只遍历一遍数据(O(N))的情况下,能够随机选取出 m 个不重复的数据. 比较直接的想法是利用随机数算 ...
- 蓄水池抽样算法 Reservoir Sampling
2018-03-05 14:06:40 问题描述:给出一个数据流,这个数据流的长度很大或者未知.并且对该数据流中数据只能访问一次.请写出一个随机选择算法,使得数据流中所有数据被选中的概率相等. 问题求 ...
- Reservoir Sampling - 蓄水池抽样算法&&及相关等概率问题
蓄水池抽样——<编程珠玑>读书笔记 382. Linked List Random Node 398. Random Pick Index 从n个数中随机选取m个 等概率随机函数面试题总结 ...
- leetcode398 and leetcode 382 蓄水池抽样算法
382. 链表随机节点 给定一个单链表,随机选择链表的一个节点,并返回相应的节点值.保证每个节点被选的概率一样. 进阶:如果链表十分大且长度未知,如何解决这个问题?你能否使用常数级空间复杂度实现? 示 ...
- Reservoir Sampling 蓄水池抽样算法,经典抽样
随机读取数据,如何保证真随机是不可能的,因为计算机的随机函数是伪随机的. 但是在不考虑计算机随机函数的情况下,如何保证数据的随机采样呢? 1.系统提供的shuffle函数 C++/Java都提供有sh ...
- Spark MLlib之水塘抽样算法(Reservoir Sampling)
1.理解 问题定义可以简化如下:在不知道文件总行数的情况下,如何从文件中随机的抽取一行? 首先想到的是我们做过类似的题目吗?当然,在知道文件行数的情况下,我们可以很容易的用C运行库的rand函数随机的 ...
- Reservoir Sampling - 蓄水池抽样问题
问题起源于编程珠玑Column 12中的题目10,其描述如下: How could you select one of n objects at random, where you see the o ...
- Reservoir Sampling - 蓄水池抽样
问题起源于编程珠玑Column 12中的题目10,其描述如下: How could you select one of n objects at random, where you see the o ...
- 算法系列:Reservoir Sampling
copyright © 1900-2016, NORYES, All Rights Reserved. http://www.cnblogs.com/noryes/ 欢迎转载,请保留此版权声明. -- ...
随机推荐
- LinuxI/O 性能分析
.I/O linux 命令: ostat 监视I/O子系统 iostat [参数][时间][次数] 通过iostat方便查看CPU.网卡.tty设备.磁盘.CD-ROM 等等设备的活动情况, 负载信息 ...
- dom node 查找父级parentNode
var o = document.querySelectorAll("a[href='baidu.com']"); var p = o[o.length-1];console.lo ...
- Spring Boot logback
前言 今天来介绍下spring Boot如何配置日志logback,我刚学习的时候,是带着下面几个问题来查资料的,你呢 如何引入日志? 日志输出格式以及输出方式如何配置? 代码中如何使用? 正文 Sp ...
- Java连接MySQL数据库及操作
Java操作MySQL数据库,需要驱动mysql-connector-java 来进行操作,去下载对应的jar包 一.导入需要的jar包 我用的是maven对包进行管理,在maven中添加如下内容 ...
- flush(), clear(), save()的简单解释
hibernate最新发布包的javadoc里对这三个方法的解释是: clear() :Completely clear the session.清空session,该清空操作只对于要保存的.删除的和 ...
- Python issubclass() 函数
Python issubclass() 函数 Python 内置函数 描述 issubclass() 方法用于判断参数 class 是否是类型参数 classinfo 的子类. 语法 以下是 iss ...
- Dice
Dice http://acm.hdu.edu.cn/showproblem.php?pid=5012 Time Limit: 2000/1000 MS (Java/Others) Memory ...
- 修改mysql数据库 允许远程访问
首先使用root身份进入MySQL mysql>use mysql; //进入mysql表 mysql> selecthost,user from user; //查看结果是不是root ...
- 通过html5 的EventSource来进行数据推送
以往我们要获取服务器的数据更新,一般通过ajax的定时请求,不过这样效率就低了.我们通过html5的EventSource可以很方便的获取服务器的数据更新,不过IE好像不支持. 例1如下: ind ...
- linux 下 php 安装 event
1.下载event源码包 https://pecl.php.net/package/event 如:event-2.0.4.tgz 2.解压 > tar zxvf event-2.0.4.tgz ...