哈希算法
本篇博文主要介绍哈希算法的定义,常见的哈希算法,哈希冲突的解决方法…
相比入其他数据结构,散列表的优势在于查找和插入的速度非常快,在不发生冲突的情况下,查找和插入的时间复杂度为O(1),是一种时间换空间的做法,而B树查找和插入的时间复杂度为O(logn),针对写优化的LSM树,插入的时间复杂度为O(1),采取日志的方式进行追加,但是查找的效率非常低,比如leveldb的设计中内存组件数据结构是跳表,查找效率为O(logn),当然,不同的数据结构设计都是为特定的场景服务的。
哈希表,或者散列表可以理解为一个线性表,可以使一维线性表也可以使多维线性表,比如多布隆过滤器会设计多个哈希表,多张哈希表可以放在一个多维数组中存放。
散列表的每个索引位置我们通常称为"桶",bucket。影响散列表性能的主要问题是散列冲突,冲突的程度主要取决于:
- 散列函数的选取,一个好的散列函数应该尽可能的让值均匀地分布于整个散列表中;
- 处理冲突的方法,当发生冲突时如何存放"同义词";
- 负载因子,或者说装载因子,load factor,负载因子越大,冲突发生的可能性越小,但是相应的空间浪费也越大;
常见的解决冲突方法:
- 开放定址法,open addressing,当冲突发生时,对哈希值加增量, h a s h i = ( H A S H ( k e y ) + d i ) m o d m hash_i=(HASH(key)+d_i)\;mod\;m hashi=(HASH(key)+di)modm,增量方法有线性探测(Liner Probing),即 d i = i d_i=i di=i,增量序列为线性函数;还有平方探测(Quadratic Probing), d i = ± 1 2 , ± 2 2 , ± 3 2 , . . . , ± k 2 , ( k ≤ m / 2 ) d_i=\pm1^2,\pm2^2,\pm3^2,...,\pm k^2,(k\leq m/2) di=±12,±22,±32,...,±k2,(k≤m/2);还有伪随机数序列,称为伪随机探测.
- 单独链表法,将发生冲突的值保存在桶的链表中;
- 双散列
- 再散列,将发生冲突的散列计算值重新将此值作为key再进行计算。
- 建立公共溢出区。
常见的散列函数:
- 直接寻址法,取关键字或者关键字的某个线性值作为散列地址,H(key)=key或者H(key)=a*key+b;
- 数字分析法,平方取中,折叠,随机数,除留余数
常用的hash
MD5和SHA-1是目前最常用的hash算法,两者均以MD4为基础进行设计.
hash的一个作用是将大范围映射到一个小范围,用于节省空间,存储数据,另外hash也会应用在查找上。hash应用的注意点:
- hash逼近单向函数,所以可以用来加密;
- 不同作用的hash函数,考虑的点不同,加密hash函数考虑它与单项函数的差距,查找hash函数考虑它映射到小范围内的冲突率
一般hash函数应用的对象主要是数组,比如字符串,目标值为int值,因此hash可分为以下几类:
- 加法hash,把输入元素一个个加起来的hash值
- 位运算hash,通过位运算(位移,异或)来混合输入元素,比如旋转hash
static int rotatingHash(string key, int prime) {
int hash, i;
for (hash = key.size(), i = 0; i < key.size(); ++i) {
hash = (hash << 4) ^ (hash >> 28) ^ key[i];
}
return hash % prime;
}
- 乘法hash,除法hash
- 查表hash,CRC系列算法
哈希应用场景
安全加密,唯一标识,数据校验,散列函数,负载均衡(比如一致性哈希),分布式缓存.
其他
哈希函数产生的值是随机的,因此hash不并适合范围查找,即range look up,而LSM树等结构适合范围查找,这就是为什么leveldb在设计memtable内存组件时选择skiplist作为他的数据结构,整体的组织方式选择LSM树。
未完待续…
2020-4-22 20:05:23