数据结构与算法之美-散列表(下)

散列表和链表组合使用

LRU缓存淘汰算法

借助散列表,我们可以把LRU缓存淘汰算法的时间复杂度降为O(1)。

一个缓冲cache系统主要包含以下操作

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

单纯采用链表,时间复杂度只能是O(n)。

将散列表和双向链表结合,就可以降为O(1),其结构如下图所示:

《数据结构与算法之美-散列表(下)》

其中,我们使用双向链表存储数据,data存储数据,prev前驱指针,next后继指针。

此外,新增加了hnext指针,这个指针就是链表法散列表中的拉链的后继指针。

 

如何做到O(1)

查找

因为是散列表所以查找一个数据的操作时间复杂度就接近于O(1)。

删除

删除一个数据,我们借助散列表再O(1)的时间复杂度里找到该结点,

而双向链表有前驱指针,可以直接删除该节点,时间复杂度为O(1)。

添加

添加一个数据比较复杂,首先要看其是否已经在缓存中,

如果在就将其移动到双向链表的尾部,如果不在就检查缓存满了没,

满了就删除双向链表的头结点,再将数据放到双向链表的尾部,

如果没有满就直接将数据放大双向链表的尾部。

以上操作中,设计查找的操作是散列表完成的,删除节点、插入节点是双向链表完成的,所以时间复杂度是O(1)。

 

Redis有序集合

 在有序集合中,每个成员对象有两个重要的属性,键key和分值score。

我们不仅需要key来查找数据,还会需要用score查找数据。

细化一下Redis有序集合的操作:

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

如果只按照分支将成员对象组织成跳表的结构,那么按照键值删除、查询对象就会很慢。

我们可以按照键值构建一个散列表,这样按照key来删除、查找一个对象的时间复杂度就都变成了O(1)。

 

Java中的LinkedHashMap

Jva中的LinkedHashMap中的Linked并不是链表法表示散列表的意思,而是双向链表和散列表结合。

LinkedHashMap本身就是一个支持LRU缓存淘汰策略的缓存系统,其数据的存取移动删除规则和LRU一样。

 

思考

今天讲的几个散列表和链表结合使用的例子里,我们用的都是双向链表。如果把双向链表改成单链表,还能否正常工作呢?为什么呢?

 

假设猎聘网有 10 万名猎头,每个猎头都可以通过做任务(比如发布职位)来积累积分,然后通过积分来下载简历。

假设你是猎聘网的一名工程师,如何在内存中存储这 10 万个猎头 ID 和积分信息,让它能够支持这样几个操作:

  • 根据猎头的 ID 快速查找、删除、更新这个猎头的积分信息;
  • 查找积分在某个区间的猎头 ID 列表;
  • 查找按照积分从小到大排名在第 x 位到第 y 位之间的猎头 …

 

    原文作者:曹丞相快跑
    原文地址: https://www.cnblogs.com/errornull/p/9952162.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞