Leetcode: Implementation of LRUCache in four versions
Design and implement a data structure for Least Recently Used (LRU) cache. it shoshould support the following operations: get and set. get (key)-Get the value (will always be positive) of the key if the key exists in the cache, otherwise return-1.set( key, value) -Set or insert the value if the key is not already present. when the cache reached its capacity, it shocould invalidate the least recently u Sed item before inserting a new item. analysis 1: Maintain the least recently used cache (LRU). 1. Use count to count the cache. Each time the cache is operated (get, set), the count of the cache is set to 0, the remaining cache's count plus 1, and the maximum count is the latest and least used cache. 2) use a linked list to move the cache to the linked list header during each cache operation (get and set, the cache at the end of the linked list is the least recently used cache 2: quick search cache 1) Use stl: unordered_map to store the cache address (internal hashTable) Version 1 use std: list to maintain LRU, the actual cache space in the linked list. Runtime: 248 ms. 1 # include <list> 2 # include <unordered_map> 3 4 struct KeyValue {5 KeyValue (int pKey, int pValue): key (pKey), value (pValue ){}; 6 int key; 7 int value; 8}; 9 10 class LRUCache {11 public: 12 LRUCache (int capacity): _ capacity (capacity ){}; 13 14 int get (int key); 15 void set (int key, int value); 16 17 private: 18 std: unordered_map <int, std: list <KeyValue>:: iterator> keyToNodeItr; 19 std: list <KeyValue> Lru; 20 21 int _ capacity; 22}; 23 24 void LRUCache: set (int key, int value) {25 auto itr = keyToNodeItr. find (key); 26 if (itr! = KeyToNodeItr. end () {// set value27 itr-> second-> value = value; 28 KeyValue tmp (itr-> second-> key, itr-> second-> value ); 29 keyToNodeItr. erase (itr-> second-> key); 30 lru. erase (itr-> second); 31 lru. push_front (tmp); 32} else {// insert value33 if (lru. size ()! = _ Capacity) {34 lru. push_front (KeyValue (key, value); 35} else {36 // pop back lru37 if (lru. size ()! = 0) {38 keyToNodeItr. erase (lru. rbegin ()-> key); 39 lru. pop_back (); 40} 41 lru. push_front (KeyValue (key, value); 42} 43} 44 45 keyToNodeItr. insert (std: pair <int, std: list <KeyValue >:: iterator> (key, lru. begin (); 46} 47 48 int LRUCache: get (int key) {49 auto itr = keyToNodeItr. find (key); 50 if (itr! = KeyToNodeItr. end () {51 int value = itr-> second-> value; 52 53 KeyValue tmp (itr-> second-> key, itr-> second-> value ); 54 keyToNodeItr. erase (itr-> second-> key); 55 lru. erase (itr-> second); 56 lru. push_front (tmp); 57 keyToNodeItr. insert (std: pair <int, std: list <KeyValue >:: iterator> (key, lru. begin (); 58 59 return value; 60} 61 return-1; 62} because the linked list stores the actual cache space, when you need to change the cache location, the linked list and map both need to be changed, resulting in high overhead. Version 2 uses std: list to maintain LRU and store cache pointers in the linked list. Runtime: 186 ms. 1 # include <list> 2 # include <unordered_map> 3 4 struct KeyValue {5 KeyValue (int pKey, int pValue): key (pKey), value (pValue ){}; 6 int key; 7 int value; 8}; 9 10 class LRUCache {11 public: 12 LRUCache (int capacity): _ capacity (capacity ){}; 13 14 int get (int key); 15 void set (int key, int value); 16 17 ~ LRUCache (); 18 19 private: 20 std: unordered_map <int, std: list <KeyValue * >:: iterator> keyToNodeItr; 21 std :: list <KeyValue *> lru; 22 23 int _ capacity; 24}; 25 26 LRUCache ::~ LRUCache () {27 for (auto itr = lru. begin (); itr! = Lru. end (); ++ itr) {28 delete * itr; 29} 30} 31 32 void LRUCache: set (int key, int value) {33 auto itr = keyToNodeItr. find (key); 34 if (itr! = KeyToNodeItr. end () {// set value35 KeyValue * tmp = * (itr-> second); 36 tmp-> value = value; 37 lru. erase (itr-> second); 38 lru. push_front (tmp); 39 itr-> second = lru. begin (); // avoid invalid iterator40} else {// insert value41 if (lru. size () = _ capacity) {// pop back lru42 KeyValue * tmp = * (lru. rbegin (); 43 keyToNodeItr. erase (tmp-> key); 44 delete tmp; 45 lru. pop_back (); 46} 47 lru. push_front (new KeyV Alue (key, value); 48 keyToNodeItr. insert (std: pair <int, std: list <KeyValue * >:: iterator> (key, lru. begin (); 49} 50} 51 52 int LRUCache: get (int key) {53 auto itr = keyToNodeItr. find (key); 54 if (itr! = KeyToNodeItr. end () {55 KeyValue * kvPtr = * (itr-> second); 56 lru. erase (itr-> second); 57 lru. push_front (kvPtr); 58 itr-> second = lru. begin (); 59 return kvPtr-> value; 60} 61 return-1; 62} note that the list iterator is stored in map, therefore, you still need to reset the key-to-iterator ing in map to prevent the iterator from being invalid. Version 3 seems that std: list is too cumbersome. so implements a lightweight double-stranded table instead of std: list. Runtime: 77 ms. 1 struct BiListNode {2 BiListNode () {}; 3 BiListNode (int key, int value): key (key), value (value) {}; 4 int key; 5 int value; 6 BiListNode * pre; 7 BiListNode * next; 8}; 9 10 class BiList {11 public: 12 BiList (): _ count (0) {13 _ head = new BiListNode (); 14 _ head-> pre = _ head; 15 _ head-> next = _ head; 16} 17 18 void push_front (BiListNode * pNode); 19 20 void move_front (BiListNode * pNode); 21 22 BiLis TNode * begin () {23 return _ head-> next; 24} 25 26 BiListNode * rbegin () {27 return _ head-> pre; 28} 29 30 void pop_back (); 31 32 int size () {return _ count;} 33 34 ~ BiList (); 35 36 private: 37 BiListNode * _ head; 38 int _ count; 39}; 40 41 void BiList: push_front (BiListNode * pNode) {42 pNode-> next = _ head-> next; 43 pNode-> pre = _ head; 44 _ head-> next-> pre = pNode; 45 _ head-> next = pNode; 46 if (_ head-> pre = _ head) {47 _ head-> pre = pNode; 48} 49 ++ _ count; 50} 51 52 void BiList: move_front (BiListNode * pNode) {53 if (pNode = _ head-> next) {54 return; 55} 56 PNode-> pre-> next = pNode-> next; 57 pNode-> next-> pre = pNode-> pre; 58 59 pNode-> next = _ head-> next; 60 pNode-> pre = _ head; 61 62 _ head-> next-> pre = pNode; 63 64 _ head-> next = pNode; 65} 66 67 void BiList :: pop_back () {68 BiListNode * tailPtr = _ head-> pre; 69 tailPtr-> pre-> next = _ head; 70 _ head-> pre = tailPtr-> pre; 71 delete tailPtr; 72 -- _ count; 73} 74 75 BiList ::~ BiList () {76 for (BiListNode * itr = _ head-> next; itr! = _ Head; itr = itr-> next) {77 delete itr; 78} 79 delete _ head; 80} 81 82 83 class LRUCache {84 public: 85 LRUCache (int capacity): _ capacity (capacity) {}; 86 87 int get (int key); 88 89 void set (int key, int value); 90 91 private: 92 int _ capacity; 93 94 BiList biList; 95 std: unordered_map <int, BiListNode *> keyToNodePtr; 96}; 97 98 int LRUCache: get (int key) {99 auto itr = keyToNodePtr. find (key ); 100 if (itr! = KeyToNodePtr. end () {101 biList. move_front (itr-> second); 102 return itr-> second-> value; 103} 104 return-1; 105} 106 107 void LRUCache: set (int key, int value) {108 auto itr = keyToNodePtr. find (key); 109 if (itr! = KeyToNodePtr. end () {// set value110 itr-> second-> value = value; 111 biList. move_front (itr-> second); 112} else {// insert113 if (biList. size () = _ capacity) {114 keyToNodePtr. erase (biList. rbegin ()-> key); 115 biList. pop_back (); 116} 117 biList. push_front (new BiListNode (key, value); 118 keyToNodePtr. insert (std: pair <int, BiListNode *> (key, biList. begin (); 119} 120} the self-implemented double-chain table has only 80 lines of code, greatly improving the code running efficiency. Version 4 dual-chain tables are all implemented by themselves, so they are stuck to the end, and then implement an open chain hash table by themselves. Runtime: 66 ms. 1 struct BiListNode {2 BiListNode () {}; 3 BiListNode (int key, int value): key (key), value (value) {}; 4 int key; 5 int value; 6 BiListNode * pre; 7 BiListNode * next; 8}; 9 10 class BiList {11 public: 12 BiList (): _ count (0) {13 _ head = new BiListNode (); 14 _ head-> pre = _ head; 15 _ head-> next = _ head; 16} 17 18 void push_front (BiListNode * pNode); 19 20 void move_front (BiListNode * pNode); 21 22 BiLis TNode * begin () {23 return _ head-> next; 24} 25 26 BiListNode * rbegin () {27 return _ head-> pre; 28} 29 30 void pop_back (); 31 32 int size () {return _ count;} 33 34 ~ BiList (); 35 36 private: 37 BiListNode * _ head; 38 int _ count; 39}; 40 41 void BiList: push_front (BiListNode * pNode) {42 pNode-> next = _ head-> next; 43 pNode-> pre = _ head; 44 _ head-> next-> pre = pNode; 45 _ head-> next = pNode; 46 if (_ head-> pre = _ head) {47 _ head-> pre = pNode; 48} 49 ++ _ count; 50} 51 52 void BiList: move_front (BiListNode * pNode) {53 if (pNode = _ head-> next) {54 return; 55} 56 PNode-> pre-> next = pNode-> next; 57 pNode-> next-> pre = pNode-> pre; 58 59 pNode-> next = _ head-> next; 60 pNode-> pre = _ head; 61 62 _ head-> next-> pre = pNode; 63 64 _ head-> next = pNode; 65} 66 67 void BiList :: pop_back () {68 BiListNode * tailPtr = _ head-> pre; 69 tailPtr-> pre-> next = _ head; 70 _ head-> pre = tailPtr-> pre; 71 delete tailPtr; 72 -- _ count; 73} 74 75 BiList ::~ BiList () {76 for (BiListNode * itr = _ head-> next; itr! = _ Head; itr = itr-> next) {77 delete itr; 78} 79 delete _ head; 80} 81 82 struct hashNode {83 hashNode (int key, BiListNode * ptr): key (key), ptr (ptr), next (NULL) {}; 84 int key; 85 BiListNode * ptr; 86 hashNode * next; 87 }; 88 89 class HashTable {90 public: 91 HashTable (int capacity); 92 93 hashNode * find (int key); 94 95 void insert (int key, BiListNode * ptr ); 96 97 void erase (int key); 98 99 ~ HashTable (); 100 101 private: 102 int _ capacity; 103 hashNode ** hashArray; 104}; 105 106 HashTable: HashTable (int capacity): _ capacity (capacity) {107 hashArray = new hashNode * [capacity]; 108 for (int I = 0; I <_ capacity; ++ I) {109 hashArray [I] = NULL; 110} 111} 112 113 hashNode * HashTable: find (int key) {114 for (hashNode * itr = hashArray [key % _ capacity]; itr! = NULL; 115 itr = itr-> next) {116 if (itr-> key = key) {117 return itr; 118} 119} 120 return NULL; 121} 122 123 void HashTable: insert (int key, BiListNode * ptr) {124 hashNode * tmp = new hashNode (key, ptr ); 125 126 int relativeKey = key % _ capacity; 127 128 if (hashArray [relativeKey] = NULL) {129 hashArray [relativeKey] = tmp; 130 return; 131} 132 133 tmp-> next = hashArray [relativeKey]; 134 hashArray [relati VeKey] = tmp; 135} 136 void HashTable: erase (int key) {137 for (hashNode * pre = hashArray [key % _ capacity], * itr = pre; 139 itr! = NULL; pre = itr, itr = itr-> next) {140 if (itr-> key = key) {141 if (itr! = Pre) 142 pre-> next = itr-> next; 143 else // head144 hashArray [key % _ capacity] = itr-> next; 145 146 delete itr; 147} 148} 149} 150 151 HashTable ::~ HashTable () {152 for (int I = 0; I <_ capacity; ++ I) {153 for (hashNode * itr = hashArray [I]; itr! = NULL;) {154 hashNode * tmp = itr; 155 itr = itr-> next; 156 delete tmp; 157} 158 delete [] hashArray; 160} 161 162 class LRUCache {163 public: 164 LRUCache (int capacity): _ capacity (capacity) {165 hashTable = new HashTable (1024); 166 }; 167 168 int get (int key); 169 170 void set (int key, int value); 171 172 ~ LRUCache () {delete hashTable;} 173 174 private: 175 int _ capacity; 176 BiList bilist; 177 HashTable * hashTable; 178}; 179 180 int LRUCache: get (int key) {181 hashNode * tmp = hashTable-> find (key); 182 if (tmp! = NULL) {183 bilist. move_front (tmp-> ptr); 184 return tmp-> ptr-> value; 185} 186 return-1; 187} 188 189 void LRUCache: set (int key, int value) {190 hashNode * tmp = hashTable-> find (key); 191 if (tmp! = NULL) {// set192 bilist. move_front (tmp-> ptr); 193 tmp-> ptr-> value = value; 194 return; 195} 196 197 // insert198 if (bilist. size () = _ capacity) {199 hashTable-> erase (bilist. rbegin ()-> key); 200 bilist. pop_back (); 201} 202 203 bilist. push_front (new BiListNode (key, value); 204 hashTable-> insert (key, bilist. begin (); 205}