维基百科上列的一些probabilistic data structures:Bloom filter、Count–min sketch、Cuckoo filter、HyperLogLog、Kinetic hanger、Kinetic heater、Locality-sensitive hashing、MinHash、Quotient filter、Random binary tree、Random tree、Rapidly-exploring random tree、SimHash、Skip list、Treap,单词拆开貌似都大部分认识,但合起来就不知道啥意思了:)
下面介绍几个常用的:
基数统计(cardinality estimation)是指用概率算法的思想,估计一个集合中不同的数的个数。
例如:{1,2,3,4,5,2,3,9,7}
这个集合有9个元素,但是2和3各出现了两次,因此不重复的元素为1,2,3,4,5,9,7,所以这个集合的基数是7。
基数统计在数据分析、网络监控及数据库优化等领域都有相关需求。精确的基数计数算法由于种种原因,在面对大数据场景时往往力不从心,如何在误差可控的情况下对基数进行估计就显得十分重要。目前常见的基数估计算法有Linear Counting、LogLog Counting、HyperLogLog Counting及Adaptive Counting等。实现使用中,Linear Counting和LogLog Counting由于在基数较大和基数较小时存在严重的失效,不适合在实际中单独使用,而Adaptive Counting或HyperLogLog Counting是不错的选择。
实现:
java: https://github.com/addthis/stream-lib/blob/master/src/main/java/com/clearspring/analytics/stream/cardinality/HyperLogLog.java
redis: redis在2.8.9版本添加了 HyperLogLog 结构,提供三个操作命令:PFADD、PFCOUNT、PFMERGE。在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。每个HyperLogLog键只需要花费12 KB内存,就可以计算接近2^64个不同元素的基数。
**使用场景:**网站日活、月活、PV、UV等统计。
**
**
如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大,同时检索速度也越来越慢。
Bloom Filter(布隆过滤器)是1970年由布隆提出的,可以用于检索一个元素是否在一个集合中。相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数(O(k))。另外,散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对安全保密要求非常严格的场合有优势。
它的优点是空间效率和查询时间都远远超过一般的算法,缺点是删除困难和有一定的误识别率(随着存入的元素数量增加,误识别率随之增加)。
实现:
java: 在Google Guava library中Google为我们提供了一个布隆过滤器的实现:com.google.common.hash.BloomFilter
redis: 而在分布式环境中可以使用redis的实现,在4.0前可以使用bitset的方法getbit和setbit来实现,4.0后redis提供插件实现方式,可以通过https://github.com/RedisLabsModules/rebloom下载安装,而redis企业版用户在5.0版本中已经包含。
使用场景:
…等等。但注意BloomFilter是存在概率问题,即如果返回不存在时就一定不存在,如果返回存在,实际可能不存在,实现使用时需要注意。
MinHash
MinHash是一种基于 Jaccard Index 相似度的算法,用于快速估计两个集合的相似度,最早由Broder Andrei Z. 在1997年提出,最初在AltaVista搜索引擎中用于在搜索结果中检测并消除重复Web页面。如今广泛应用于大数据集的相似检索、推荐系统、聚类分析等中。
举例A,B 两个集合:
A = {s1, s3, s6, s8, s9}
B = {s3, s4, s7, s8, s10}
根据Jaccard Index公式,A,B的相似度 S(A,B) = |A_∩_B|/|A∪B| = 2/8 = 0.25。
直接计算两个集合的交集与并集,是很耗计算资源的,特别是在海量数据场景下不可行。
假如,我们随机从两个集合中各挑选一个元素s(A)、s(B),刚好这两个无素相同的概率是多少呢?
这个概率其实等同于,在A∪B这个大的随机域里,选中的元素落在A∩B这个区域的概率,这个概率就等于Jaccard的相似度,这就是MinHash的基本原理。
实现:
java: 网上有不少java的实现,比如 https://github.com/ALShum/MinHashLSH, https://www.sanfoundry.com/java-program-implement-min-hash/
使用场景:
**
**
Count-min Sketch 是一个概率数据结构,和BloomFilter的统计机制类似,用作统计数据流中事件的频率。如果使用HashMap来统计各个元素的出现频率,但由于不同的元素的个数可能非常大,以至于是个天文数字,要求的内存可能会非常大,从而不切实际。如果不需要太精确的计数,可以使用Count-min Sketch。
实现:
java:https://github.com/addthis/stream-lib/blob/master/src/main/java/com/clearspring/analytics/stream/frequency/CountMinSketch.java
redis: 分布式下可以使用redis,插件地址: https://github.com/RedisLabsModules/countminsketch
使用场景:
统计一个实时的数据流中元素出现的频率
中位数、95% 分位数,这类计算在描述性统计中很常见。相较于平均数,中位数不会受到异常值的影响,但它的计算过程比较复杂,需要保留所有具体值,排序后取得中间位置的数作为结果。T-Digest算法则通过一定计算,将数据集的分布情况粗略地记录下来,从而估计出指定的分位数值。
实现:
java: https://github.com/tdunning/t-digest
redis(非官方版): https://github.com/usmanm/redis-tdigest
使用场景:实时快速的求出百亿流数据的百分位数。elasticSearch的percentiles 使用一个 TDigest 算法用于百分位近似统计。
以上几种算法都是通过牺牲准确性来提升时间与空间的利用效率的,在大量数据的场景有很大的应用价值。
stream-lib是比较有名的java实时计算与基数统计工具库,有兴趣的可以翻下github: https://github.com/addthis/stream-lib
关注微信公众号,更多干货哦!