经典数据结构-哈希链表-LRU

Ref

  1. 手撸LRU

哈希链表

概述

1、显然 cache 中的元素必须有时序,以区分最近使用的和久未使用的数据,当容量满了之后要删除最久未使用的那个元素腾位置。

2、我们要在 cache 中快速找某个 key 是否已存在并得到对应的 val;

3、每次访问 cache 中的某个 key,需要将这个元素变为最近使用的,也就是说 cache 要支持在任意位置快速插入和删除元素。

那么,什么数据结构同时符合上述条件呢?哈希表查找快,但是数据无固定顺序;链表有顺序之分,插入删除快,但是查找慢。所以结合一下,形成一种新的数据结构:哈希链表 LinkedHashMap。

LRU缓存机制

完全手撸

我们手撸出双向链表+哈希表->哈希链表,适合面试手撕:

更具体的,构建双向链表,然后尾部作为最近使用的。使用尾插法,然后让哈希表可以映射到链表的key值

这样,get方法只需要借助makeRecently方法把查询的key提升到最近使用的位置;put方法,如果有对应的key就直接删除节点deleteKey,并且重新尾插addRecently。如果不在,先判断cap有没有达到cache(也就是我们实现的双向链表)的容量,有的话使用removeLastRecently,最后addRecently

可见,本质上哈希链表就是借助哈希表给予了双向链表随机查找和修改某个key的能力,这样我们就可以实现LRU的get方法,并且也可以维持一个缓存cache来

cpp 复制代码
class Node{
public:
    int key, val;
    Node *next, *prev;
    Node(int k, int v) : key(k), val(v), next(nullptr), prev(nullptr) {}
};

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;
    }

    void addLast(Node* x){
        x->prev = tail->prev;
        x->next = tail;
        tail->prev->next = x;
        tail->prev = x;
        size++;
    }

    void remove(Node* x){
        x->prev->next = x->next;
        x->next->prev = x->prev;
        size--; 
    }

    //删除第一个节点并且返回该节点
    Node* removeFirst(){
        if(head->next == tail){
            return nullptr;
        }
        auto first = head->next;
        remove(first);
        return first;
    }

    int getSize(){ return size; }
};


class LRUCache {
private:
    unordered_map<int, Node*> mp;
    DoubleList cache;
    int cap;

public:
    LRUCache(int capacity) {
        this->cap = capacity;
    }
    
    int get(int key) {
        if(!mp.count(key)){
            return -1;
        }
        makeRecently(key);
        return mp[key]->val;
    }
    
    void put(int key, int value) {
        if(mp.count(key)){
            //如果存在,删除再加入
            deleteKey(key);
            addRecently(key, value);
            return;
        }
        //不然就额外加入,需要先判断是不是满cap(LRU),不然直接加入
        if(cap == cache.getSize()){
            removeLast();
        }
        addRecently(key, value);
    }

    void makeRecently(int key){
        Node* x = mp[key];
        cache.remove(x);
        cache.addLast(x);
    }

    void deleteKey(int key){
        Node* x = mp[key];
        cache.remove(x);
        mp.erase(key);
    }

    void addRecently(int key, int value){
        Node* x = new Node(key, value);
        cache.addLast(x);
        mp[key] = x;
    }

    void removeLast(){
        Node* dn = cache.removeFirst();
        mp.erase(dn->key);

    }



};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

借助STL库

cpp 复制代码
class LRUCache {
private:
    size_t capacity_;
    // 链表:元素为 pair<key, value>
    std::list<std::pair<int, int>> cacheList_;
    // 哈希表:key → 链表中该元素的迭代器
    std::unordered_map<int,
        typename std::list<std::pair<int, int>>::iterator> cacheMap_;

public:
    LRUCache(size_t capacity) : capacity_(capacity) {}

    int get(const int& key) {
        auto it = cacheMap_.find(key);
        if (it == cacheMap_.end()) {
            // 未命中
            return -1;
        }
        // 命中:将该节点移动到链表头部,表示最近使用
        cacheList_.splice(cacheList_.begin(), cacheList_, it->second);
        return it->second->second;
    }

    void put(const int& key, const int& value) {
        auto it = cacheMap_.find(key);
        if (it != cacheMap_.end()) {
            // 已存在:更新 value + 移动到头部
            it->second->second = value;
            cacheList_.splice(cacheList_.begin(), cacheList_, it->second);
            return;
        }
        // 新插入:如果满了,淘汰尾部(最久未使用)
        if (cacheList_.size() == capacity_) {
            auto &node = cacheList_.back();
            cacheMap_.erase(node.first);
            cacheList_.pop_back();
        }
        // 插入新节点到头部
        cacheList_.emplace_front(key, value);
        cacheMap_[key] = cacheList_.begin();
    }

    bool contains(const int& key) const {
        return cacheMap_.find(key) != cacheMap_.end();
    }

    size_t size() const {
        return cacheList_.size();
    }
};
相关推荐
八月ouc20 分钟前
Python实战小游戏(二): 文字冒险游戏
数据结构·python·文字冒险
EXtreme3529 分钟前
【数据结构】二叉树进阶:层序遍历不仅是按层打印,更是形态判定的利器!
c语言·数据结构·二叉树·bfs·广度优先搜索·算法思维·面试必考
小李小李快乐不已34 分钟前
二叉树理论基础
数据结构·c++·算法·leetcode
仰泳的熊猫38 分钟前
1149 Dangerous Goods Packaging
数据结构·c++·算法·pat考试
H_z___1 小时前
Codeforces Global Round 31 (Div. 1 + Div. 2) A ~ E
数据结构·算法
Dylan的码园1 小时前
队列与queue
java·数据结构·链表
小龙报1 小时前
【算法通关指南:算法基础篇 】双指针专题:1.唯一的雪花 2.逛画展 3.字符串 4.丢手绢
c语言·数据结构·c++·人工智能·深度学习·算法·信息与通信
阿昭L10 小时前
leetcode链表相交
算法·leetcode·链表
松涛和鸣10 小时前
Linux Makefile : From Basic Syntax to Multi-File Project Compilation
linux·运维·服务器·前端·windows·哈希算法
xiaolang_8616_wjl15 小时前
c++超级细致的基本框架
开发语言·数据结构·c++·算法