设计LRU缓存

LRU缓存

LRU(Least Recently Used,最近最少使用)缓存是一种常见的缓存淘汰算法,其基本思想是:当缓存空间已满时,移除最近最少使用的数据。LRU缓存通常通过链表(双向链表)和哈希表相结合来实现,利用哈希表快速查找,链表保持数据的使用顺序。

链接:leetcode 设计LRU缓存

LRU缓存的实现思路

实现思路:哈希表 + 双向链表

  • 为什么使用哈希表?

    哈希表:用来存储键值对,可以在常数时间内(O(1))进行查找、插入和删除操作。

  • 为什么使用双向带头尾链表?

    双向链表:用来维护数据的使用顺序。最近使用的元素放在链表的头部,最久未使用的元素放在链表的尾部。通过这种方式可以在O(1)的时间复杂度下实现删除最久未使用的元素。

LRU缓存的操作

  • Get(key): 如果键存在于缓存中,返回对应的值并将该键值对移到链表头部,表示最近被访问过。如果不存在,返回-1。
  • Put(key, value): 插入键值对。如果缓存已满,则删除最久未使用的元素,之后插入新的键值对,并将其移到链表头部。

C++11 STL实现LRU缓存

时间复杂度分析:

get(key):查找操作是O(1),然后通过 touch 函数将键移到链表头部,也是在O(1)时间内完成的。

put(key, value):插入或更新键值对的操作是O(1),如果缓存满了需要删除最久未使用的元素(evict),删除操作也是O(1)。因此,get 和 put 操作的时间复杂度都是 O(1)。

cpp 复制代码
#include <iostream>
#include <unordered_map>
#include <list>

class LRUCache {
public:
    LRUCache(int capacity) : capacity(capacity) {}

    // 获取缓存中的值
    int get(int key) {
        auto it = cache.find(key);
        if (it == cache.end()) {
            return -1;  // 未找到键,返回 -1
        }
        touch(it);  // 标记为最近使用
        return it->second.first;  // 返回对应的值
    }

    // 插入新的键值对
    void put(int key, int value) {
        auto it = cache.find(key);
        if (it != cache.end()) {
            touch(it);  // 标记为最近使用
            it->second.first = value;  // 更新值
        } else {
            if (cache.size() == capacity) {
                evict();  // 删除最久未使用的元素
            }
            // 插入新的键值对到链表头部
            order.push_front(key);
            cache[key] = {value, order.begin()};  // 存入哈希表,值和链表位置
        }
    }

private:
    // 更新元素为最近使用
    void touch(std::unordered_map<int, std::pair<int, std::list<int>::iterator>>::iterator it) {
        int key = it->first;
        order.erase(it->second.second);  // 删除当前元素
        order.push_front(key);  // 将元素插入链表头部
        it->second.second = order.begin();  // 更新迭代器位置
    }

    // 淘汰最久未使用的元素
    void evict() {
        int key_to_evict = order.back();  // 获取尾部元素(最久未使用)
        order.pop_back();  // 从链表中移除
        cache.erase(key_to_evict);  // 从哈希表中删除
    }

    int capacity;  // 缓存容量
    std::list<int> order;  // 双向链表,维护键的访问顺序
    std::unordered_map<int, std::pair<int, std::list<int>::iterator>> cache;  // 哈希表,存储键值对和链表位置
};

int main() {
    LRUCache cache(2);  // 设置缓存容量为2
    cache.put(1, 1);    // 缓存: {1=1}
    cache.put(2, 2);    // 缓存: {1=1, 2=2}
    std::cout << cache.get(1) << std::endl;  // 返回 1,缓存: {2=2, 1=1}
    cache.put(3, 3);    // 淘汰键 2,缓存: {1=1, 3=3}
    std::cout << cache.get(2) << std::endl;  // 返回 -1 (未找到)
    cache.put(4, 4);    // 淘汰键 1,缓存: {3=3, 4=4}
    std::cout << cache.get(1) << std::endl;  // 返回 -1 (未找到)
    std::cout << cache.get(3) << std::endl;  // 返回 3
    std::cout << cache.get(4) << std::endl;  // 返回 4

    return 0;
}

效果:代码简洁,但效率不高。

自行设计双向链表 + 哈希表

cpp 复制代码
#include <iostream>
#include <unordered_map>

using namespace std;

struct DLinkedNode {
    int key, value;
    DLinkedNode* prev;
    DLinkedNode* next;
    DLinkedNode() : key(0), value(0), prev(nullptr), next(nullptr) {}
    DLinkedNode(int _key, int _value) : key(_key), value(_value), prev(nullptr), next(nullptr) {}
};

class LRUCache {
private:
    unordered_map<int, DLinkedNode*> cache;
    DLinkedNode* head;
    DLinkedNode* tail;
    int size;
    int capacity;

public:
    LRUCache(int _capacity) : capacity(_capacity), size(0) {
        // 使用伪头部和伪尾部节点
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head->next = tail;
        tail->prev = head;
    }
    
    int get(int key) {
        if (!cache.count(key)) {
            return -1;  // 如果找不到该键,返回 -1
        }
        DLinkedNode* node = cache[key];
        moveToHead(node);  // 移动到链表头部
        return node->value;  // 返回值
    }
    
    void put(int key, int value) {
        if (!cache.count(key)) {
            // 如果 key 不存在,创建新节点
            DLinkedNode* node = new DLinkedNode(key, value);
            cache[key] = node;
            addToHead(node);  // 添加到链表头部
            ++size;
            if (size > capacity) {
                // 超过容量,删除尾部节点
                DLinkedNode* removed = removeTail();
                cache.erase(removed->key);  // 从哈希表中删除该键
                delete removed;  // 防止内存泄漏
                --size;
            }
        }
        else {
            // 如果 key 存在,更新值,并移到头部
            DLinkedNode* node = cache[key];
            node->value = value;
            moveToHead(node);
        }
    }

    void addToHead(DLinkedNode* node) {
        node->prev = head;
        node->next = head->next;
        head->next->prev = node;
        head->next = node;
    }
    
    void removeNode(DLinkedNode* node) {
        node->prev->next = node->next;
        node->next->prev = node->prev;
    }

    void moveToHead(DLinkedNode* node) {
        removeNode(node);  // 移除节点
        addToHead(node);   // 重新添加到头部
    }

    DLinkedNode* removeTail() {
        DLinkedNode* node = tail->prev;
        removeNode(node);
        return node;
    }
};

int main() {
    LRUCache cache(2);  // 缓存容量为2
    cache.put(1, 1);    // 缓存: {1=1}
    cache.put(2, 2);    // 缓存: {1=1, 2=2}
    cout << cache.get(1) << endl;  // 返回 1,缓存: {2=2, 1=1}
    cache.put(3, 3);    // 淘汰键 2,缓存: {1=1, 3=3}
    cout << cache.get(2) << endl;  // 返回 -1,键 2 不存在
    cache.put(4, 4);    // 淘汰键 1,缓存: {3=3, 4=4}
    cout << cache.get(1) << endl;  // 返回 -1,键 1 不存在
    cout << cache.get(3) << endl;  // 返回 3
    cout << cache.get(4) << endl;  // 返回 4

    return 0;
}

代码转自力扣官方题解。

效果:时间复杂度明显降低, 效率提高。

总结

上述实现利用了哈希表和双向链表的组合,保证了LRU缓存操作的高效性。哈希表提供了O(1)的查找和更新时间,而双向链表提供了O(1)的插入和删除操作,确保了缓存的高效管理。这个实现适用于高性能缓存系统,如数据库缓存、Web缓存等。

相关推荐
时光の尘1 小时前
C语言菜鸟入门·关键字·int的用法
c语言·开发语言·数据结构·c++·单片机·链表·c
sjsjs111 小时前
【数据结构-表达式解析】【hard】力扣224. 基本计算器
数据结构·算法·leetcode
C++忠实粉丝1 小时前
计算机网络socket编程(6)_TCP实网络编程现 Command_server
网络·c++·网络协议·tcp/ip·计算机网络·算法
坊钰1 小时前
【Java 数据结构】时间和空间复杂度
java·开发语言·数据结构·学习·算法
武昌库里写JAVA1 小时前
一文读懂Redis6的--bigkeys选项源码以及redis-bigkey-online项目介绍
c语言·开发语言·数据结构·算法·二维数组
feilieren1 小时前
DataGrip 连接 Redis、TongRDS
数据库·redis·缓存
孙克旭_1 小时前
第三章 分布式缓存Redis
redis·分布式·缓存
Allen Bright1 小时前
Jedis存储一个-以String的形式的对象到Redis
数据库·redis·缓存
禊月初三1 小时前
LeetCode 4.寻找两个中序数组的中位数
c++·算法·leetcode
学习使我飞升2 小时前
spf算法、三类LSA、区间防环路机制/规则、虚连接
服务器·网络·算法·智能路由器