没有合适的资源?快使用搜索试试~ 我知道了~
20丨散列表(下):为什么散列表和链表经常会一起使用?1
需积分: 0 1 下载量 93 浏览量
2022-08-03
11:49:00
上传
评论
收藏 2.07MB PDF 举报
温馨提示
试读
14页
在链表那一节,我讲到如何用链表来实现 LRU 缓存淘汰算法,但是链表实现的 LRU 缓存淘汰算法的时间复杂度是 O(n),当时我也提到了,通过散列表可以将这个时
资源推荐
资源详情
资源评论
20 | 散列表(下):为什么散列表和链表经常会一起使用?
2018-11-05 王争
数据结构与算法之美
进入课程
讲述:修阳
时长 11:39 大小 5.34M
我们已经学习了 20 节内容,你有没有发现,有两种数据结构,散列表和链表,经常会被放
在一起使用。你还记得,前面的章节中都有哪些地方讲到散列表和链表的组合使用吗?我带
你一起回忆一下。
在链表那一节,我讲到如何用链表来实现 LRU 缓存淘汰算法,但是链表实现的 LRU 缓存
淘汰算法的时间复杂度是 O(n),当时我也提到了,通过散列表可以将这个时间复杂度降低
到 O(1)。
在跳表那一节,我提到 Redis 的有序集合是使用跳表来实现的,跳表可以看作一种改进版
的链表。当时我们也提到,Redis 有序集合不仅使用了跳表,还用到了散列表。
下载APP
除此之外,如果你熟悉 Java 编程语言,你会发现 LinkedHashMap 这样一个常用的容器,
也用到了散列表和链表两种数据结构。
今天,我们就来看看,在这几个问题中,散列表和链表都是如何组合起来使用的,以及为什
么散列表和链表会经常放到一块使用。
LRU 缓存淘汰算法
在链表那一节中,我提到,借助散列表,我们可以把 LRU 缓存淘汰算法的时间复杂度降低
为 O(1)。现在,我们就来看看它是如何做到的。
首先,我们来回顾一下当时我们是如何通过链表实现 LRU 缓存淘汰算法的。
我们需要维护一个按照访问时间从大到小有序排列的链表结构。因为缓存大小有限,当缓存
空间不够,需要淘汰一个数据的时候,我们就直接将链表头部的结点删除。
当要缓存某个数据的时候,先在链表中查找这个数据。如果没有找到,则直接将数据放到链
表的尾部;如果找到了,我们就把它移动到链表的尾部。因为查找数据需要遍历链表,所以
单纯用链表实现的 LRU 缓存淘汰算法的时间复杂很高,是 O(n)。
实际上,我总结一下,一个缓存(cache)系统主要包含下面这几个操作:
这三个操作都要涉及“查找”操作,如果单纯地采用链表的话,时间复杂度只能是 O(n)。
如果我们将散列表和链表两种数据结构组合使用,可以将这三个操作的时间复杂度都降低到
O(1)。具体的结构就是下面这个样子:
往缓存中添加一个数据;
从缓存中删除一个数据;
在缓存中查找一个数据。
我们使用双向链表存储数据,链表中的每个结点处理存储数据(data)、前驱指针
(prev)、后继指针(next)之外,还新增了一个特殊的字段 hnext。这个 hnext 有什么
作用呢?
因为我们的散列表是通过链表法解决散列冲突的,所以每个结点会在两条链中。一个链是刚
刚我们提到的双向链表,另一个链是散列表中的拉链。前驱和后继指针是为了将结点串在双
向链表中,hnext 指针是为了将结点串在散列表的拉链中。
了解了这个散列表和双向链表的组合存储结构之后,我们再来看,前面讲到的缓存的三个操
作,是如何做到时间复杂度是 O(1) 的?
首先,我们来看如何查找一个数据。我们前面讲过,散列表中查找数据的时间复杂度接近
O(1),所以通过散列表,我们可以很快地在缓存中找到一个数据。当找到数据之后,我们
还需要将它移动到双向链表的尾部。
其次,我们来看如何删除一个数据。我们需要找到数据所在的结点,然后将结点删除。借助
散列表,我们可以在 O(1) 时间复杂度里找到要删除的结点。因为我们的链表是双向链表,
双向链表可以通过前驱指针 O(1) 时间复杂度获取前驱结点,所以在双向链表中,删除结点
只需要 O(1) 的时间复杂度。
剩余13页未读,继续阅读
资源评论
宝贝的麻麻
- 粉丝: 34
- 资源: 294
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功