時間複雜度為O(1)的LRU演算法
阿新 • • 發佈:2019-02-05
LRU,演算法在操作快取中常常被用到,由於其訪問頻繁,因此縮小LRU時間複雜度是非常必要的,好的LRU演算法的實現能夠很好的提高系統的穩定性
資料結構中map的訪問速度非常快,時間複雜度為O(1),因此在快取結構中,可以藉助map結構,同時由於快取需要容量滿時需要刪除操作,並且對於最近被訪問的需要重新置於頭部,在資料結構中連結串列能夠很好的完成該操作,故快取結構藉助map加連結串列結構來降低時間複雜度,使得查詢、刪除、交換的時間複雜度都為O(1),
具體設計如下,map中存放key -value對,查詢時可以通過key快速定位到value,
value存放的是node節點,所有的value,都是雙向連結串列中一個node,刪除時直接移除
尾部節點即可,為簡化操作,連結串列自帶頭節點head和尾節點tail
package Inter.other; /** * 快取通用實現介面介面 * Created by lin on 2018/9/19. */ public interface Cache<K, V> { <V> V get(K key); void set(K key, V value); void clear(); }
package Inter.other; /** * 鍵值生成策略介面 * Created by lin on 2018/9/19. */ public interface KeyGenerationStrategy<K, V> { K generationKey(V value); }
package Inter.other; /** * 連結串列節點的定義 * Created by lin on 2018/9/16. */ public class Node<K,V> { V value; K key;//表示該節點的鍵; Node next; Node prev; public Node(V value, K key) { this.value = value; this.key = key; } public Node(Node prev, Node next, V value) { this.prev = prev; this.next = next; this.value = value; } public K getKey() { return this.key; } @Override public String toString() { return "prev:" + prev.value + "當前節點" + this.value + "next:" + next.value; } }
package Inter.other; /** * 簡單的鍵值生成 * Created by lin on 2018/9/19. */ public class SimpleKeyGenerationStrategy<K, V> implements KeyGenerationStrategy<K, V> { @Override public K generationKey(V value) { return (K) value.toString(); } }
package Inter.other; import java.util.HashMap; import java.util.Map; /** * 快取演算法的具體實現 * Created by lin on 2018/9/16. * 時間複雜度為O(1)的一個快取 */ public class LRUCache<K, V> implements Cache<K, V> { // private KeyGenerationStrategy<K, V> keyGenerationStrategy; //預設容量大小 private static final int DEFAULT_CAPACITY = 8; /* 快取容量的大小 */ private int capacity; /* 快取已使用的容量 */ private int size; /* 為了實現快速尋找,這裡使用map,查詢時間複雜度為O(1)*/ private Map<K, Node<K, V>> map = new HashMap<>(); /* 為了實現快速替換,這裡使用連結串列,刪除或者加入時間複雜度為O(1)*/ private Node<K, V> head; private Node<K, V> tail; /** * 初始化 * * @param capacity */ public LRUCache(int capacity) { // map = new HashMap<>(); if (capacity <= 0) { capacity = DEFAULT_CAPACITY; } this.capacity = capacity; this.head = new Node<K, V>(null, null, null); this.tail = new Node<K, V>(head, null, null); head.next = tail; } /** * 從快取中獲取指定值,沒有返回空 * * @param * @param <V> * @return */ @Override public <V> V get(K key) { Node<K, V> node = (Node<K, V>) map.get(key); if (node == null) { return null; } else { moveToFirst(node); return node.value; } } /** * 指定節點新增到快取中 * * @param key value值對應的鍵 * @param value 存放的值 */ @Override public void set(K key, V value) { Node<K, V> node = new Node(value, key); //快取容量未滿,不需要淘汰,直接新增到最後一個 if (size <= capacity) { node.prev = head; node.next = head.next; head.next.prev = node; head.next = node; map.put(node.key, node); size++; } else {//容量已滿,淘汰最後一個節點即可 // map.put((K)node.key, node); Node delNode = tail.prev; delNode.prev.next = node; node.prev = delNode.prev; node.next = tail; tail.prev = node; delNode.next = null; delNode.prev = null; delNode = null; map.remove(delNode.key); } } //清空快取 @Override public void clear() { this.head = new Node<K, V>(null, null, null); this.tail = new Node<K, V>(head, null, null); head.next = tail; size = 0; } /** * 當節點被訪問時需要放置到快取最前面 * * @param node */ private void moveToFirst(Node node) { //validationIsSwap(); if (node == head.next) { return; } Node<K, V> nodePrev = node.prev; Node<K, V> nodeNext = node.next; Node beMoved = head.next;// 頭節點的下一個節點 head.next = node; node.prev = head; node.next = beMoved; beMoved.prev = node; nodePrev.next = nodeNext; nodeNext.prev = nodePrev; } /** * 確定是否可以交換,如果size小於等於1 則沒必要 * <p> * private void validationIsSwap() { * if (size <= 1) { * throw new IllegalArgumentException("快取容量不大於1,不能進行該操作"); * } * } */ public static void main(String[] args) { LRUCache<String, Integer> lruCache = new LRUCache(20); KeyGenerationStrategy<String, Integer> keyGenerationStrategy = new SimpleKeyGenerationStrategy<>(); String key1 = keyGenerationStrategy.generationKey(1); String key2 = keyGenerationStrategy.generationKey(2); String key3 = keyGenerationStrategy.generationKey(3); lruCache.set(key1, 1); lruCache.set(key2, 2); lruCache.set(key3, 3); System.out.println(lruCache.get(key1)+""); ; System.out.println(lruCache.get(key2)+""); ; System.out.println(lruCache.get(key3)+""); ; System.out.println(lruCache.get(key1)+""); ; // lruCache.swapAndFirst(node2); Node head = lruCache.head; //第一個 head = head.next; System.out.println(head); //第二個 head = head.next; System.out.println(head); //第三個 head = head.next; System.out.println(head); // lruCache.set(node1); } private Node getHead() { return this.head; } }