LRU机制:哈希表+双向链表 [labuladong-刷题打卡 day9]

发布时间 2023-08-09 20:13:25作者: Alan-Blog

今天的知识点LRU缓存机制的实现。学过计组都知道LRU算法(least recently used 最近最少使用算法)是资源管理中的常用算法。那么他是如何实现的呢?
LRU原理和Redis实现

图片名称

146. LRU 缓存此题算是对LRU机制的一个简化。
为了使查找删除在O(1)中实现,我们结合哈希表和双向链表各自在查找和删除插入中的优势,得到LRU的数据结构:哈希链表,其实在Linux内核中也采用了哈希链表
下面采用带你手撸 LRU 算法

哈希链表的实现

双向链表的实现

双向链表节点

//双向链表节点
class Node {
    public:
        int key, val;
        Node* next;
        Node* prev;
        Node(int k, int v){
            this->key = k;
            this->val = v;
        }
};

双向链表类

定义节点后,自然就可以构建一个双向链表,并且封装后续需要的插入删除等操作了!

class DoubleList {
private:
    // 头尾虚节点
    Node* head;
    Node* tail;
    // 链表元素数
    int size;

public:
    DoubleList() {
        // 初始化双向链表的数据
        head = new Node(0, 0);
        tail = new Node(0, 0);
        head->next = tail;
        tail->prev = head;
        size = 0;
    }

    // 在链表尾部添加节点 x,时间 O(1)
    void addLast(Node* x) {
        x->prev = tail->prev;
        x->next = tail;
        tail->prev->next = x;
        tail->prev = x;
        size++;
    }

    // 删除链表中的 x 节点(x 一定存在)
    // 由于是双链表且给的是目标 Node 节点,时间 O(1)
    void remove(Node* x) {
        x->prev->next = x->next;
        x->next->prev = x->prev;
        size--;
    }

    // 删除链表中第一个节点,并返回该节点,时间 O(1)
    Node* removeFirst() {
        if (head->next == tail)
            return nullptr;
        Node* first = head->next;
        remove(first);
        return first;
    }

    // 返回链表长度,时间 O(1)
    int getSize() { return size; }
};

哈希链表类

实现双向链表后,结合哈希表实现get,put方法
get方法主要是根据哈希表查找,如果查找到就移动节点至链表尾,并返回值
put方法也需要先查找,存在则修改值并移动至链表尾,如果不存在,但已经达到容量,则删除头节点添加新节点至链表尾
(黑体字为封装的私有函数或需要实现的功能)

class LRUCache {
private:
    /* 节点结构体 */
    struct Node {
        int key, val;
        Node* next;
        Node(int _key, int _val) : key(_key), val(_val), next(nullptr) {}
    };
    /* 哈希表存储节点指针 */
    unordered_map<int, Node*> map;
    /* 虚拟头节点 */
    Node* head;
    /* 虚拟尾节点 */
    Node* tail;
    /* 缓存容量 */
    int capacity;

public:
    LRUCache(int _capacity) : capacity(_capacity) {
        /* 初始化虚拟头尾节点 */
        head = new Node(-1, -1);
        tail = new Node(-1, -1);
        head->next = tail;
        tail->next = nullptr;
    }

    int get(int key) {
        if (!map.count(key)) {
            return -1;
        }
        /* 通过哈希表定位到节点 */
        Node* node = map[key];
        /* 将节点移动到链表尾部 */
        moveToEnd(node);
        return node->val;
    }

    void put(int key, int value) {
        if (map.count(key)) {
            /* 如果已存在,修改对应节点的值,并移动到链表尾部 */
            Node* node = map[key];
            node->val = value;
            moveToEnd(node);
            return;
        }
        if (map.size() == capacity) {
            /* 如果超出容量,删除链表头部节点 */
            deleteHead();
        }
        /* 添加新的节点到链表尾部,并在哈希表中添加映射 */
        addNode(new Node(key, value));
    }

private:
    /* 将节点移动到链表尾部 */
    void moveToEnd(Node* node) {
        removeNode(node);
        addNode(node);
    }

    /* 删除链表头部节点 */
    void deleteHead() {
        Node* node = head->next;
        removeNode(node);
        map.erase(node->key);
        delete node;
    }

    /* 添加节点到链表尾部 */
    void addNode(Node* node) {
        tail->next = node;
        node->next = nullptr;
        map[node->key] = node;
        tail = node;
    }

    /* 删除链表中的节点 */
    void removeNode(Node* node) {
        Node* prev = head;
        while (prev->next != node) {
            prev = prev->next;
        }
        prev->next = node->next;
    }
};