|
42 | 42 | */ |
43 | 43 |
|
44 | 44 | /** |
45 | | - * 方法一:哈希表 + 双向链表 |
| 45 | + * 实现方法:哈希表 + 双向链表 |
46 | 46 | * |
47 | 47 | * LRU 缓存机制可以通过哈希表辅以双向链表实现,可以用一个哈希表和一个双向链表维护所有在缓存中的简直队。 |
48 | 48 | * 1. 双向链表按照被使用的顺序存储这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的。 |
49 | 49 | * 2. 哈希表即为普通的哈希映射(HashMap),通过缓存数据的键映射到其在双向链表中的位置。 |
50 | 50 | * |
51 | 51 | * 我们使用哈希表进行定位,找出缓存项在双向链表中的位置,随后将其移动到双向链表的头部,即可在O(1)的时间内完成 get 或者 put 操作。 |
52 | 52 | * 具体的方法如下: |
53 | | - * 1. |
| 53 | + * 1. 对于 get 操作,首先判断 key 是否存在: |
| 54 | + * a. 如果 key 不存在,则返回 -1; |
| 55 | + * b. 如果 key 存在,则 key 对应的节点是最近被使用的节点。通过哈希表定位到该节点在双向链表的位置, |
| 56 | + * 并将其移动到双向链表的头部,最后返回该节点的值。 |
| 57 | + * 2. 对于 put 操作,首先判断 key 是否存在: |
| 58 | + * a. 如果 key 不存在,使用 key 和 value 创建一个新的节点,在双向链表的头部添加该节点,并将 key 和该节点添加进哈希表中。 |
| 59 | + * 然后判断双向链表的节点数是否超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希表中对应的项 |
| 60 | + * b. 如果 key 存在,则与 get 操作类似,先通过哈希表定位,再将对应的节点的值更新为 value,并将该节点移到双向链表的头部。 |
| 61 | + * |
| 62 | + * 上述操作中,访问哈希表的时间复杂度为 O(1),在双向链表的头部添加节点、在双向链表的尾部删除节点的复杂度也为 O(1)。 |
| 63 | + * 而将一个节点移到双向链表的头部,可以分成「删除该节点」和「在双向链表的头部添加节点」两步操作,都可以在 O(1) 时间内完成。 |
| 64 | + * |
| 65 | + * NOTE: |
| 66 | + * 在双向链表的实现中,使用一个伪头部(dummy head)和伪尾部(dummy tail)标记界限,这样在添加节点和删除节点的时候就不需要检查相邻的节点是否存在 |
| 67 | + * |
| 68 | + * 复杂度: |
| 69 | + * 时间复杂度:对于 put 和 get 操作都是O(1); |
| 70 | + * 空间复杂度:O(Capacity),因为哈希表和双向链表最多存储 capacity + 1个元素 |
54 | 71 | * |
55 | 72 | */ |
| 73 | + |
| 74 | +#include <iostream> |
| 75 | +#include <unordered_map> |
| 76 | + |
| 77 | +using namespace std; |
| 78 | + |
| 79 | +// 双向链表结构体 |
| 80 | +struct DLinkedNode |
| 81 | +{ |
| 82 | + int key, value; |
| 83 | + DLinkedNode *prev; |
| 84 | + DLinkedNode *next; |
| 85 | + DLinkedNode() : key(0), value(0), prev(nullptr), next(nullptr) {} |
| 86 | + DLinkedNode(int _key, int _value) : key(_key), value(_value), prev(nullptr), next(nullptr) {} |
| 87 | +}; |
| 88 | + |
| 89 | +class LRUCache |
| 90 | +{ |
| 91 | +private: |
| 92 | + /* data */ |
| 93 | + unordered_map<int, DLinkedNode *> cache; |
| 94 | + DLinkedNode *head; |
| 95 | + DLinkedNode *tail; |
| 96 | + int size; |
| 97 | + int capacity; |
| 98 | + |
| 99 | +public: |
| 100 | + LRUCache(int _capacity) : capacity(_capacity), size(0) |
| 101 | + { |
| 102 | + head = new DLinkedNode(); |
| 103 | + tail = new DLinkedNode(); |
| 104 | + head->next = tail; |
| 105 | + tail->prev = head; |
| 106 | + }; |
| 107 | + |
| 108 | + int get(int key) |
| 109 | + { |
| 110 | + // 如果 key 不存在,则返回 -1 |
| 111 | + if (!cache.count(key)) |
| 112 | + { |
| 113 | + return -1; |
| 114 | + } |
| 115 | + |
| 116 | + DLinkedNode *node = cache[key]; |
| 117 | + |
| 118 | + return node->value; |
| 119 | + } |
| 120 | + |
| 121 | + void put(int key, int value) |
| 122 | + { |
| 123 | + if (!cache.count(key)) |
| 124 | + { |
| 125 | + // 如果 key 不存在,创建一个新的节点 |
| 126 | + DLinkedNode *node = new DLinkedNode(key, value); |
| 127 | + // 添加 node 到哈希表 |
| 128 | + cache[key] = node; |
| 129 | + |
| 130 | + // 将节点添加到双向链表头部 |
| 131 | + addToHead(node); |
| 132 | + ++size; // Cache size 加一 |
| 133 | + |
| 134 | + if (size > capacity) |
| 135 | + { |
| 136 | + // 如果超出容量,则删除双向链表的尾部节点 |
| 137 | + DLinkedNode *removed = removeTail(); |
| 138 | + // 同时删除哈希表中对应的项 |
| 139 | + cache.erase(removed->key); |
| 140 | + // 删除节点 removed,防止内存泄漏 |
| 141 | + delete removed; |
| 142 | + --size; // Cache size 减一 |
| 143 | + } |
| 144 | + } |
| 145 | + else |
| 146 | + { |
| 147 | + // 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部 |
| 148 | + DLinkedNode *node = cache[key]; |
| 149 | + node->value = value; |
| 150 | + moveToHead(node); |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + // 添加到双向链表头部 |
| 155 | + void addToHead(DLinkedNode *node) |
| 156 | + { |
| 157 | + node->prev = head; |
| 158 | + node->next = head->next; |
| 159 | + head->next->prev = node; |
| 160 | + head->next = node; |
| 161 | + } |
| 162 | + |
| 163 | + // 从双向链表移除节点 |
| 164 | + void removeNode(DLinkedNode *node) |
| 165 | + { |
| 166 | + node->prev->next = node->next; |
| 167 | + node->next->prev = node->prev; |
| 168 | + } |
| 169 | + |
| 170 | + // 移动到双向链表头部,即先做移除节点操作(调用removeNode),然后添加到双向链表头部(调用addToHead) |
| 171 | + void moveToHead(DLinkedNode *node) |
| 172 | + { |
| 173 | + removeNode(node); |
| 174 | + addToHead(node); |
| 175 | + } |
| 176 | + |
| 177 | + // 移除双向链表尾部节点 |
| 178 | + DLinkedNode *removeTail() |
| 179 | + { |
| 180 | + DLinkedNode *node = tail->prev; |
| 181 | + removeNode(node); |
| 182 | + return node; |
| 183 | + } |
| 184 | +}; |
0 commit comments