文章目录
总体知识脉络
背景
- 使用 word 文档时,word 如何判断某个单词是否拼写正确?
- 网络爬虫程序,怎么让它不去爬相同的 url 页面?
- 垃圾邮件过滤算法如何设计?
- 公安办案时,如何判断某嫌疑人是否在网逃名单中?
- 缓存穿透问题如何解决?
需求
从海量数据中查询某字符串是否存在
平衡二叉树
不论是AVL还是红黑树,在“海量数据”数据面前都是不合适的,因为红黑树会将key,即数据存储起来,而海量的数据会导致内存不足。并且设计到字符串比较,效率也是很慢的。所以在这个需求下,用树相关的数据结构是不合适的。
扩展:
- c++标准库(STL)中的set和map结构都是采⽤红⿊树实现的,它增删改查的时间复杂度是O(log2N)。set和map的关键区别是set不存储val字段。
- 优点:存储效率⾼,访问速度⾼效
- 缺点:对于数据量⼤且查询字符串⽐较⻓且查询字符串相似时将会是噩梦
散列表hashtable
散列表构成:数组+hash函数。它是将字符串通过hash函数⽣成⼀个整数再映射到数组当中(所以散列表不需要”比较字符串“,而红黑树需要),它增删改查的时间复杂度是o(1)。
注意:散列表的节点中 kv 是存储在一起的
struct node {
void *key;
void *val;
struct node *next;
};
扩展:
- c++标准库(STL)中的unordered_map<string, bool>是采⽤hashtable实现的
- hashtable中节点存储了key和val,hashtable并没有要求key的⼤⼩顺序,我们同样可以修改代码让插⼊存在的数据变成修改操作
- 优点:访问速度更快;不需要进⾏字符串⽐较
- 缺点:需要引⼊策略避免冲突,存储效率不⾼;空间换时间
hash函数
hash函数:映射函数 Hash(key)=addr ;hash 函数可能会把两个或两个以上的不同 key 映射到同一地址,这种情况称之为冲突(或者hash 碰撞);
hash函数的作用:避免插⼊的时候字符串的⽐较,hash函数计算出来的值通过对数组⻓度的取模能随机分布在数组当中。
选择hash
如何选取hash函数?
- 选取计算速度快
- 强随机分布(等概率、均匀地分布在整个地址空间)
murmurhash1,murmurhash2,murmurhash3,siphash(redis6.0当中使⽤,rust等大多数语言选用的hash算法来实现hashmap),cityhash 都具备强随机分布性。siphash 主要解决字符串接近的强随机分布性,所以如果要hash字符串的话,优先选用siphash。
负载因子
用来形容散列表的存储密度。数组存储元素的个数 / 数据长度;负载因子越小,冲突越小,负载因子越大,冲突越大。
冲突处理
-
拉链法
引用链表来处理哈希冲突,也就是将冲突元素用链表链接起来,这也是常用的处理冲突的⽅式。但是可能出现一种极端情况,冲突元素比较多,该冲突链表过长,这个时候可以将这个链表转换为红黑树。由原来链表时间复杂度 转换为红黑树时间复杂度 ,那么判断该链表过长的依据是多少?可以采⽤超过 256(经验值)个节点的时候将链表结构转换为红黑树结构。
-
开放地址法
将所有的元素都存放在哈希表的数组中,不使用额外的数据结构;一般使用线性探查的思路解决,具体步骤如下:
1. 当插⼊新元素的时,使⽤哈希函数在哈希表中定位元素位置
2. 检查数组中该槽位索引是否存在元素。如果该槽位为空,则插⼊,否则3
3. 在 2 检测的槽位索引上加⼀定步⻓接着检查2
加⼀定步⻓分为以下几种:
1. i+1,i+2,i+3,i+4, … ,i+n
2.
这两种都会导致同类hash聚集,也就是近似值它的hash值也近似。那么它的数组槽位也靠近,形成hash聚集。第⼀种同类聚集冲突在前,第⼆种只是将聚集冲突延后。
3. 可以使⽤双重哈希来解决上⾯出现hash聚集现象
在.net HashTable类的hash函数Hk定义如下:
Hk(key) = [GetHash(key) + k * (1 + (((GetHash(key) >> 5) + 1) % (hashsize – 1)))] % hashsize
在此 (1 + (((GetHash(key) >> 5) + 1) % (hashsize – 1))) 与 hashsize 互为素数(两数互为素数表示两者没有共同的质因⼦)
执⾏了 hashsize 次探查后,哈希表中的每⼀个位置都有且只有⼀次被访问到,也就是说,对于给定的 key,对哈希表中的同⼀位置不会同时使⽤ Hi 和 Hj;
具体原理:https://www.cnblogs.com/organic/p/6283476.html
Stl中unordered_*散列表实现
在 STL 中 unordered_map 、 unordered_set 、unordered_multimap 、 unordered_multiset 四兄弟底层实现都是散列表;