【原创】算法分享(4)Cardinality Estimate 基数计数概率算法

读过《编程珠玑》(<Programming Pearls>)的人应该还对开篇的Case记忆犹新,大概的场景是:

作者的一位在电话公司工作的朋友想要统计一段时间内不同的电话号码的个数,电话号码的数量很大,当时的内存很小,所以不能把所有的电话号码全部放到内存来去重统计,他的朋友很苦恼。

作者聪明的想到了用bit数组来解决问题,每个电话号码可以映射为bit数组的index,bit数组初始状态所有位为0,所有电话号码逐一处理:将bit数组对应位置为1,处理完之后统计bit数组中有多少个1即可。

示例:[0,1,0,0,0,1,0,...] 这个bit数组表示2和5存在

不得不说这种想法非常精妙,即减少了内存占用(8位电话号码如果全部放到内存需要381M(每个电话号码存成Integer占4Byte计算),而使用bit数组只需要11M),而且只需要两次循环就可以得到结果;

这是一个基数计数的问题,Cardinality:estimating the number of distinct elements.

1 Bitmap

上边提到的方式是使用bitmap,思路是将dataset中的每一个element映射到一个bit位,不允许冲突,所需要的内存空间大概为基数*1bit(上例中是100,000,000bit),并且计数精准;

但是当基数非常大时,即使bitmap内存也放不下!

好消息是如果不要求计数精准(允许一定范围内的误差),可以采用概率估算算法:

2 LC:Linear Counting

公式

其中:n是估算值,m是bitmap大小,Vn是bitmap中0出现的比率,比如0.1;

原理

使用固定大小的hashtable来存放dataset,可以想见:

  • dataset的基数越小,hashtable越空,当基数为0时hashtable所有的key都是空的
  • dataset的基数越大,hashtable越满,冲突也越多,当基数无穷大时hashtable所有的key都被占用并且每个key都有大量冲突

所以可以根据hashtable中key被占用的情况来估算dataset的基数,这里主要用到了对数曲线的特性(0<x<1这一段):

过程

初始化一个bitmap,所有位为0,dataset的每一个element都hash到bitmap的一个bit位,hash之后将对应的bit位置为1,hash允许冲突,最后根据bitmap中0的数量和bitmap大小由公式来估算基数;

注意:虽然LC用的也是bitmap,但是相比原始的bitmap算法,LC的bitmap大小可以比基数小很多,因为LC的映射允许冲突,另外可以设置bitmap大小来决定误差的大小;

参考:A linear-time probabilistic counting algorithm for database applications

https://wenku.baidu.com/view/9c4489ee0975f46527d3e1d1.html

3 LLC:LogLog Counting

公式


其中:m是桶的数量,M为桶的集合,k是用于分桶的位数,ρ为bit数组中第一个为1的下标即index,E是估算值;

原理

来看抛硬币的过程,抛硬币的过程是伯努利Bernoulli过程,每次的结果要么是0,要么是1,并且概率均为1/2,假设一次抛硬币game定义为抛到1为止,可能第一次就抛到1(P=0.5),也可能前边抛了i次0最后才抛到1(P=1-0.5^i);抛硬币game玩的次数越多,越容易出现前边很多次都是0的情况,这是因为开头连续出现的0的个数越多,出现概率越小,需要尝试伯努利过程的次数就越多,所以可以利用概率根据结果(开头出现0的个数,即i)来反推出条件(game玩了多少次,即n=2^i);

但是由于随机性的存在导致误差较大,所以通过将dataset分为m份,每份单独统计,最后取算数平均值的方式来降低随机性从而减小误差;

过程

dataset的每一个element先映射到一个bit数组,比如32位bit数组,将这个bit数组的1到k位的值作为桶的bucket_index(即第几个桶),将k+1到32位中第一个为1的index作为value放到桶中,如果桶里已经有value,桶会保存一个最大的value,数据集元素全部映射完之后,将所有桶的value取算数平均值,根据n=2^i,这样可以得到每个桶内的基数,再乘以m可以得到整个dataset的基数,公式最前边的α是修正参数;

比如k=2,则m=2^k=4,即4个桶,dataset中一个element映射的bit数组为[1,0,0,0,0,1,0,...],取前两位[1,0]对应的值是2,即第2个桶,取第3位之后的数组[0,0,0,1,0,...]可见第一个位1的index是3,将3放到桶2中,以此类推;

参考:Loglog Counting of Large Cardinalities

http://algo.inria.fr/flajolet/Publications/DuFl03-LNCS.pdf

4 HLLC:Hyper LogLog Counting

公式

原理

同LLC,只有一点不同:取均值的时候不使用算数平均数而改用调和平均数

参考:HyperLogLog: the analysis of a near-optimal cardinality estimation algorithm

http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf

5 最优实践

公式

过程

在大量实践中根据各个参数和结果的情况进行调优,综合使用HLLC和LC等算法

参考:

http://static.googleusercontent.com/media/research.google.com/en//pubs/archive/40671.pdf

总结

目前HLLC在很多开源组件中都有应用,比如redis、druid等

其他:

https://research.neustar.biz/2012/10/25/sketch-of-the-day-hyperloglog-cornerstone-of-a-big-data-infrastructure/

原文地址:https://www.cnblogs.com/barneywill/p/10172443.html

时间: 2024-07-30 07:56:08

【原创】算法分享(4)Cardinality Estimate 基数计数概率算法的相关文章

解读Cardinality Estimation&lt;基数估计&gt;算法(第一部分:基本概念)

基数计数(cardinality counting)是实际应用中一种常见的计算场景,在数据分析.网络监控及数据库优化等领域都有相关需求.精确的基数计数算法由于种种原因,在面对大数据场景时往往力不从心,因此如何在误差可控的情况下对基数进行估计就显得十分重要.目前常见的基数估计算法有Linear Counting.LogLog Counting.HyperLogLog Counting及Adaptive Counting等.这几种算法都是基于概率统计理论所设计的概率算法,它们克服了精确基数计数算法的

基数计数——HyperLogLog

所谓的基数计数就是统计一组元素中不重复的元素的个数.如统计某个网站的UV,或者用户搜索网站的关键词数量:再如对一个网站分别统计了三天的UV,现在需要知道这三天的UV总量是多少,怎么融合多个统计值. 1.方法 (假设元素个数为m,去重后个数为n) 1.集合操作去重 时间复杂为O(m2),空间复杂度随元素个数线性增长.数据量一大就崩了. 2.B+树 将数据插入到B+树中达到去重目的,然后顺序访问叶节点链从而得到n值.时间复杂的为O( lgm + n ),内存亦随元素个数线性增长.数据量一大就崩了.

【原创】算法分享(7)最小二乘法

Ordinary Least Square 最小二乘法 提到最小二乘法要先提到拟合,拟合Fitting是数值分析的基础工具之一,在二维平面上分为直线拟合和曲线拟合,直线拟合找到一条直线尽可能穿过所有的点,注意这里是尽可能,因为只要超过2个点,就有可能发生直线不能精确穿过所有点的情况,这时确定直线的原则有很多种,最小二乘法就是其中的一种,当直线不能穿过点时产生误差(点和直线的距离),最小二乘法就是让所有点的误差的平方和最小: 最小二乘法(又称最小平方法)是一种数学优化技术.它通过最小化误差的平方和

算法导论-排序(四)计数排序(线性时间排序)

目录 1.计数排序介绍 2.流程图 3.代码实现 4.性能分析 5.参考资料 内容 1.计数排序介绍 什么是计数排序? 计数排序是一种特殊的排序算法,之前介绍的排序算法需要对数进行两两比较,效率下界为θ(nlgn);   而计数排序不需要对数进行比较就可以进行排序:很神奇吧,只需要对待排序数组进行遍历就可以排序,效率为Θ(n)..哈哈,那么神奇,下面开讲!!!! 限制:计数排序只能对非负整数(0,1,2,3...N)进行排序 排序思想:计数排序的基本思想就是对每一个输入元素 x,确定出小于 x

[原创] 算法之递归(3)- 链表操作

算法之递归(3)- 链表操作 递归(2)尝试了一个单链表的遍历,同时又分析了如何添加自己的操作,是在递归调用之前,还是在递归调用之后. 今天,打算将问题深入一下,即添加相应的操作在递归的过程中. (免责声明:下面的解法纯属娱乐 ,另外,示例代码未经编译和调试,许多想法未经实践验证.) 查找链表当中倒数第N个节点. 解法一 逐层递归,遍历到最后一个节点,并从返回的节点一次向后递归,遍历N次,找到倒数第N个节点. private LNode targetNode = null; private LN

算法学习-02(希尔排序,计数排序,桶排序,基数排序)

希尔排序 # 希尔排序 # 希尔排序是对插入排序的升级改造 # 它的大致流程是 # 1.将长度为n的序列 分为d = n//2组 # 2.使每一组变的有序 # 3.将序列分为 d1 = d // 2 组 # 4.将每一组变的有序 # 5.直到最后 d 小于等于 0 def inster_sort_gap(li,gap): for i in range(gap,len(li)): tmp = li[i] j = i - gap while j >= 0 and tmp > li[j]: li[j

算法导论第八章__实现计数排序

计数排序:不须要比較就能得出排序的顺序__比如.本章的计数排序.基数排序.桶排序 比較排序:须要进行比較才干得出排序的顺序__比如,本章的堆排序.高速排序(本质是插入排序).插入排序 代码清单:计数排序__完美演绎下标的作用 public class Count_Sort { //接收须要排序的数组 private int[] A; //排序后的数组 private int[] B; //用于计数的数组 private int[] C; // 初始化 public Count_Sort(int[

【原创-算法-实现】异步HTTP请求操作

一.说明 1) 这个类 是我 在真实项目中,优化解决真实问题 时,不参考第三方代码,完全由自己查阅MSDN官方文档 , 完成的一个真实生产环境中使用的功能类 2) 读者在使用此类时,请尊重原创,在代码中加上原创注释://  Author -- Meng.NET (cnblogs.com)  ,同时欢迎 二次改进.二次创作 以共同进步 3) 此代码以[面向对象].[C#闭包].[异步回调].[超时].[等待].[自动重试]方式实现及完成,且可以配置扩展 二.代码 废话不多说,上干货,代码如下: 1

程序员训练机器学习 SVM算法分享

http://www.csdn.net/article/2012-12-28/2813275-Support-Vector-Machine 摘要:支持向量机(SVM)已经成为一种非常受欢迎的算法.本文主要阐述了SVM是如何进行工作的,同时也给出了使用Python Scikits库的几个示例.SVM作为一种训练机器学习的算法,可以用于解决分类和回归问题,还使用了kernel trick技术进行数据的转换,再根据转换信息在可能的输出之中找到一个最优的边界. [CSDN报道]支持向量机(Support