散列表(下):为什么散列表和链表会经常一起使用?

散列表(下):为什么散列表和链表会经常一起使用?

LRU缓存淘汰算法

想知道如何借助散列表将LRU缓存淘汰算法先回顾如何通过链表实现LRU缓存淘汰算法

需要维护一个按照访问时间从大到小有序排列的链表结构,因为缓存大小有限,当缓存空间不够,需要淘汰一个数据的时候,我们就直接将链表头部的结点删除

当要缓存某个数据的时候,先在链表中查找该数据,如果没有找到,直接将数据放到链表尾部,找到了就移动到链表尾部,因为查找数据需要遍历链表,所以使用链表实现LRU缓存淘汰算法的时间复杂度很高

一个缓存cache系统主要包含下面几个操作:

  • 往缓存中添加一个数据
  • 从缓存中删除一个数据
  • 在缓存中查找一个数据

如果将散列表和链表两种数据结构组合使用,可以将三个操作的时间复杂度降低到O(1)

使用双向链表存储数据,链表中的每个结点处理存储数据(data)、前驱指针(prev)、后继指针(next),还有一个特殊字段hnext

因为散列表是通过链表法解决散列冲突的,所以每个结点会在两条链中,一个链式双向链表,一个是散列表中的拉链,前驱和后继指针是为了将结点串在双向链表中,hnext指针是为了将结点串在散列表的拉链中

整个过程设计的查找操作都可以通过散列表来完成

Redis有序集合

在有序集合中,每个成员对象有两个重要属性,key(键值)和score(分值),不仅可以通过score来查找数据,还会通过key来查找数据

比如用户积分排行榜有一个功能:通过用户的ID来查找积分信息,可以通过积分区间来查找用户ID或者姓名信息,这里包含ID、姓名和积分的用户信息,就是成员对象,用户ID就是KEY,积分就是SCORE

整个操作就是:

  • 添加一个成员对象
  • 按照键值来删除一个成员对象
  • 按照键值来查找一个成员对象
  • 按照分值区间查找数据,比如查找积分在[100,356]之间的成员对象
  • 按照分值从小到大排序成员变量

我们按照分值将成员对象组织成跳表的结构,再按照键值构建一个散列表,这样按照key来删除、查找一个成员对象时间复杂度就会变成o(1)

Java LinkedHashMap

HashMap<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 散列表中数据是经过散列函数打乱之后无规律存储的,如何实现按照数据的插入顺序来遍历打印的?LinkedHashMap是通过散列表和链表一起来遍历数据

//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());
}

结果是1,2,3,5

每次调用put()函数,往LinkedHashMap中添加数据的时候,都会将数据添加到链表的尾部,所以前四个操作完成之后,链表中的数据是3,1,5,2,当将键值为3的数据放入LinkedHashMap的时候,先查找这个键值是否已经存在,然后将已经存在的(3,11)删除,将新的(3,26)放到链表的尾部,所以这是链表中的数据是1,5,2,3,当第9行代码访问到key=5的时候,将访问到的数据移动到链表的尾部,所以这时候链表中的数据是1,2,3,5,所以按照时间排序的LinkedHashMap本身就是一个支持LRU缓存淘汰策略的缓存系统

为什么散列表和链表经常一起使用?

散列表数据结构虽然支持高效的数据插入、删除、查找操作,但是散列表中的数据都是通过函数打乱之后无规律存储的,如果希望按照顺序遍历散列表中的数据,需要将散列表中的数据拷贝到数组中然后排序,再遍历

因为散列表是动态数据结构,不停的有数据的输入、删除,所以希望按顺序遍历数据的时候,需要先排序,效率会很低,所以为了解决问题,将散列表和链表结合一起使用

散列表和链表结合使用的是双向链表,如果改成单链表,能否正常工作?

​ 不能正常工作,删除一个元素时,虽然能够O(1)的找到目标,但是要删除该节点需要拿到前一个节点的指针,时间复杂度就会变成O(n)

猎聘网有10W猎头,每个猎头可以通过做任务来发布积分,然后通过积分来下载简历,你是工程师,如何在内存中存储这10W个猎头ID和积分信息,让他能够支持这样几个操作
  • 根据猎头ID快速查找、删除、更新这个猎头的积分信息
  • 查找积分在某个区间的猎头ID列表
  • 查找按照积分从小到大排名在第X位到第y位之间的猎头ID列表

​ 以积分排序构建一个跳表,再以猎头ID构建一个散列表

​ ID在散列表中可以O(1)查找到这个猎头,积分以跳表存储,跳表支持区间查询

其实只有两种数据结构:链表和数组。

数组可以随机访问却需要连续的内存,链表可以不连续存储,但访问查找是线性的,散列表和链表、跳表的混合使用就是为了结合数组和链表

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值