LRU 緩存機制實現(xiàn):哈希表 + 雙向鏈表

算法詳解


LRU 緩存機制可以通過哈希表輔以雙向鏈表實現(xiàn),我們用一個哈希表和一個雙向鏈表維護(hù)所有在緩存中的鍵值對。


雙向鏈表按照被使用的順序存儲了這些鍵值對,靠近頭部的鍵值對是最近使用的,而靠近尾部的鍵值對是最久未使用的。

哈希表即為普通的哈希映射(HashMap),通過緩存數(shù)據(jù)的鍵映射到其在雙向鏈表中的位置。

這樣以來,我們首先使用哈希表進(jìn)行定位,找出緩存項在雙向鏈表中的位置,隨后將其移動到雙向鏈表的頭部,即可在 O(1) 的時間內(nèi)完成 get 或者 put 操作。具體的方法如下:


對于 get 操作,首先判斷 key 是否存在:

如果 key 不存在,則返回 ?1;

如果 key 存在,則 key 對應(yīng)的節(jié)點是最近被使用的節(jié)點。通過哈希表定位到該節(jié)點在雙向鏈表中的位置,并將其移動到雙向鏈表的頭部,最后返回該節(jié)點的值。




對于 put 操作,首先判斷 key 是否存在:

如果 key 不存在,使用 key 和 value 創(chuàng)建一個新的節(jié)點,在雙向鏈表的頭部添加該節(jié)點,并將 key 和該節(jié)點添加進(jìn)哈希表中。然后判斷雙向鏈表的節(jié)點數(shù)是否超出容量,如果超出容量,則刪除雙向鏈表的尾部節(jié)點,并刪除哈希表中對應(yīng)的項;

如果 key 存在,則與 get 操作類似,先通過哈希表定位,再將對應(yīng)的節(jié)點的值更新為 value,并將該節(jié)點移到雙向鏈表的頭部。







上述各項操作中,訪問哈希表的時間復(fù)雜度為 O(1),在雙向鏈表的頭部添加節(jié)點、在雙向鏈表的尾部刪除節(jié)點的復(fù)雜度也為O(1)。而將一個節(jié)點移到雙向鏈表的頭部,可以分成「刪除該節(jié)點」和「在雙向鏈表的頭部添加節(jié)點」兩步操作,都可以在 O(1) 時間內(nèi)完成。


小貼士




在雙向鏈表的實現(xiàn)中,使用一個偽頭部(dummy head)和偽尾部(dummy tail)標(biāo)記界限,這樣在添加節(jié)點和刪除節(jié)點的時候就不需要檢查相鄰的節(jié)點是否存在。


Java源代碼:

```java

public class LRUCache {

? ? class DLinkedNode {

? ? ? ? int key;

? ? ? ? int value;

? ? ? ? DLinkedNode prev;

? ? ? ? DLinkedNode next;

? ? ? ? public DLinkedNode() {}

? ? ? ? public DLinkedNode(int _key, int _value) {key = _key; value = _value;}

? ? }


? ? private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();

? ? private int size;

? ? private int capacity;

? ? private DLinkedNode head, tail;


? ? public LRUCache(int capacity) {

? ? ? ? this.size = 0;

? ? ? ? this.capacity = capacity;

? ? ? ? // 使用偽頭部和偽尾部節(jié)點

? ? ? ? head = new DLinkedNode();

? ? ? ? tail = new DLinkedNode();

? ? ? ? head.next = tail;

? ? ? ? tail.prev = head;

? ? }


? ? public int get(int key) {

? ? ? ? DLinkedNode node = cache.get(key);

? ? ? ? if (node == null) {

? ? ? ? ? ? return -1;

? ? ? ? }

? ? ? ? // 如果 key 存在,先通過哈希表定位,再移到頭部

? ? ? ? moveToHead(node);

? ? ? ? return node.value;

? ? }


? ? public void put(int key, int value) {

? ? ? ? DLinkedNode node = cache.get(key);

? ? ? ? if (node == null) {

? ? ? ? ? ? // 如果 key 不存在,創(chuàng)建一個新的節(jié)點

? ? ? ? ? ? DLinkedNode newNode = new DLinkedNode(key, value);

? ? ? ? ? ? // 添加進(jìn)哈希表

? ? ? ? ? ? cache.put(key, newNode);

? ? ? ? ? ? // 添加至雙向鏈表的頭部

? ? ? ? ? ? addToHead(newNode);

? ? ? ? ? ? ++size;

? ? ? ? ? ? if (size > capacity) {

? ? ? ? ? ? ? ? // 如果超出容量,刪除雙向鏈表的尾部節(jié)點

? ? ? ? ? ? ? ? DLinkedNode tail = removeTail();

? ? ? ? ? ? ? ? // 刪除哈希表中對應(yīng)的項

? ? ? ? ? ? ? ? cache.remove(tail.key);

? ? ? ? ? ? ? ? --size;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? else {

? ? ? ? ? ? // 如果 key 存在,先通過哈希表定位,再修改 value,并移到頭部

? ? ? ? ? ? node.value = value;

? ? ? ? ? ? moveToHead(node);

? ? ? ? }

? ? }


? ? private void addToHead(DLinkedNode node) {

? ? ? ? node.prev = head;

? ? ? ? node.next = head.next;

? ? ? ? head.next.prev = node;

? ? ? ? head.next = node;

? ? }


? ? private void removeNode(DLinkedNode node) {

? ? ? ? node.prev.next = node.next;

? ? ? ? node.next.prev = node.prev;

? ? }


? ? private void moveToHead(DLinkedNode node) {

? ? ? ? removeNode(node);

? ? ? ? addToHead(node);

? ? }


? ? private DLinkedNode removeTail() {

? ? ? ? DLinkedNode res = tail.prev;

? ? ? ? removeNode(res);

? ? ? ? return res;

? ? }

}

```

作者:LeetCode-Solution

鏈接:https://leetcode-cn.com/problems/lru-cache/solution/lruhuan-cun-ji-zhi-by-leetcode-solution/

來源:力扣(LeetCode)

著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容