为什么散列表和链表会经常一起使用
LRU缓存淘汰算法
总结链表那篇文章中,曾经实现过LRU缓存淘汰算法,但是当时的删除插入操作时间复杂是 O(n)。
如果用散列表加链表的方式实现LRU缓存淘汰算法,时间复杂度就变成O(1)。
数据的存贮我们用散列表实现,散列冲突用链表法解决。如果有相同散列值的元素,我们就将其插入到该散列值对应位置链表的尾部,链表上的节点都有一个 hnext 指针,指向该链表的后后继节点。
插入元素的排列顺序,我们用双向链表来维护。也就是说,散列表中的所有元素,除了因为散列冲突的原因,导致相同散列值元素之间有链表维护以外,还有一个双向链表将所有的元素串联起来。
查找某一元素的时候,根据散列值可以迅速定位元素位置,时间复杂度 O(1)。
删除某一元素的时候,查找的时间复杂度是 O(1),又因为该节点维护在双向链表中,所以删除操作时间复杂度也是 O(1),总的操作时间复杂度是 O(1)。
插入某一元素的时候,首先在散列表中查找该元素,若找到该元素则将其移动到双向链表的尾部,时间复杂度为 O(1),如果没有找到,则计算散列值存入散列表中,并将其维护到双向链表的尾部,时间复杂度为 O(1)。
上述操作中,隐含了一些条件,比如需要明确知道头结点,尾节点指针,这样才能保证插入双向链表尾部和头部的时间复杂度为 O(1)。
Redis有序集合
跳表那篇文章中总结到 Redis 的有序集合是通过跳表实现的,其实不然。
Redis 的有序集合中的元素有两个很重要的属性:key 和 score,我们不仅通过 key 查找数据还通过 score 查找数据。
举例:在有序集合中存储学生以及成绩信息,key 代表的是学生的学号,score 代表的是学生的成绩。那么对学生信息会有以下操作:
1:查找某个学生信息。
2:删除某个学生信息。
3:添加某个学生信息。
4:查找分数在某个区间的学生信息。
5:按照分数对学生排序。
如果单以跳表去存储维护学生分数信息,虽然保证了有序,但是查找学生信息的时间复杂度就是复杂了。
如果以学号为关键值用散列表存储学生的信息,查找时间复杂度为 O(1) 了,但是实现上述 4,5 的操作就复杂的多了。
所以近似于上述 LRU缓存淘汰算法 的数据结构存储学生信息,就能兼顾对学生信息的各种高效操作了,这就是 Redis 有序集合的实现原理。
Java LinkedHashMap
我在公司开发用到的主要语言就是 Java 其中的 LinkedHashMap 操作类经常用到,下面对其进行简单分析。
先看代码1
2
3
4
5
6
7
8
9HashMap<Integer, Integer> m = new LinkedHashMap<>();
m.put(3, 11);
m.put(1, 12);
m.put(5, 23);
m.put(2, 22);
for (Map.Entry e : m.entrySet()) {
System.out.println(e.getKey());
}
输出结果是 3 1 5 2。首先从类名就看的出该操作类底层是 HashMap 实现的,但是输出元素居然是有序的,说明该结构中有除了用到散列表还用到了链表,用链表去维护数据的有序性。
再看代码:1
2
3
4
5
6
7
8
9
10
11
12
13// 10 是初始大小,0.75 是装载因子,true 是表示按照访问时间排序
HashMap<Integer, Integer> m = new LinkedHashMap<>(10, 0.75f, true);
m.put(3, 11);
m.put(1, 12);
m.put(5, 23);
m.put(2, 22);
m.put(3, 26);
m.get(5);
for (Map.Entry e : m.entrySet()) {
System.out.println(e.getKey());
}
首先,当 m 分别放入 3,1,2,5这三个元素的时候,我们是知道这些数据是有序的,按照插入散列表中时间循序排列。当再次插入 key 值为 3 的元素的时候,LinkedHashMap 内部是先找到 key = 3 的元素,将其删除,然后插入新的 key = 3 的元素,并将其维护在链表的尾部。当访问 key = 5 的元素的时候,查找到该元素,并将其维护在链表的尾部。所以最终的输出结果是 1 2 3 5。
经过上述两段代码,我们可以发现 Java 中的 LinkedHashMap 其实实现的就是LRU缓存淘汰算法。
总结
其实,总结了这么多突然发现重要的其实就是两种数据结构:数组和链表。数组支持随机访问,但是插入删除不够高效,链表插入删除快,但是随机访问却低效。数组需要的是连续空间,对内存要求比较高,链表占用的是随机内存地址,提高了内存利用率。将数组与链表相互结合起来,取长补短,就能实现高性能的增删改查功能。
本文创作灵感来源于 极客时间 王争老师的《数据结构与算法之美》课程,通过课后反思以及借鉴各位学友的发言总结,现整理出自己的知识架构,以便日后温故知新,查漏补缺。
初入算法学习,必是步履蹒跚,一路磕磕绊绊跌跌撞撞。看不懂别慌,也别忙着总结,先读五遍文章先,无他,唯手熟尔~
与诸君共勉