LRU缓存 数据结构设计(C++)
做LeetCode第146題LRU緩存,覺得收獲不小,特此記錄。
請你設計并實現一個滿足 LRU (最近最少使用) 緩存 約束的數據結構。 實現 LRUCache 類:
- LRUCache(int capacity) 以 正整數 作為容量 capacity 初始化 LRU 緩存。
- int get(int key) 如果關鍵字 key 存在于緩存中,則返回關鍵字的值,否則返回 -1 。
- void put(int key, int value) 如果關鍵字 key 已經存在,則變更其數據值 value ;如果不存在,則向緩存中插入該組 key-value 。如果插入操作導致關鍵字數量超過 capacity ,則應該逐出最久未使用的關鍵字。
函數 get 和 put 必須以 O(1) 的平均時間復雜度運行。
關于LRU的規則,學過操作系統的肯定都知道,此處不再贅述。
題目關鍵在于 get() 和 put() 必須O(1),這就使得我們選擇數據結構的時候需要有些考究。
首先我們回顧一下本科學過的數據結構知識,存儲這樣的多個key-value對肯定是使用線性表存儲,而線性表又分為順序表(數組)和鏈表,由于題目中涉及到了插入和刪除(逐出),且有O(1)的條件限制,所以肯定是使用鏈表來存儲。
確定了基本數據結構,我們再來想想如何使用或改造使其滿足題目需要。
首先考慮 get(),我們需要做到如果關鍵字key在緩存中,就能查找到關鍵字的值,且有O(1)的條件限制,那自然是使用哈希表,哈希表的鍵就是LRU緩存中的key自然不用說,關鍵在于 哈希表的值是什么?直觀想法那就是LRU的value唄,其實不然,因為我們其實不僅要通過key查到value,還有一個隱含的操作,即需要把我們剛剛查過的這個LRU緩存節點置于最高優先級的位置上(LRU的規則),所以該哈希表的value應該是一個鏈表節點,我們通過key查到這個鏈表節點以后,就可以對其進行操作以改變其優先級,同時我們也可以直接通過node->value來取得key對應的value值,一舉兩得。
那到底怎么體現優先級,怎么做到關鍵字的插入和逐出呢,其實很簡單,在鏈表頭部的表示優先級最高(即最近使用),在鏈表尾部的表示優先級最低(即最久未使用),這樣一來 put() 的編寫就容易了,當來一個新關鍵字的時候,就把其節點插入鏈表頭,同時更新哈希表。每當鏈表中的節點個數超過LRU的容量時,我們就刪除掉鏈表尾的的元素,同時更新哈希表。
現在我們還有一個問題需要解決,就是使用普通的單鏈表,是否能滿足put()和get()均為O(1)的題目要求。根據以上的分析,我們對鏈表會有以下操作:
根據以上對鏈表的要求,可以確定只有雙鏈表能滿足要求,單鏈表、單循環鏈表、帶尾指針的單循環鏈表都無法在O(1)的時間下做到以上三個操作,大家可以模擬一下。
確定好數據結構,代碼就很好寫了,只需按需實現LRU功能即可:
struct doubleLinkNode {int key, value;doubleLinkNode *pre, *next; };class LRUCache { private:doubleLinkNode *head, *rear;int size, capacity;unordered_map<int, doubleLinkNode*> map;void insert(doubleLinkNode* node) {// 把一個非鏈表上的節點插入到表頭node->pre = head;node->next = head->next;head->next->pre = node;head->next = node;}void insertHead(doubleLinkNode* node) {// 把一個鏈表上的節點插入到表頭node->pre->next = node->next;node->next->pre = node->pre;insert(node);}int deleteRear() {doubleLinkNode *node = rear->pre;node->pre->next = node->next;node->next->pre = node->pre;int key = node->key;delete node;return key;}public:LRUCache(int cap): size(0), capacity(cap) {head = new doubleLinkNode();rear = new doubleLinkNode();head->next = rear;head->pre = nullptr;rear->next = nullptr;rear->pre = head;}int get(int key) {if(map.count(key)) {insertHead(map[key]);return map[key]->value;}return -1;}void put(int key, int value) {if(map.count(key)) {doubleLinkNode *node = map[key];if(node->value != value) {node->value = value;}insertHead(node);} else {doubleLinkNode *node = new doubleLinkNode;node->key = key;node->value = value;insert(node);++size;if(size > capacity) {int nodeKey = deleteRear();map.erase(nodeKey);--size;}map[key] = node;}} };總結
以上是生活随笔為你收集整理的LRU缓存 数据结构设计(C++)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Linux网络编程学习】阻塞、非阻塞、
- 下一篇: C++里数组名+1和数组名的地址+1的区