JavaScript雙向鏈表實現LFU緩存算法
什麼是LFU
LFU(Least Frequently Used),最近最少使用策略,也就是說在一段時間內,數據被使用頻次最少的,優先被淘汰。
它是一種用於管理計算機內存的緩存算法,采用LFU算法的最簡單方法是為每個加載到緩存的塊分配一個計數器。每次引用該塊時,計數器將增加一。當緩存達到容量並有一個新的內存塊等待插入時,系統將搜索計數器最低的塊並將其從緩存中刪除。
描述
請你為 最不經常使用(LFU)緩存算法設計並實現數據結構。
實現 LFUCache 類:
LFUCache(int capacity) – 用數據結構的容量 capacity 初始化對象
int get(int key) – 如果鍵 key 存在於緩存中,則獲取鍵的值,否則返回 -1 。
void put(int key, int value) – 如果鍵 key 已存在,則變更其值;如果鍵不存在,請插入鍵值對。
當緩存達到其容量 capacity 時,則應該在插入新項之前,移除最不經常使用的項。
在此問題中,當存在平局(即兩個或更多個鍵具有相同使用頻率)時,應該去除 最近最久未使用 的鍵。
為瞭確定最不常使用的鍵,可以為緩存中的每個鍵維護一個 使用計數器 。使用計數最小的鍵是最久未使用的鍵。
當一個鍵首次插入到緩存中時,它的使用計數器被設置為 1 (由於 put 操作)。對緩存中的鍵執行 get 或 put 操作,使用計數器的值將會遞增。
函數 get 和 put 必須以 O(1) 的平均時間復雜度運行。
解題思路
1、構造節點結構體
保存對應的數據信息
const LFUNode = function ( key = "", val = "", freq = 0, pre = null, next = null ) { this.key = key; this.val = val; this.freq = freq; this.pre = pre; this.next = next; };
2、構造雙向鏈表
構造帶有頭尾節點的雙向鏈表,head和tail為哨兵節點,不保存信息,僅用於標記頭尾。
const LFULinkLine = function (node) { let head = new LFUNode("head"); let tail = new LFUNode("tail"); head.next = node; tail.pre = node; node.pre = head; node.next = tail; this.head = head; this.tail = tail; };
3、編寫鏈表頭添加節點方法
將節點插入到鏈表的頭結點之後,其他節點往後移。
LFULinkLine.prototype.addNode = function (node) { this.head.next.pre = node; node.pre = this.head; node.next = this.head.next; this.head.next = node; };
4、編寫刪除節點方法
將節點從鏈表中刪除。
LFULinkLine.prototype.removeNode = function (node) { node.pre.next = node.next; node.next.pre = node.pre; };
5、構造LRU緩存結構體
構造LRU緩存結構體,具體信息如下代碼,capacity用於保存最大緩存數,即該類的容量;num保存當前存儲的數據量,用於判別是否超出最大容量;minFreq保存當前存儲的數據中的最小頻率,刪除的時候需要優先刪除頻率最小的;kvMap保存節點詳細信息,索引為節點的key值,查詢可以直接從這裡查出信息;freqMap保存對應頻率的鏈表信息,索引為節點的freq(頻率),刪除的時候可以快速從這裡獲取需要刪除節點的信息。
/** * @param {number} capacity */ var LFUCache = function (capacity) { this.capacity = capacity;//最大緩存 this.num = 0;//當前數目 this.minFreq = 0;//當前最小頻率 this.kvMap = new Map();//保存節點信息 this.freqMap = new Map();//保存對應頻率的鏈表信息 };
6、編寫get方法
get主要有以下兩種情況:
(1)節點存在
- 通過kvMap獲取到對應節點
- 將節點從freqMap中刪除
- 判斷是否需要修改minFreq
- 修改節點的freq
- 重新將節點插入freqMap
- 返回節點的value
(2)節點不存在
容量capacity為0或者kvMap中沒有該節點信息,直接返回-1即可。
/** * @param {number} key * @return {number} */ LFUCache.prototype.get = function (key) { if (this.capacity === 0) return -1; if (!this.kvMap.has(key)) return -1; //通過kvMap獲取到對應節點 let node = this.kvMap.get(key); let linkLine = this.freqMap.get(node.freq); //將節點從freqMap中刪除 linkLine.removeNode(node); //判斷是否需要修改minFreq //清空瞭 if (linkLine.head.next === linkLine.tail) { this.freqMap.delete(node.freq); if (this.minFreq == node.freq) this.minFreq++; } //修改節點的freq node.freq++; //重新將節點插入freqMap if (this.freqMap.has(node.freq)) { linkLine = this.freqMap.get(node.freq); linkLine.addNode(node); } else { this.freqMap.set(node.freq, new LFULinkLine(node)); } //返回節點的value return node.val; };
7、編寫put方法
put操作主要有以下兩種情況:
(1)更新
- 通過kvMap獲取到對應節點
- 將節點從freqMap中刪除
- 判斷是否需要修改minFreq
- 更新節點信息
- 重新將節點插入freqMap
(2)存入
存入情況下又有兩種情況:
容量已滿
- 通過minFreq找到需要刪除的節點
- 將節點從freqMap中刪除
- 判斷是否需要修改minFreq
- 將新節點插入freqMap
- 更新minFreq
容量未滿
- 修改num
- 將新節點插入freqMap
- 更新minFreq
/** * @param {number} key * @param {number} value * @return {void} */ LFUCache.prototype.put = function (key, value) { if (this.capacity === 0) return; if (this.kvMap.has(key)) { //更新 //通過kvMap獲取到對應節點 let node = this.kvMap.get(key); //將節點從freqMap中刪除 let linkLine = this.freqMap.get(node.freq); linkLine.removeNode(node); //判斷是否需要修改minFreq if (linkLine.head.next === linkLine.tail) { if (this.minFreq == node.freq) this.minFreq++; this.freqMap.delete(node.freq); } //更新節點信息 node.val = value; node.freq++; //重新將節點插入freqMap if (this.freqMap.has(node.freq)) { linkLine = this.freqMap.get(node.freq); linkLine.addNode(node); } else { linkLine = new LFULinkLine(node); this.freqMap.set(node.freq, linkLine); } } else {//存入 if (this.capacity == this.num) {//存滿 //通過minFreq找到需要刪除的節點 let freq = this.minFreq; let linkLine = this.freqMap.get(freq); let node = linkLine.tail.pre; //將節點從freqMap中刪除 linkLine.removeNode(node); this.kvMap.delete(node.key); //判斷是否需要修改minFreq if (linkLine.head.next === linkLine.tail) { this.freqMap.delete(node.freq); } } else { //修改num this.num++; } //將新節點插入freqMap let node = new LFUNode(key, value, 0); this.kvMap.set(key, node); if (this.freqMap.has(0)) { let linkLine = this.freqMap.get(0); linkLine.addNode(node); } else { let linkLine = new LFULinkLine(node); this.freqMap.set(0, linkLine); } //更新minFreq this.minFreq = 0; } };
代碼
const LFUNode = function ( key = "", val = "", freq = 0, pre = null, next = null ) { this.key = key; this.val = val; this.freq = freq; this.pre = pre; this.next = next; }; const LFULinkLine = function (node) { let head = new LFUNode("head"); let tail = new LFUNode("tail"); head.next = node; tail.pre = node; node.pre = head; node.next = tail; this.head = head; this.tail = tail; }; LFULinkLine.prototype.addNode = function (node) { this.head.next.pre = node; node.pre = this.head; node.next = this.head.next; this.head.next = node; }; LFULinkLine.prototype.removeNode = function (node) { node.pre.next = node.next; node.next.pre = node.pre; }; /** * @param {number} capacity */ var LFUCache = function (capacity) { this.capacity = capacity; this.num = 0; this.minFreq = 0; this.kvMap = new Map(); this.freqMap = new Map(); }; /** * @param {number} key * @return {number} */ LFUCache.prototype.get = function (key) { if (this.capacity === 0) return -1; if (!this.kvMap.has(key)) return -1; let node = this.kvMap.get(key); let linkLine = this.freqMap.get(node.freq); linkLine.removeNode(node); //清空瞭 if (linkLine.head.next === linkLine.tail) { this.freqMap.delete(node.freq); if (this.minFreq == node.freq) this.minFreq++; } node.freq++; if (this.freqMap.has(node.freq)) { linkLine = this.freqMap.get(node.freq); linkLine.addNode(node); } else { this.freqMap.set(node.freq, new LFULinkLine(node)); } return node.val; }; /** * @param {number} key * @param {number} value * @return {void} */ LFUCache.prototype.put = function (key, value) { if (this.capacity === 0) return; if (this.kvMap.has(key)) { //更新 let node = this.kvMap.get(key); node.val = value; let linkLine = this.freqMap.get(node.freq); linkLine.removeNode(node); if (linkLine.head.next === linkLine.tail) { if (this.minFreq == node.freq) this.minFreq++; this.freqMap.delete(node.freq); } node.freq++; if (this.freqMap.has(node.freq)) { linkLine = this.freqMap.get(node.freq); linkLine.addNode(node); } else { linkLine = new LFULinkLine(node); this.freqMap.set(node.freq, linkLine); } } else { //存入 if (this.capacity == this.num) { //存滿 let freq = this.minFreq; let linkLine = this.freqMap.get(freq); let node = linkLine.tail.pre; linkLine.removeNode(node); this.kvMap.delete(node.key); if (linkLine.head.next === linkLine.tail) { this.freqMap.delete(node.freq); } } else { this.num++; } let node = new LFUNode(key, value, 0); this.kvMap.set(key, node); if (this.freqMap.has(0)) { let linkLine = this.freqMap.get(0); linkLine.addNode(node); } else { let linkLine = new LFULinkLine(node); this.freqMap.set(0, linkLine); } this.minFreq = 0; } }; /** * Your LFUCache object will be instantiated and called as such: * var obj = new LFUCache(capacity) * var param_1 = obj.get(key) * obj.put(key,value) */
到此這篇關於JavaScript雙向鏈表實現LFU緩存算法的文章就介紹到這瞭,更多相關JavaScript LFU緩存算法內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- python基於雙向鏈表實現LFU算法
- 如何利用Go語言實現LRU Cache
- C++ 實現LRU 與 LFU 的緩存算法
- Java之理解Redis回收算法LRU案例講解
- Java實現常用緩存淘汰算法:FIFO、LRU、LFU