二. 大数据常用的算法和数据结构 <<大数据日知录>> 读书笔记
基本上是hash实用的各种举例
- 布隆过滤器 Bloom Filter
常用来检测某个原色是否是巨量数据集合中的成员,优势是节省空间,不会有漏判(已经存在的数据肯定能够查找到),缺点是有误判(不存在的数据可能也会被找到)。
应用场景有,chrome进行恶意的url判断,爬虫判断爬取过的url,缓存使用BF进行海量数据查找,比特币使用BF对历史交易进行验证。
基本思想是,首先有个位数组,长度为m,将数据a通过n个hash函数进行计算,每个hash得到的结果x 在[1,m]区间,将x作为一个索引,索引到位数组的x位置并置1,这样数据a就会将位数组中的n个位置置1,对a的存在与否的查询,就通过判断a hash后所有位置的1是否存在,来进行判断
因为只需要位数组来进行数据的映射存储,所以BF的空间利用率是很高的,但是很明显,这种方法会有误判率,如果a和b进行hash后设置的bit 1正好覆盖了数据c的hash后的bit 1的位置,则会误判c也存在。所以基本的BF算法是不适合对误判率要求较高的情况,另外一个优点是BF不会有漏判......
那么影响误判率的因素有哪些呢?数据集合越大,自然设置的bit 1 会越多,误判率会越高;位数组的长度越大,bit 1 重复的可能越小,误判率越低;hash函数个数对误判率的影响,分两头说,映射的写bit过程,hash函数越多,越多的bit会被置1,导致误判率越高,查询时,哈希函数越多,误判率越低。书中提供了一个经过数学分析的公式,我们要做的,就是灵活的使用公式,根据实际情况来决定,在误判率和数据预估的情况下,如何设计hash函数和位数组。
计数BF:
基本的BF是无法删除映射关系的,因为一旦删除某数据对应的所有bit 1,可能影响到其他映射到同样bit的数据。解决方案是bit不仅置1,还进行一个置1的计数。
2. skip list
一般而言,列表的查找是耗时的,因为要一个接一个的查找,而skip list可以解决这个问题,redis就用到了skip list提高了查找效率
总体而言,skip list是将list进行了分层,数据可能同时在多层的列表中,而数据的查找,可以在各层之间进行,这样就是跳跃着查找,而不是挨个的查找。
数据的插入,在找到位置后,会通过随机数进行新节点的层数生成,然后加入整体的skip list,更新每一层的指针。
3. Merkle 哈希树
Merkle树主要用来在海量数据下定位少量变化的数据内容。
树如下,root的hash值会由CDEF四个数据块的hash值得到,所以有一个数据有变化,导致根节点的hash变化,然后就向子节点查询,最终快速找到是C D E F 哪个子节点变化了。
4 snappy与LZSS算法
这里说的是压缩和解压算法。
词典编码,即用一个词典中的号码来代替文本中的词,实现压缩。而这个词典可以预设,即静态词典,或者动态生成,LZSS就是动态词典编码。
比如abcdxabcm,动态词典由已经编码的文本导出生成,当编码abcdx后,到abcm,发现abc在动态词典中存在,于是这个abc就可以压缩输出,达到压缩目的。可见,如果要提高压缩效率,可以对查询字典额字符做合理的最小匹配长度限制,如果上面例子的最小匹配长度为4,那么上面提到的abc就不会压缩了。
同时,如何找到最长匹配的字符串也是影响效率的关键点之一,我们可以将各种长度片段存入hash表,就可以实现最快的匹配。
snappy基本上遵循lz算法,它规定最小匹配长度为4,同时将整个数据分割32kb,独立压缩,这样2个字节可以匹配字符串的相对位置
5 cuckoo hashing
这种hash可以很好的解决hash冲突,提高了hash的查找效率
插入一个数据x,将其进行2个不同的hash,得到2个hash桶的索引,如果有任意一个为空,则插入,如果都不为空,则踢出当前数据,然后插入x,被踢出的数据重复上述操作,如果一直有被踢出的数据,则设定最大替换次数,当到达最大,要么增加桶,要么重新选择合适的hash函数来替换之前的函数。
这种方式下,查找变成O(1)的复杂度,因为只要查2个位置即可。
这种hash的一般变体,是增加hash函数的个数或者让每个桶存储多个数据,可以提高hash空间的桶里用来过滤