哈希表
哈希表(Hash Table)又称散列表,是一种可以通过 “关键码” (key)直接进行访问的数据结构。
哈希表由两部分组成:
一个数据结构
,通常是链表、数组Hash 函数
,输入 “关键码”(key),返回数据结构的索引
对外表现为可以通过关键码直接访问:hash_table[key] = value
实际上是在数据结构的 hash(key)
位置处存储了 value:data_structure[hash(key)] = value
比如关键码是整数,定义 hash(key) = key
,那这个哈希表其实就是一个数组,key 自己就是下标。
当然,一般情况下,关键码 key 是一个比较复杂的信息,比如很大的数、字符串,这时候 key 就不能直接作为数据结构的下标了。此时就需要设计一个 Hash 函数,把复杂信息映射到一个较小的值域内,作为索引。
例如 hash_table["lies"] = 233
,以各字符 ASCII 码相加然后再 mod 20 作为 Hash 函数。
哈希碰撞
哈希碰撞(Collisions)指的是两个不同的 key 被计算出同样的 Hash 结果。
把复杂信息映射到小的值域,发生碰撞是不可避免的。
好的 Hash 函数可以减少碰撞发生的几率,让数据尽可能地均衡分布。
开散列是最常见的碰撞解决方案。
- Hash 函数依然用于计算数组下标
- 数组的每个位置存储一个了链表的表头指针(我们称它为表头数组)
- 每个链表保存具有相同 Hash 值的数据
“挂链” -> 表头数组每个位置 “挂” 着一个链表。
时间复杂度
- 期望:插入、查询、删除都是 O(1)
- 数据分布比较均衡时
- 最坏:插入、查询、删除都是 O(n)
- 数据全部被映射为相同的 Hash 值
无序集合、无序映射
集合(set、unordered_set)存储不重复的元素。
- 有序集合,遍历时按元素大小排列,一般用平衡二叉搜索树实现,O(logN)
- 无序集合,一般用哈希表实现,O(1)
映射(map、unordered_map)存储关键码(key)不重复的键值对(key-value pair)。
- 有序集合,遍历时按照 key 大小排列,一般用平衡二叉搜索树实现,O(logN)
- 无序集合,一般用哈希表实现,O(1)
对于语言内置的类型(int、string),已经有默认的优秀的 hash 函数,可以直接放进 set/map 里使用。
LeetCode 练习题
Cache
缓存的两个要素:大小、替换策略。
常见替换算法:
- LRU - least recently used,最近最少使用(淘汰最旧数据)
- LFU - least frequently used,最不经常使用(淘汰频次最少数据)
LRU Cache
LeetCode 练习题