1. 散列思想
- 散列表(Hash Table),(散列-哈希-Hash)
- 特性:下标随机访问(时间复杂度O(1)),数组扩展
- 词汇:键(key)、关键字、散列函数、散列值(下标)
2. 散列函数要求
- 散列函数计算得到的散列值是一个非负整数
- 如果 key1 = key2,那 hash(key1) = hash(key2)
- 如果 key1 ≠ key2,那 hash(key1) ≠ hash(key2)。
- 问题:很难找到一个不同的key对应的散列值都不一样的散列函数(要求3),即散列冲突(数据无限,空间有限)
3. 散列冲突
- 开放寻址法
- 核心思想: 如果冲突,则重新探测新位置
- 方法
- 线性探测法
- 二次探测
- 双重散列
- 线性探测法
- 插入:先求散列值,冲突时,往后查找,直到找到空闲位置插入
- 查找:类似插入,至空闲位置还未找到则不存在。
- 删除:类似插入,标记删除元素,查找时跳过它(影响查找)
- 问题:数据多时,算法退化 -> O(n)
- 二次探测:hash(key)+n2
- 双重散列:一组散列函数
- 装载因子:散列表的装载因子 = 填入表中的元素个数 / 散列表的长度
- 链表法
- n个数据,m个槽
- 槽对应链表,存储散列值相等的元素
- 按槽插入,时间复杂度O(1)
- 查找、删除,槽内链表查找节点,平均时间复杂度O(n/m)
4. 散列表和链表联合使用
- 散列表:增删查快,但是无序
- 链表(或跳表):排序
4.0 缓存淘汰策略
- 先进先出策略 FIFO(First In,First Out)
- 最少使用策略 LFU(Least Frequently Used)
- 最近最少使用策略 LRU(Least Recently Used)
4.1 LRU 缓存淘汰算法
- 主要操作:添加,删除,查找
- 时间复杂度:均为O(1)
- 散列表 + 双向链表
- 散列表:用于查找,复杂度O(1)
- 散列表使用拉链(单链表)解决散列冲突(链表法)
- 双向链表:保存数据的顺序关系,便于删除、新增,复杂度O(1)。单链表时间复杂度o(n)
- 添加分析
- 是否存在
- 如果存在,移到尾部
- 如果不存在,且双向链表是否已满,删除头部节点,尾部添加
- 如果不存在,且未满,尾部添加
4.2 Redis 有序集合
- 有序集合的两个重要属性:键值(key),分值(score)
- score操作:跳表实现的有序集合,区间查找效率高
- key操作:散列表,根据key快速查找,时间复杂度O(1)
- 类似LRU的解决方法:跳表 + 散列表
4.3 Java LinkedHashMap
- 支持 LRU 缓存淘汰策略
- 支持按照访问时间排序
- 双向链表 + 散列表