leetcode - LRU缓存

打印 上一主题 下一主题

主题 799|帖子 799|积分 2397

什么是 LRU

LRU (迩来最少利用算法),
   最早是在操作系统中打仗到的, 它是一种内存数据淘汰策略, 常用于缓存系统的淘汰策略.
  
  LRU算法基于局部性原理, 即迩来被访问的数据在未来被访问的概率更高, 因此应该保留迩来被访问的数据.
  迩来最少利用的表明

   LRU (迩来最少利用算法), 中的 "迩来" 不是其绝对值的修饰, 而是一个范围.
如: 你迩来去了那些地方, 迩来看了哪些书.
而不是: 离你迩来的人是谁, 离你迩来的座位是哪一个. 
  了解了迩来的意义, 那么串联起来就是: 迩来利用的一堆数据中, 哪一个数据利用的是最少的
LRU原理

下面展示了 LRU 算法的基本原理.


可以看到, 在 LRU 算法中, 涉及到了对象的移动, 假如利用 数组 来作为缓存, 那么移动对象的服从很慢. 因为在这个算法中, 经常涉及到头插元素, 数组 的头插是O(n^2), 非常的慢.
所以推荐利用 双向链表 来实现.
146. LRU 缓存 - 力扣(LeetCode)
但是在题目中, 要求查找和插入的时间复杂度为O(1);
双向链表的插入删除时间复杂度为O(1), 但是查找的时间复杂度为O(n).
双向链表 + 哈希表

单利用双向链表, 查找的时间复杂度为O(n), 那么数据布局的查找操作的时间复杂度为O(1)?
答案很明显: 哈希表

 界说链表节点 ListNode

  1. struct ListNode
  2. {
  3. public:
  4.     ListNode()
  5.     {}
  6.     ListNode(int k, int v)
  7.     :key(k),
  8.     value(v)
  9.     {}
  10.     ~ListNode()
  11.     {}
  12.     int key;
  13.     int value;
  14.     // 节点中不仅存储 value, 还存储 key, 这在后面的 put 函数中有用
  15.     ListNode* next;
  16.     ListNode* prev;
  17. };
复制代码
LRUcache 成员属性

  1. class LRUCache {
  2. public:
  3.     int _size = 0; // 记录缓存中已经缓存了多少数据
  4.     int _capacity = 0; // 记录缓存大小 (可缓存的数据个数)
  5.     ListNode* head = nullptr; // 双向链表的头节点
  6.     ListNode* tail = nullptr; // 双向链表的尾节点
  7.     unordered_map<int, ListNode*> table;
  8.     // 底层是通过 hashtable 实现的map, 用来通过 kev 查找节点
  9. }
复制代码
LRUcache 成员方法

构造 / get / put 函数

  1. class LRUCache {
  2. public:
  3.     LRUCache(int capacity) {
  4.         _capacity = capacity; // 记录缓存的大小
  5.         // 初始化链表的 头节点 和 尾节点
  6.         head = new ListNode;
  7.         tail = new ListNode;
  8.         // 将头尾节点连接起来
  9.         head->next = tail;
  10.         head->prev = tail;
  11.         tail->next = head;
  12.         tail->prev = head;
  13.     }
  14.     // 通过 key 获取对应的 value. 如果 key 不存在, 则返回 -1
  15.     int get(int key) {
  16.         auto it = table.find(key); // 通过 hashtable 查找 key 是否存在
  17.         if(it == table.end())
  18.         {
  19.             return -1; // 不存在对应的 [key, value], 返回 -1
  20.         }
  21.         // 存在 key, 记录value, 然后更新这个节点, 将这个节点移动到链表头部
  22.         int ret = it->second->value;
  23.         MoveToHead(it->second); // 将这个节点移动到头部
  24.         return ret;
  25.     }
  26.     // 插入一对键值对 [key, value]
  27.     void put(int key, int value) {
  28.         auto it = table.find(key); // 在 hashtable 中查找是否已经存在 key
  29.         if(it != table.end()) // 已经存在 key 则更新节点的值, 并且将这个节点移动到链表头部
  30.         {
  31.             // 更新节点
  32.             it->second->value = value;
  33.             MoveToHead(it->second); // 将节点移动到链表头部
  34.             return; // 直接返回, 下面是进行插入的操作
  35.         }
  36.         
  37.         // key 不存在, 判断 空间是否已满, 满了就需要删除 链表末尾的节点
  38.         if(_size == _capacity)
  39.         {
  40.             // ListNode 中记录的 key 就起作用了, 如果只有 value, 那么就还需要遍历 table
  41.             int back = tail->prev->key;
  42.             table.erase(back); // 删除 hashtable 中这个节点的记录
  43.             pop_back(); // 删除尾部节点
  44.             --_size;
  45.         }
  46.         // 链表末尾的节点已被删除, 现在需要向 链表头部 插入 新的节点
  47.         ListNode* node = push_front(key, value);
  48.         table[key] = node; // 在 hashtable 中记录这个新的节点
  49.         ++_size;
  50.     }
  51. };
复制代码
MoveToHead / push_front / pop_back 函数

  1. class LRUCache {
  2. public:
  3.     // 将 node 移动到链表头部
  4.     void MoveToHead(ListNode* node)
  5.     {
  6.         if(node == head->next) // 如果这个节点就是头部, 那么就不移动
  7.         {
  8.             return;
  9.         }
  10.         ListNode* node_next = node->next; // 记录 node 节点的后一个节点
  11.         ListNode* node_prev = node->prev; // 记录 node 节点的前一个节点
  12.         node_prev->next = node_next; // 将 node 的前后节点连接起来
  13.         node_next->prev = node_prev;
  14.         // 将 node 节点链接到链表首部
  15.         node->prev = head;
  16.         node->next = head->next;
  17.         head->next->prev = node;
  18.         head->next = node;
  19.     }
  20.     // 头插
  21.     ListNode* push_front(int key, int value)
  22.     {
  23.         ListNode* node = new ListNode(key, value);
  24.         ListNode* next = head->next;
  25.         head->next = node;
  26.         node->prev = head;
  27.         next->prev = node;
  28.         node->next = next;
  29.         return node;
  30.     }
  31.     // 尾删
  32.     void pop_back()
  33.     {
  34.         ListNode* prev = tail->prev->prev;
  35.         ListNode* cur = tail->prev;
  36.         prev->next = tail;
  37.         tail->prev = prev;
  38.         delete cur;
  39.     }
  40. };
复制代码
 

 
完整代码

  1. class LRUCache {
  2. public:
  3.     struct ListNode
  4.     {
  5.     public:
  6.         ListNode()
  7.         {}
  8.         ListNode(int k, int v)
  9.         :key(k),
  10.         value(v)
  11.         {}
  12.         ~ListNode()
  13.         {}
  14.         int key;
  15.         int value;
  16.         ListNode* next;
  17.         ListNode* prev;
  18.     };
  19.     int _size = 0;
  20.     int _capacity = 0;
  21.     ListNode* head = nullptr;
  22.     ListNode* tail = nullptr;
  23.     unordered_map<int, ListNode*> table;
  24.     LRUCache(int capacity) {
  25.         _capacity = capacity;
  26.         head = new ListNode;
  27.         tail = new ListNode;
  28.         head->next = tail;
  29.         head->prev = tail;
  30.         tail->next = head;
  31.         tail->prev = head;
  32.     }
  33.    
  34.     int get(int key) {
  35.         auto it = table.find(key);
  36.         if(it == table.end())
  37.         {
  38.             return -1;
  39.         }
  40.         int ret = it->second->value;
  41.         MoveToHead(it->second); // 将这个节点移动到头部
  42.         return ret;
  43.     }
  44.    
  45.     void put(int key, int value) {
  46.         auto it = table.find(key);
  47.         if(it != table.end())
  48.         {
  49.             // 更新节点
  50.             it->second->value = value;
  51.             MoveToHead(it->second);
  52.             return;
  53.         }
  54.         if(_size == _capacity)
  55.         {
  56.             int back = tail->prev->key;
  57.             table.erase(back); // 删除 hashtable 中的键值对
  58.             pop_back(); // 删除尾部节点
  59.             --_size;
  60.         }
  61.         ListNode* node = push_front(key, value);
  62.         table[key] = node;
  63.         ++_size;
  64.     }
  65.     void MoveToHead(ListNode* node)
  66.     {
  67.         if(node == head->next)
  68.         {
  69.             return;
  70.         }
  71.         ListNode* node_next = node->next;
  72.         ListNode* node_prev = node->prev;
  73.         node_prev->next = node_next;
  74.         node_next->prev = node_prev;
  75.         node->prev = head;
  76.         node->next = head->next;
  77.         head->next->prev = node;
  78.         head->next = node;
  79.     }
  80.     ListNode* push_front(int key, int value)
  81.     {
  82.         ListNode* node = new ListNode(key, value);
  83.         ListNode* next = head->next;
  84.         head->next = node;
  85.         node->prev = head;
  86.         next->prev = node;
  87.         node->next = next;
  88.         return node;
  89.     }
  90.     void pop_back()
  91.     {
  92.         ListNode* prev = tail->prev->prev;
  93.         ListNode* cur = tail->prev;
  94.         prev->next = tail;
  95.         tail->prev = prev;
  96.         delete cur;
  97.     }
  98. };
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

忿忿的泥巴坨

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表