算法通關村第五關—LRU的設計與實現(黃金)

?????????LRU的設計與實現

一、理解LRU的原理

?LeetCode146:運用你所掌握的數據結構,設計和實現一個LRU(最近最少使用)緩存機制

實現LRUCache類:
LRUCache(int capacity) 以正整數作為容量capacity初始化 LRU 緩存
int get(int key) 如果關鍵字key存在于緩存中,則返回關鍵字的值,否則返回-1
void put(int key,int value) 如果關鍵字已經存在,則變更其數據值;如果關鍵字不存在,則插入該組[關鍵字-]。當緩存容量達到上限時,它應該在寫入新數據之前刪除最久未使用的數據值,從而為新的數據值留出空間
進階:你是否可以在O(1)時間復雜度內完成這兩種操作?
輸入
["LRUCache","put","put","get","put","get","put","get","get","get"]
[[2],[1,1],[2,2],[1],[3,3],[2],[4,4],[1],[3],[4]]
輸出
[nu1l,nu1l,nu11,1,nu1l,-1,nu1,-1,3,4]
解釋
LRUCache lRUCache new LRUCache(2);
lRUCache.put(1,1);//緩存是{1=1}
lRUCache.put(2,2);//緩存是{1=1,2=2}
lRUCache.get(1);//返回1
lRUCache.put(3,3);//該操作會使得關鍵字2作廢,緩存是{1=1,3=3}
lRUCache.get(2);//返回-1(未找到)
lRUCache.put(4,4);//該操作會使得關鍵字1作廢,緩存是{4=4,3=3}
lRUCache.get(1);//返回-1(未找到)
lRUCache.get(3);//返回3
lRUCache.get(4);//返回4

?關于什么是LRU,簡單來說就是當內存空間滿了,不得不淘汰某些數據時(通常是容量已滿),選擇最久未被使用的數據進行淘汰。
?這里做了簡化,題目讓我們實現一個容量固定的LRUCache。如果插入數據時,發現容器已滿時,則先按照LRU規則淘汰一個數據,再將新數據插入,其中「插入」和「查詢」都算作一次“使用”。
最近最少使用算法(LRU)是大部分操作系統為最大化頁面命中率而廣泛采用的一種頁面置換算法。該算法的思路是,發生缺頁中斷時,選擇未使用時間最長的頁面置換出去。假設內存只能容納3個頁大小,按照7 0 1 2 0 3 0 4的次序訪問頁。假設內存按照棧的方式來描述訪問時間,在上面的,是最近訪問的,在下面的是,最遠時間訪問的,LRU就是這樣工作的:
image.png

二、hash+雙向鏈表實現LRU

?目前公認最合理的方式是使用ash+雙向鏈表。想不到吧,接下來我們就看看該怎么做。
Hash的作用是用來做到O(1)訪問元素,哈希表就是普通的哈希映射(HashMap),通過緩存數據的鍵映射到其在雙向鏈表中的位置。Hash里的數據是key-value結構。value就是我們自己封裝的node,key則是鍵值,也就是在Hash的地址。
?雙向鏈表用來實現根據訪問情況對元素進行排序。雙向鏈表按照被使用的順序存儲了這些鍵值對,靠近頭部的鍵值對是最近使用的,而靠近尾部的鍵值對是最久未使用的。
這樣以來,我們要確認元素的位置直接訪問哈希表就行了,找出緩存項在雙向鏈表中的位置,隨后將其移動到雙向鏈表的頭部,即可在O(1)的時間內完成get或者put操作。具體的方法如下:
1.對于get操作,首先判斷key是否存在:
(1)如果key不存在,則返回-1;
(2)如果key存在,則key對應的節點是最近被使用的節點。通過哈希表定位到該節點在雙向鏈表中的位置,并將其移動到雙向鏈表的頭部,最后返回該節點的值。
2.對于put操作,首先判斷key是否存在:
(1)如果key不存在,使用key和value創建一個新的節點,在雙向鏈表的頭部添加該節點,并將key和該節點添加進哈希表中。然后判斷雙向鏈表的節點數是否超出容量,如果超出容量,則刪除雙向鏈表的尾部節點,并刪除哈希表中對應的項;
(2)如果key存在,則與get操作類似,先通過哈希表定位,再將對應的節點的值更新為value,并將該節點移到雙向鏈表的頭部。
?上述各項操作中,訪問哈希表的時間復雜度為O(1),在雙向鏈表的頭部添加節點、在雙向鏈表的尾部刪除節點的復雜度也為O(1)。而將一個節點移到雙向鏈表的頭部,可以分成[刪除該節點]和[在雙向鏈表的頭部添加節點]兩步操作,都可以在O(1)時間內完成。
?同時為了方便操作,在雙向鏈表的實現中,使用一個偽頭部(dummy head)和偽尾部(dummy tail)標記界限,這樣在添加節點和刪除節點的時候就不需要檢查相鄰的節點是否存在。
?來看一個容量為3的例子,首先緩存了1,此時結構如圖所示。之后再緩存2和3,結構如b所示。
image.png
?之后4再進入,此時容量已經不夠了,只能將最遠未使用的元素1刪掉,然后將4插入到鏈表頭部。此時就變成了上圖c的樣子。
?接下來假如又訪問了一次2,會怎么樣呢?此時會將2移動到鏈表的首部,也就是下圖d的樣子。
image.png
?之后假如又要緩存5呢?此時就將tail指向的3刪除,然后將5插入到鏈表頭部。也就是上圖e的樣子。上面的方案要實現是非常容易的,我們注意到鏈表主要執行幾個操作:
1假如容量沒滿,則將新元素直接插入到鏈表頭就行了。
2.如果容量夠了,新的元素到來,則將tail指向的表尾元素刪除就行了。
3.假如要訪問已經存在的元素,則此時將該先從鏈表中刪除,再插入到表頭就行了。
再看Hash的操作:
1.Hash沒有容量的限制,凡是被訪問的元素都會在Hash中有個標記,key就是我們的查詢條件,而value就是鏈表的結點的引用,可以不用訪問鏈表直接定位到某個結點,然后就可以執行我們在上一節提到的方法來刪除對應的結點。
2.這里雙向鏈表的刪除好理解,那HashMap中是如何刪除的呢?其實就是將node變成為null。這樣get(key)的時候返回的是null,就實現了刪除的功能。
?上述各項操作中,訪問哈希表的時間復雜度為O(),在雙向鏈表的頭部添加節點、在雙向鏈表的尾部刪除節點的復雜度也為O()。而將一個節點移到雙向鏈表的頭部,可以分成「刪除該節點」和「在雙向鏈表的頭部添加節點」兩步操作,都可以在O(1)時間內完成。

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;// 使用偽頭部和偽尾部節點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 不存在,創建一個新的節點DLinkedNode newNode = new DLinkedNode(key, value);// 添加進哈希表cache.put(key, newNode);// 添加至雙向鏈表的頭部addToHead(newNode);++size;if (size > capacity) {// 如果超出容量,刪除雙向鏈表的尾部節點DLinkedNode tail = removeTail();// 刪除哈希表中對應的項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;}
}

測試類

public static void main(String[] args){
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1,1);//緩存是{1=1}
lRUCache.put(2,2);//緩存是{1=1,2=2}
System.out.println(lRUCache.get(1));//返回1
lRUCache.put(3,3);//該操作會使得關鍵字2作廢,緩存是{1=1,3=3}
System.out.println(lRUCache.get(2));//返回-1(未找到)
lRUCache.put(4,4);//該操作會使得關鍵字1作廢,緩存是{4=4,3=3}
System.out.println(lRUCache.get(1));//返回-1(未找到)
System.out.println(lRUCache.get(3));//返回3
System.out.println(lRUCache.get(4));//返回4
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/211272.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/211272.shtml
英文地址,請注明出處:http://en.pswp.cn/news/211272.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數據可視化|jupyter notebook運行pyecharts,無法正常顯示“可視化圖形”,怎么解決?

前言 本文是該專欄的第39篇,后面會持續分享python數據分析的干貨知識,記得關注。 相信有些同學在本地使用jupyter notebook運行pyecharts的時候,在代碼沒有任何異常的情況下,無論是html還是notebook區域,都無法顯示“可視化圖形”,界面區域只有空白一片。遇到這種情況,…

SQL(COALESCE)

zstarling 非空值查找及替換COALESCE 非空值查找及替換COALESCE 新語法SQL COALESCE(staff_no,staff_no1,)詳解 在SQL中&#xff0c;COALESCE函數用于返回一組表達式中的第一個非NULL值。它接受兩個或多個參數&#xff0c;并按參數順序依次判斷每個參數是否為NULL&#xff0c…

如何才能保證績效考核的有效性呢?

績效管理是現代人力資源管理的核心&#xff0c;做好績效考核是做好績效管理的重要手段。但企業績效考核的設計往往缺乏針對性和科學性&#xff0c;績效考核工作也常常停留在形式上&#xff0c;最終沒能為提高組織效率提供幫助&#xff0c;還消耗員工與管理者的時間、精力。于是…

Nginx服務優化以及防盜鏈

1. 隱藏版本號 以在 CentOS 中使用命令 curl -I http://192.168.66.10 顯示響應報文首部信息。 查看版本號 curl -I http://192.168.66.10 1. 修改配置文件 vim /usr/local/nginx/conf/nginx.conf http {include mime.types;default_type application/octet-stream;…

京東數據運營(京東API接口):10月投影儀店鋪數據分析

鯨參謀監測的京東平臺10月份投影儀市場銷售數據已出爐&#xff01; 10月份&#xff0c;環同比來看&#xff0c;投影儀市場銷售均上漲。鯨參謀數據顯示&#xff0c;今年10月&#xff0c;京東平臺投影儀的銷量為16萬&#xff0c;環比增長約22%&#xff0c;同比增長約8%&#xff1…

鴻蒙應用開發ArkTS基礎組件的使用

語雀知識庫地址&#xff1a;語雀HarmonyOS知識庫 飛書知識庫地址&#xff1a;飛書HarmonyOS知識庫 本文示例代碼地址&#xff1a;Gitee 倉庫地址 嗨&#xff0c;各位好呀&#xff0c;我是小白 上一篇文章我為大家介紹了如何使用 ArkTS 開發鴻蒙應用&#xff0c;對 HarmonyOS 項…

大文件分割,合并------C++ ------fstream

將一個大文件(這里測試文件為5.2G)切分為指定大小的文件,然后在把分割后的文件拼接合并為分割前的源文件 #include <boost/timer.hpp> // 計時函數#include <filesystem> #include <fstream> #include <vector> // 分隔后文件夾的格式, 原文件名_chun…

探索開源游戲的樂趣與無限可能 | 開源專題 No.47

CleverRaven/Cataclysm-DDA Stars: 9.0k License: NOASSERTION Cataclysm&#xff1a;Dark Days Ahead 是一個回合制的生存游戲&#xff0c;設定在一個后啟示錄世界中。盡管有些人將其描述為 “僵尸游戲”&#xff0c;但 Cataclysm 遠不止于此。在這個殘酷、持久、程序生成的世…

【原創】【一類問題的通法】【真題+李6卷6+李4卷4(+李6卷5)分析】合同矩陣A B有PTAP=B,求可逆陣P的策略

【鋪墊】二次型做的變換與相應二次型矩陣的對應&#xff1a;二次型f&#xff08;x1&#xff0c;x2&#xff0c;x3&#xff09;xTAx&#xff0c;g&#xff08;y1&#xff0c;y2&#xff0c;y3&#xff09;yTBy ①若f在可逆變換xPy下化為g&#xff0c;即P為可逆陣&#xff0c;有P…

Unity 通過鍵盤鼠標控制物體移動、旋轉、縮放的方法

在Unity中&#xff0c;使用鍵盤ADWS鍵控制物體移動&#xff0c;通過鼠標左鍵控制物體旋轉&#xff0c;鼠標中鍵控制物體縮放是再常見不過的方法。 方法如下&#xff1a; using System.Collections; using System.Collections.Generic; using UnityEngine;public class MoveCo…

數字系統設計(EDA)實驗報告【出租車計價器】

一、問題描述 題目九&#xff1a;出租車計價器設計&#xff08;平臺實現&#xff09;★★ 完成簡易出租車計價器設計&#xff0c;選做停車等待計價功能。 1、基本功能&#xff1a; &#xff08;1&#xff09;起步8元/3km&#xff0c;此后2元/km&#xff1b; &#xff08;2…

紅隊攻防實戰之ThinkPHP-RCE集錦

你若不勇敢&#xff0c;誰又可以替你堅強&#xff1f; ThinkPHP 2.x RCE漏洞 1、查詢phpinfo() 2、任意代碼執行 3、Getshell 蟻劍連接&#xff1a; ThinkPHP5 5.0.23 RCE漏洞 發送數據包&#xff1a; 成功執行id命令&#xff1a; 工具驗證 ThinkPHP5 SQL注入漏洞 &&am…

什么是神經網絡的非線性

大家好啊&#xff0c;我是董董燦。 最近在寫《計算機視覺入門與調優》&#xff08;右鍵&#xff0c;在新窗口中打開鏈接&#xff09;的小冊&#xff0c;其中一部分說到激活函數的時候&#xff0c;談到了神經網絡的非線性問題。 今天就一起來看看&#xff0c;為什么神經網絡需…

cuda函數的前綴作用

文章目錄 cuda函數的前綴作用1、前綴作用2、global3、device4、host cuda函數的前綴作用 1、前綴作用 函數執行環境標識符&#xff0c;即表明函數在哪里被調用 2、global __global__修飾的函數是核函數&#xff0c;這些函數在GPU上執行&#xff0c;但是需要在CPU上調用。 g…

激光打標機在智能手表上的應用:科技與時尚的完美結合

隨著科技的飛速發展&#xff0c;智能手表已經成為我們日常生活中不可或缺的智能設備。而在智能手表制造中&#xff0c;激光打標機扮演著至關重要的角色。本文將詳細介紹激光打標機在智能手表制造中的應用&#xff0c;以及其帶來的優勢和影響。 ? 一、激光打標機在智能手表制…

按訂單周期結算的產品成本

原文地址&#xff1a;Product Cost By Order Cycle | SAP Blogs 產品成本核算是每個制造企業的控制部門的核心職責之一&#xff0c;根據其產品和生產的性質&#xff0c;每個企業的成本核算有所不同。它支持組織在其他職能領域做出大量戰略決策。在過去幾年中獲得了 SAP 產品成本…

Vite4、Vue3、Axios 針對請求模塊化封裝搭配自動化導入(簡單易用)

針對請求模塊化封裝搭配自動化導入&#xff08;簡單易用&#xff09; 目標目錄目標代碼前提步入正題src / utils / index.jssrc /api / index.jssrc /api / request.jssrc /api / service.jssrc /api / utils.jssrc /api / modules / demo.js 自動化配置vite.config.jseslint 校…

QEMU環境調試方法

本文從調試的角度出發&#xff0c;分享QEMU調試過程中的常見調試方法。 1.如何查看makefile構建過程執行的命令&#xff1f; 為了深入理解ucore操作系統實驗的編譯鏈接細節&#xff0c;需要知道makefile在執行的過程中一步一步的指令執行情況。然而大部分的工程中&#xff0c;…

CopyClip 2:提升Mac開發效率的利器

在Mac開發的日常工作中&#xff0c;高效地處理剪貼板內容是一個至關重要的任務。幸運的是&#xff0c;有一款強大的工具可以極大地提升你的開發效率——CopyClip 2。本文將介紹CopyClip 2的功能和優勢&#xff0c;以及它是如何成為Mac開發者們不可或缺的利器的。 CopyClip 2 簡…

【Docker】進階之路:(二)Docker簡介

【Docker】進階之路&#xff1a;&#xff08;二&#xff09;Docker簡介 什么是 DockerDocker 由來與發展歷程Docker的架構與組成Docker容器生態容器核心技術容器規范容器平臺技術 為什么使用DockerDocker的應用場景 什么是 Docker 簡單地講&#xff0c;Docker就是一個應用容器…