经典数据结构-哈希链表-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();
    }
};
相关推荐
梵刹古音5 分钟前
【C语言】 递归函数
c语言·数据结构·算法
代码游侠30 分钟前
C语言核心概念复习(二)
c语言·开发语言·数据结构·笔记·学习·算法
you-_ling1 小时前
数据结构:5.哈希表
数据结构·散列表
鲨辣椒100862 小时前
二叉树代码变现——递归函数实现深度遍历
数据结构
2301_810730102 小时前
python第四次作业
数据结构·python·算法
春栀怡铃声2 小时前
认识二叉树~
c语言·数据结构·经验分享·c·编译
仰泳的熊猫2 小时前
题目1434:蓝桥杯历届试题-回文数字
数据结构·c++·算法·蓝桥杯
ygklwyf2 小时前
模拟退火算法零基础快速入门
数据结构·c++·算法·模拟退火算法
寄存器漫游者3 小时前
数据结构 二叉树与哈希表
数据结构·散列表
Sayuanni%33 小时前
数据结构_Map和Set
java·数据结构