题目来源
LeetCode: https://leetcode.com/problems/lru-cache/
LRU简介
LRU (Least Recently Used,最近最少使用)算法是操作系统中一种经典的页面置换算法,当发生缺页中断时,需要将内存的一个或几个页面换出,LRU指出应该将内存最近最少使用的那些页面进行换出,依据的是程序的局部性原理,最近经常使用的页面在不久的将来也很有可能被使用,反之最近很少使用的页面未来也不太可能再使用。
LRU 数据结构
LRU采用双向链表+hash表的数据结构实现,双向链表作为队列存储当前缓存节点,其中从表头到表尾的元素按照最近使用的时间进行排列,放在表头的是最近刚刚被使用过的元素,表尾的最近最少使用的元素;如果仅仅采用双向链表,那么查询某个元素需要 O(n) 的时间,为了加快双向链表中元素的查询速度,采用hash表讲key进行映射,可以在O(1)的时间内找到需要节点。
LRU主要实现以下两个接口:
int Get(int key); void Put(int key, int value);
其中 Get 用来读取队列中的元素,同时需要将该元素移动到表头;Put 用来向队列中插入元素。
LRU 具体实现
从实现的角度来看,每次 Get 时, 需要判断该 key 是否在队列中,如果不在,返回-1;如果在,需要重新移动该元素到表头位置(具体实现,可以先删除,在插入到表头)。 每次 Put 时,首先需要判断key是否在队列中,如果在,那么更新其 value值,然后移动该元素到表头即可;如果不在,需要进一步判断,队列是否已满,如果已满;那么需要首先删除队尾元素,并对 size - 1, 删除哈希表中对应元素的 key;然后在插入新的元素到队头。
具体C++代码如下:
1 /** 2 * LRU Cache Implementation using DoubleLinkList & hashtable 3 * Copyright 2015 python27 4 * 2015/06/26 5 */ 6 #include <iostream> 7 #include <string> 8 #include <map> 9 #include <list> 10 #include <deque> 11 #include <cassert> 12 #include <cstdio> 13 #include <cstdlib> 14 using namespace std; 15 16 struct CacheNode 17 { 18 int key; 19 int value; 20 CacheNode* prev; 21 CacheNode* next; 22 23 CacheNode(int k, int v) : key(k), value(v), prev(NULL), next(NULL) 24 {} 25 26 CacheNode():key(0), value(0), prev(NULL), next(NULL) 27 {} 28 }; 29 30 class LRUCache 31 { 32 public: 33 LRUCache(int capacity); 34 35 int Get(int key); 36 void Put(int key, int value); 37 38 public: 39 void PrintList() const; 40 private: 41 void InsertNodeFront(CacheNode* p); 42 void DeleteNode(CacheNode* p); 43 44 private: 45 map<int, CacheNode*> m_hashtable; // hash table 46 CacheNode* m_head; // double link list head 47 CacheNode* m_tail; // double link list tail 48 int m_capacity; // capacity of link list 49 int m_size; // current size of link list 50 }; 51 52 void LRUCache::PrintList() const 53 { 54 CacheNode* p = m_head; 55 for (p = m_head; p != NULL; p = p->next) 56 { 57 printf("(%d, %d)->", p->key, p->value); 58 } 59 printf(" "); 60 printf("size = %d ", m_size); 61 printf("capacity = %d ", m_capacity); 62 } 63 64 LRUCache::LRUCache(int capacity) 65 { 66 m_capacity = capacity; 67 m_size = 0; 68 m_head = NULL; 69 m_tail = NULL; 70 } 71 72 // insert node into head pointed by p 73 void LRUCache::InsertNodeFront(CacheNode* p) 74 { 75 if (p == NULL) return; 76 77 if (m_head == NULL) 78 { 79 m_head = p; 80 m_tail = p; 81 } 82 else 83 { 84 p->next = m_head; 85 m_head->prev = p; 86 m_head = p; 87 } 88 } 89 90 // delete node in double linklist pointed by p 91 void LRUCache::DeleteNode(CacheNode* p) 92 { 93 if (p == NULL) return; 94 95 assert(m_head != NULL && m_tail != NULL); 96 97 if (m_size == 1) 98 { 99 if (p == m_head && p == m_tail) 100 { 101 delete p; 102 m_head = NULL; 103 m_tail = NULL; 104 } 105 else 106 { 107 fprintf(stderr, "Delete Wrong! No such Node"); 108 return; 109 } 110 } 111 else if (p == m_head) 112 { 113 m_head = m_head->next; 114 m_head->prev = NULL; 115 delete p; 116 } 117 else if (p == m_tail) 118 { 119 m_tail = m_tail->prev; 120 m_tail->next = NULL; 121 delete p; 122 } 123 else 124 { 125 p->prev->next = p->next; 126 p->next->prev = p->prev; 127 delete p; 128 } 129 130 } 131 132 int LRUCache::Get(int key) 133 { 134 // if key not in return -1 135 if (m_hashtable.find(key) == m_hashtable.end()) 136 { 137 return -1; 138 } 139 140 CacheNode* p = m_hashtable[key]; 141 int k = p->key; 142 int v = p->value; 143 144 // delete this node 145 DeleteNode(p); 146 147 // insert this node to the head 148 p = new CacheNode(k, v); 149 InsertNodeFront(p); 150 // update hash table 151 m_hashtable[k] = p; 152 return p->value; 153 } 154 155 void LRUCache::Put(int key, int value) 156 { 157 // if key alread in, update 158 if (m_hashtable.find(key) != m_hashtable.end()) 159 { 160 CacheNode* p = m_hashtable[key]; 161 162 // delete node 163 DeleteNode(p); 164 // insert node 165 p = new CacheNode(key, value); 166 InsertNodeFront(p); 167 // update hash table 168 m_hashtable[key] = p; 169 return; 170 } 171 // if list is full, delete the tail node 172 else if (m_size >= m_capacity) 173 { 174 // delete the tail node 175 CacheNode* p = m_tail; 176 m_hashtable.erase(p->key); 177 DeleteNode(p); 178 m_size--; 179 } 180 181 // create node and insert into head 182 assert(m_size < m_capacity); 183 CacheNode* p = new CacheNode(key, value); 184 InsertNodeFront(p); 185 m_hashtable[key] = p; 186 m_size++; 187 } 188 189 int main() 190 { 191 LRUCache lru(3); 192 lru.Put(1, 11); 193 lru.PrintList(); 194 lru.Put(2, 22); 195 lru.PrintList(); 196 lru.Put(3, 33); 197 lru.PrintList(); 198 lru.Put(4, 44); 199 lru.PrintList(); 200 int value = lru.Get(3); 201 printf("Get(3) = %d ", value); 202 lru.PrintList(); 203 value = lru.Get(2); 204 printf("Get(2) = %d ", value); 205 lru.PrintList(); 206 value = lru.Get(4); 207 printf("Get(4) = %d ", value); 208 lru.PrintList(); 209 value = lru.Get(1); 210 printf("Get(1) = %d ", value); 211 lru.PrintList(); 212 213 return 0; 214 }