LRU缓存 数据结构设计(C++)

做LeetCode第146题LRU缓存,觉得收获不小,特此记录。

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类:

  • LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存。
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该逐出最久未使用的关键字。

函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

关于LRU的规则,学过操作系统的肯定都知道,此处不再赘述。

题目关键在于 get() 和 put() 必须O(1),这就使得我们选择数据结构的时候需要有些考究。

首先我们回顾一下本科学过的数据结构知识,存储这样的多个key-value对肯定是使用线性表存储,而线性表又分为顺序表(数组)和链表,由于题目中涉及到了插入和删除(逐出),且有O(1)的条件限制,所以肯定是使用链表来存储。

确定了基本数据结构,我们再来想想如何使用或改造使其满足题目需要。

首先考虑 get(),我们需要做到如果关键字key在缓存中,就能查找到关键字的值,且有O(1)的条件限制,那自然是使用哈希表,哈希表的键就是LRU缓存中的key自然不用说,关键在于 哈希表的值是什么?直观想法那就是LRU的value呗,其实不然,因为我们其实不仅要通过key查到value,还有一个隐含的操作,即需要把我们刚刚查过的这个LRU缓存节点置于最高优先级的位置上(LRU的规则),所以该哈希表的value应该是一个链表节点,我们通过key查到这个链表节点以后,就可以对其进行操作以改变其优先级,同时我们也可以直接通过node->value来取得key对应的value值,一举两得。

那到底怎么体现优先级,怎么做到关键字的插入和逐出呢,其实很简单,在链表头部的表示优先级最高(即最近使用),在链表尾部的表示优先级最低(即最久未使用),这样一来 put() 的编写就容易了,当来一个新关键字的时候,就把其节点插入链表头,同时更新哈希表。每当链表中的节点个数超过LRU的容量时,我们就删除掉链表尾的的元素,同时更新哈希表。

现在我们还有一个问题需要解决,就是使用普通的单链表,是否能满足put()和get()均为O(1)的题目要求。根据以上的分析,我们对链表会有以下操作:

  1. 把一个新节点插入链表头。当我们put()一个新关键字,其不存在于缓存中,且缓存没有满时,就把这个新的关键字节点插入链表头。
  2. 把一个链表中的节点移动到链表头。当我们get()一个关键字,其存在于缓存中,我们就需要把这个节点从当前位置插入到链表头部。
  3. 删除链表尾的节点。当我们put()一个新关键字,其不存在于缓存中,我们需要插入新节点,但此时缓存满了,我们需要把最近最少使用的节点从链表中剔除掉,也就是删除链表尾的节点。

根据以上对链表的要求,可以确定只有双链表能满足要求,单链表、单循环链表、带尾指针的单循环链表都无法在O(1)的时间下做到以上三个操作,大家可以模拟一下。

确定好数据结构,代码就很好写了,只需按需实现LRU功能即可:

struct doubleLinkNode {
    int key, value;
    doubleLinkNode *pre, *next;
};

class LRUCache {
private:
    doubleLinkNode *head, *rear;
    int size, capacity;
    unordered_map<int, doubleLinkNode*> map;

    void insert(doubleLinkNode* node) {
        // 把一个非链表上的节点插入到表头
        node->pre = head;
        node->next = head->next;
        head->next->pre = node;
        head->next = node;
    }

    void insertHead(doubleLinkNode* node) {
        // 把一个链表上的节点插入到表头
        node->pre->next = node->next;
        node->next->pre = node->pre;
        insert(node);
    }

    int deleteRear() {
        doubleLinkNode *node = rear->pre;
        node->pre->next = node->next;
        node->next->pre = node->pre;
        int key = node->key;
        delete node;
        return key;
    }

public:
    LRUCache(int cap): size(0), capacity(cap) {
        head = new doubleLinkNode();
        rear = new doubleLinkNode();
        head->next = rear;
        head->pre = nullptr;
        rear->next = nullptr;
        rear->pre = head;
    }
    
    int get(int key) {
        if(map.count(key)) {
            insertHead(map[key]);
            return map[key]->value;
        }
        return -1;
    }
    
    void put(int key, int value) {
        if(map.count(key)) {
            doubleLinkNode *node = map[key];
            if(node->value != value) {
                node->value = value;
            }
            insertHead(node);
        } else {
            doubleLinkNode *node = new doubleLinkNode;
            node->key = key;
            node->value = value;
            insert(node);
            ++size;
            if(size > capacity) {
                int nodeKey = deleteRear();
                map.erase(nodeKey);
                --size;
            }
            map[key] = node;
        }
    }
};
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值