日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

06 | 链表(上):如何实现LRU缓存淘汰算法?

發布時間:2023/12/10 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 06 | 链表(上):如何实现LRU缓存淘汰算法? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?

緩存

作用

緩存是一種提高數據讀取性能的技術,在硬件設計、軟件開發中都有著非常廣泛的應用,比如常見的 CPU 緩存、數據庫緩存、瀏覽器緩存等等。

淘汰策略

常見的策略有三種:先進先出策略 FIFO(First In,First Out)、最少使用策略 LFU(Least Frequently Used)、最近最少使用策略 LRU(Least Recently Used)。

鏈表

不需要一塊連續的內存空間,它通過“指針”將一組零散的內存塊串聯起來使用。三種最常見的鏈表結構,它們分別是:單鏈表、雙向鏈表和循環鏈表

鏈表通過指針將一組零散的內存塊串聯在一起。其中,我們把內存塊稱為鏈表的“結點”。為了將所有的結點串起來,每個鏈表的結點除了存儲數據之外,還需要記錄鏈上的下一個結點的地址。如圖所示,我們把這個記錄下個結點地址的指針叫作后繼指針 next。

單鏈表

兩個結點是比較特殊的,它們分別是第一個結點和最后一個結點。我們習慣性地把第一個結點叫作頭結點,把最后一個結點叫作尾結點。其中,頭結點用來記錄鏈表的基地址。有了它,我們就可以遍歷得到整條鏈表。而尾結點特殊的地方是:指針不是指向下一個結點,而是指向一個空地址 NULL,表示這是鏈表上最后一個結點。

循環鏈表

循環鏈表是一種特殊的單鏈表。實際上,循環鏈表也很簡單。它跟單鏈表唯一的區別就在尾結點。我們知道,單鏈表的尾結點指針指向空地址,表示這就是最后的結點了。而循環鏈表的尾結點指針是指向鏈表的頭結點

雙向鏈表

單向鏈表只有一個方向,結點只有一個后繼指針 next 指向后面的結點。而雙向鏈表,顧名思義,它支持兩個方向,每個結點不止有一個后繼指針 next 指向后面的結點,還有一個前驅指針 prev 指向前面的結點

從結構上來看,雙向鏈表可以支持 O(1) 時間復雜度的情況下找到前驅結點,正是這樣的特點,也使雙向鏈表在某些情況下的插入、刪除等操作都要比單鏈表簡單、高效。

刪除給定指針指向的結點:

單鏈表并不支持直接獲取前驅結點,所以,為了找到前驅結點,我們還是要從頭結點開始遍歷鏈表,直到 p->next=q,說明 p 是 q 的前驅結點

雙向鏈表來說,這種情況就比較有優勢了。因為雙向鏈表中的結點已經保存了前驅結點的指針,不需要像單鏈表那樣遍歷。所以,針對第二種情況,單鏈表刪除操作需要 O(n) 的時間復雜度,而雙向鏈表只需要在 O(1) 的時間復雜度內就搞定了

對于一個有序鏈表,雙向鏈表的按值查詢的效率也要比單鏈表高一些。因為,我們可以記錄上次查找的位置 p,每次查詢時,根據要查找的值與 p 的大小關系,決定是往前還是往后查找,所以平均只需要查找一半的數據。

實際的軟件開發中,雙向鏈表盡管比較費內存,但還是比單鏈表的應用更加廣泛的原因。如果你熟悉 Java 語言,你肯定用過 LinkedHashMap 這個容器。如果你深入研究 LinkedHashMap 的實現原理,就會發現其中就用到了雙向鏈表這種數據結構。

總結:當內存空間充足的時候,如果我們更加追求代碼的執行速度,我們就可以選擇空間復雜度相對較高、但時間復雜度相對很低的算法或者數據結構。相反,如果內存比較緊缺,比如代碼跑在手機或者單片機上,這個時候,就要反過來用時間換空間的設計思路。

思考

如何基于鏈表實現 LRU 緩存淘汰算法?

維護一個有序單鏈表,越靠近鏈表尾部的結點是越早之前訪問的。當有一個新的數據被訪問時,我們從鏈表頭開始順序遍歷鏈表。

1. 如果此數據之前已經被緩存在鏈表中了,我們遍歷得到這個數據對應的結點,并將其從原來的位置刪除,然后再插入到鏈表的頭部。

2. 如果此數據沒有在緩存鏈表中,又可以分為兩種情況:如果此時緩存未滿,則將此結點直接插入到鏈表的頭部;如果此時緩存已滿,則鏈表尾結點刪除,將新的數據結點插入鏈表的頭部。

提供一個基于數組實現的LRU緩存淘汰算法,上代碼

public class ArrLRU<T> {//默認容量private static final int DEFALUT_CAPACITY = (1 << 3);private T[] holder;//存儲元素的數組private int count;//元素個數//存儲元素及其位置的對應關系private Map<T, Integer> cache;public ArrLRU() {this.holder = (T[]) new Object[DEFALUT_CAPACITY];this.count = 0;this.cache = new HashMap<>(DEFALUT_CAPACITY);}private void offer(T target) {Integer location = cache.get(target);if (location == null) {//緩存不存在該元素if (count == DEFALUT_CAPACITY) {//緩存已滿,移除緩存元素,加入數組更新緩存T t = holder[--count];cache.remove(t);removeAndCache(t, count);} else {//不存在該元素、緩存未滿putAbsentInCache(target,count);}} else {//已存在update(target,location);}}private void update(T target, Integer location) {Integer index = cache.get(target);rightShift(index);holder[0] = target;cache.put(target,0);}private void putAbsentInCache(T t, int end) {rightShift(count);holder[0] = t;cache.put(t, 0);count++;}private void removeAndCache(T t, int count) {rightShift(count);//t位置之前元素向右移動holder[0] = t;cache.put(t, 0);count++;}private void rightShift(int count) {for (int i = count - 1; i >= 0; i--) {holder[i + 1] = holder[i];cache.put(holder[i], i + 1);}}public static void main(String[] args) {ArrLRU lru = new ArrLRU<Integer>();lru.offer(Integer.valueOf(1));lru.offer(Integer.valueOf(2));lru.offer(Integer.valueOf(3));lru.offer(Integer.valueOf(4));lru.offer(Integer.valueOf(3));System.out.println(Arrays.toString(lru.holder));}} 通過鏈表實現LRU緩存淘汰算法如下 public class NodeLRU<T> {public static class Node<T> {private T data;private Node next;public Node(Node next) {this.next = next;}public Node(T data, Node next) {this.data = data;this.next = next;}public T getData() {return data;}public void setData(T data) {this.data = data;}public Node getNext() {return next;}public void setNext(Node next) {this.next = next;}}//默認鏈表容量private int DEFAULT_CAPACITY = 10;//鏈表長度private int count;//鏈表頭結點private Node head;//鏈表容量private int capacity;public NodeLRU() {this.count = 0;this.head = new Node(null);this.capacity = DEFAULT_CAPACITY;}public NodeLRU(int capacity) {this.capacity = capacity;this.count = 0;this.head = new Node(null);}public int getDEFAULT_CAPACITY() {return DEFAULT_CAPACITY;}public void setDEFAULT_CAPACITY(int DEFAULT_CAPACITY) {this.DEFAULT_CAPACITY = DEFAULT_CAPACITY;}public Node getHead() {return head;}public void add(T data) {//找到元素的前一個結點Node prevNode = findPrevNode(data);if (prevNode != null) {remove(prevNode);insertNodeAtHead(data);} else {if (count >= capacity) {deleteNodeAtEnd();}insertNodeAtHead(data);}}//頭插法private void insertNodeAtHead(T data) {Node node = head.getNext();head.setNext(new Node(data, node));count++;}//刪除prevNode結點的下一個元素private void remove(Node prevNode) {Node node = prevNode.getNext();prevNode.setNext(node.getNext());node = null;count--;}//刪除鏈表中最后一個元素private void deleteNodeAtEnd() {Node node = head;if (node.getNext() == null) {return;}while (node.getNext().getNext() != null) {node = node.getNext();}Node endNode = node.getNext();node.setNext(null);endNode = null;count--;}//查找元素的前一個節點private Node findPrevNode(T data) {Node node = head;while (node.getNext() != null) {if (head.getNext().getData().equals(data)) {return node;}node = node.getNext();}return null;}public boolean isEmpty() {return count == 0;}public int size() {return count;}public static void main(String[] args) {NodeLRU lru = new NodeLRU();for (int i = 1; i < 11; i++) {lru.add(i);}printLinkElement(lru);lru.add(5);printLinkElement(lru);}public static void printLinkElement(NodeLRU lru) {Node head = lru.getHead();StringBuilder stringBuilder = new StringBuilder();while (head.getNext().getNext() != null) {stringBuilder.append(head.getNext().getData()).append("->");head = head.getNext();}stringBuilder.append(head.getNext().getData());System.out.println(stringBuilder.toString());}}

輸出結果

總結

以上是生活随笔為你收集整理的06 | 链表(上):如何实现LRU缓存淘汰算法?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。