數據結構之并查集和LRUCache

系列文章目錄

數據結構之ArrayList_arraylist o(1) o(n)-CSDN博客

數據結構之LinkedList-CSDN博客

數據結構之棧_棧有什么方法-CSDN博客

數據結構之隊列-CSDN博客

數據結構之二叉樹-CSDN博客

數據結構之優先級隊列-CSDN博客

常見的排序方法-CSDN博客

數據結構之Map和Set-CSDN博客

數據結構之二叉平衡樹-CSDN博客

數據結構之位圖和布隆過濾器-CSDN博客


目錄

系列文章目錄

前言

一、并查集

1. 并查集的原理

2. 模擬實現并查集

3. 并查集的應用

1.判斷親戚關系

2. 判斷省份的數量

3.? 等式方程的可滿足性

二、LRUCache

1. LRUCache 的原理

2. 模擬實現 LRUCache

3. LinkedHashMap

4. 基于 LinkedHashMap 實現 LRUCache


前言

本文介紹了兩種重要的數據結構:并查集和LRUCache。并查集用于高效處理集合合并與查詢操作,文章詳細講解了其原理、模擬實現方法,并給出親戚關系判斷、省份數量計算等應用實例。LRUCache是一種緩存淘汰算法,文章剖析了其哈希表+雙向鏈表的實現原理,提供了完整的模擬實現代碼,并介紹了基于LinkedHashMap的簡化實現方案。兩種數據結構在實際開發中都有廣泛應用,本文通過代碼示例和解題思路展示了它們的具體使用方法。


一、并查集

1. 并查集的原理

合并集合:選取兩個集合,兩個集合選取相同的根節點,這兩個集合就被合并了;

查詢兩個元素是否屬于同一個集合:

查詢兩個元素之間的關系時,分別查詢兩個元素的根節點;

如果根節點相同,就表示兩個元素屬于同一個集合;

如果根節點不同,表示兩個元素不屬于同一個集合;

并查集適用于頻繁合并集合,以及頻繁查詢某兩個元素是否屬于同一集合的應用場景;

2. 模擬實現并查集

elem:表示全集

數組下標表示當前節點,數組中存放的值,表示上一級節點;

例如:elem[0] = 10,表示 0 的父節點是 10;

如果 i?是根節點,則 elem[i] 須為負數,用負數表示根,負號后面的值為當前集合中元素的個數;

例如 :0 是根節點,elem[0] = -10,表示 0 為根節點,且這個集合中有 10 個元素;


unionFindSet(int n):并查集的構造方法

n 表示全集中元素的個數;

elem 中所有值都初始化為 -1,表示未合并前都是根節點,且集合中只有當前值這一個元素;


findRoot(int x): int 找 x 所屬集合的根節點

如果 x 不存在,拋異常;

循環查找 x 上級節點 elem[x](x = elem[x]),直到 elem[x] 小于 0,即表示 x 為根節點;


union(int x1, int x2): void 合并 x1 和 x2 所在的集合

判斷 x1 和 x2 是否在同一個集合,即 x1 集合的根節點和 x2 集合的根節點是否為同一個節點;

如果是同一個節點,則表示在同一個集合,直接返回;

如果不是同一個節點(root1 表示 x1 所在集合的根節點,root2 表示 x2 所在集合的根節點):

將 root1 集合?和 root2 集合節點的數量都累加在 root1 中,即 elem[root1] = elem[root1] + elem[root2];

將 root2 的根節點改為 root1,即 elem[root2] = root1;


isSameUnionFindSet(int x1, int x2): boolean 判斷兩個節點是否屬于同一個集合

找 x1 和 x2 的根節點,并判斷是否為同一個節點;

如果是同一個節點,返回 true;

如果不是同一個節點,返回 false;


count(): int 計算一共有幾個集合?

遍歷 elem,返回負數的個數,即為集合的數量;


代碼實現:

public class UnionFindSet {public int[] elem;public UnionFindSet(int n){this.elem = new int[n];Arrays.fill(this.elem, -1);}public int findRoot(int x) {if(x < 0){throw new PosOutOfBoundsException("輸入的下標不合法,是負數!");}while(this.elem[x] >= 0){x = this.elem[x];}return x;}public boolean isSameUnionFindSet(int x1, int x2){int index1 = findRoot(x1);int index2 = findRoot(x2);if(index1 == index2){return true;}else{return false;}}public void union(int x1, int x2){int index1 = findRoot(x1);int index2 = findRoot(x2);if(index1 == index2){return;}this.elem[index1] = this.elem[index1] + this.elem[index2];this.elem[index2] = index1;}public int count(){int count = 0;for(int i = 0; i < this.elem.length; i++){if(this.elem[i] < 0) count++;}return count;}
}

3. 并查集的應用

1.判斷親戚關系

假設親戚的所有親戚也是親戚,有 10 個人,分別用 0 ~ 9 表示,。已知 0 和 6,7,8 是親戚關系,1 和 4,9是親戚關系,2 和 3,5 是親戚關系,判斷 6 和 9 是否是親戚關系,如果 8 和 1 締結了親戚關系,6 和 9 是否也有了親戚關系?

使用并查集判斷如下:

    public static void main(String[] args) {UnionFindSet unionFindSet = new UnionFindSet(10);unionFindSet.union(0, 6);unionFindSet.union(0, 7);unionFindSet.union(0, 8);unionFindSet.union(1, 4);unionFindSet.union(1, 9);unionFindSet.union(2, 3);unionFindSet.union(2, 5);System.out.println(unionFindSet.isSameUnionFindSet(6, 9));unionFindSet.union(8, 1);System.out.println(unionFindSet.isSameUnionFindSet(6, 9));}

運行結果:

?

2. 判斷省份的數量

有?n?個城市,其中一些彼此相連,另一些沒有相連。如果城市?a?與城市?b?直接相連,且城市?b?與城市?c?直接相連,那么城市?a?與城市?c?間接相連。

省份?是一組直接或間接相連的城市,組內不含其他沒有相連的城市。

給你一個?n x n?的矩陣?isConnected?,其中?isConnected[i][j] = 1?表示第?i?個城市和第?j?個城市直接相連,而?isConnected[i][j] = 0?表示二者不直接相連。

返回矩陣中?省份?的數量。

思路:

模擬實現并查集,利用并查集的思想,將相鄰的城市放到同一個集合,最終返回集合的數量即可;

class Solution {public int findCircleNum(int[][] isConnected) {int n = isConnected.length;UnionFindSet ufs = new UnionFindSet(n);for(int i = 0; i < n; i++){for(int j = 0; j < n; j++){if(isConnected[i][j] == 1){ufs.union(i, j);}}}return ufs.count();}
}class UnionFindSet {public int[] elem;public UnionFindSet(int n){this.elem = new int[n];Arrays.fill(this.elem, -1);}public int findRoot(int x) {if(x < 0){return -1;}while(this.elem[x] >= 0){x = this.elem[x];}return x;}public boolean isSameUnionFindSet(int x1, int x2){int index1 = findRoot(x1);int index2 = findRoot(x2);if(index1 == index2){return true;}else{return false;}}public void union(int x1, int x2){int index1 = findRoot(x1);int index2 = findRoot(x2);if(index1 == index2){return;}this.elem[index1] = this.elem[index1] + this.elem[index2];this.elem[index2] = index1;}public int count(){int count = 0;for(int i = 0; i < this.elem.length; i++){if(this.elem[i] < 0) count++;}return count;}
}

3.? 等式方程的可滿足性

給定一個由表示變量之間關系的字符串方程組成的數組,每個字符串方程?equations[i]?的長度為?4,并采用兩種不同的形式之一:"a==b"?或?"a!=b"。在這里,a 和 b 是小寫字母(不一定不同),表示單字母變量名。

只有當可以將整數分配給變量名,以便滿足所有給定的方程時才返回?true,否則返回?false

示例 1:

輸入:["a==b","b!=a"]
輸出:false
解釋:如果我們指定,a = 1 且 b = 1,那么可以滿足第一個方程,但無法滿足第二個方程。沒有辦法分配變量同時滿足這兩個方程。

示例 2:

輸入:["b==a","a==b"]
輸出:true
解釋:我們可以指定 a = 1 且 b = 1 以滿足滿足這兩個方程。

思路:

模擬實現并查集,遍歷 equations 數組,將具有等式關系的都放到同一個集合;

再遍歷?equations 數組,依次檢查具有不等關系的元素,看是否在同一個集合;

如果在同一個集合,則不可能成立,因為具有相等關系才會在一個集合,此時返回 false,

如果所有具有不等關系的元素都不在同一個集合 ,返回 true;

class Solution {public int[] elem = new int[26];public int findRoot(int x){while(elem[x] >= 0){x = elem[x];}return x;} public void merge(int x1, int x2){int index1 = findRoot(x1);int index2 = findRoot(x2);if(index1 == index2) return;elem[index1] = elem[index1] + elem[index2];elem[index2] = index1;}public boolean isSameSet(int x1, int x2){int index1 = findRoot(x1);int index2 = findRoot(x2);if(index1 == index2){return true;}return false;}public boolean equationsPossible(String[] equations) {int n = equations.length;Arrays.fill(elem, -1);for(int i = 0; i < n; i++){String t = equations[i];if(t.charAt(1) == '='){merge(t.charAt(0) - 'a', t.charAt(3) - 'a');}}for(int i = 0; i < n; i++){String t = equations[i];if(t.charAt(1) == '!'){boolean flag = isSameSet(t.charAt(0) - 'a', t.charAt(3) - 'a');if(flag) return false;}}return true;}
}

二、LRUCache

1. LRUCache 的原理

LRU Cache 是 least recently used cache 的縮寫;

LRU Cache 是高速緩存使用的一種數據結構,高速緩存價格昂貴,容量有限;因此當緩存的資源滿了之后,再插入新資源時,就會淘汰最早使用的資源,保留最近使用的資源;

LRUCache 底層是一個哈希表加雙向鏈表的結構:

存入資源時會先存到鏈表的尾巴節點,如果超出最大緩存容量,會刪除頭結點的資源;

查詢資源時,不僅會將查詢的資源返回,還會將這個資源重新放到鏈表的尾巴節點;

哈希表的作用就是查詢某個資源會在 O(1) 的時間復雜度內查詢到,鏈表的作用是保證資源的順序(最近使用的順序),且插入刪除時間復雜度也是 O(1);

2. 模擬實現 LRUCache

DLinkedNode:資源節點類,包含 key,val,prev,next 屬性,還有構造方法,作用參考哈希表和雙向鏈表;

head:頭結點,不存實際的資源;

tail:尾巴節點,不存實際的資源;

head 和 tail 的作用:方便雙向鏈表進行插入和刪除操作,不會出現空節點異常;

usedSize:當前保存的數據的數量;

cache:哈希表,用于保存 key 和 DLinkedNode 的映射關系,用于解決雙向鏈表查詢時間復雜度 O(N) 的問題;

capacity:緩存的最大容量;

public class MyLRUCache {static class DLinkedNode{public int key;public int val;public DLinkedNode prev;public DLinkedNode next;public DLinkedNode(){}public DLinkedNode(int key, int val){this.key = key;this.val = val;}}public DLinkedNode head;public DLinkedNode tail;public int usedSize;public Map<Integer, DLinkedNode> cache;public int capacity;// 方法// ......
}

MyLRUCache(int capacity) 初始化頭節點,尾巴節點,保存數據的數量,最大容量;

注意:一定要將頭節點和尾巴節點連起來,防止首次插入節點時出現空指針異常;


put(int key, int val): void 插入節點;

思路:

插入節點時,要檢查節點是否已經存在;

節點不存在,現在哈希表中增加節點,在雙向鏈表的尾巴節點插入,注意 usedSize++;

插入后,注意判斷當前節點的數量是否查過最大能緩存的數量;

如果超過了,要在哈希表中刪除節點,在雙向鏈表中刪除頭節點連接的第一個資源,注意 usedSize--;

如果節點已經存在了,更新節點的 val,并在雙向鏈表中,將該節點放到尾巴節點的位置;


get(int key): int 返回節點對應的 val;

節點不存在,返回 -1;

節點存在,返回節點的 val,返回之前注意將節點放到雙向鏈表尾巴節點的位置;


addToTail(DLinkedNode node):在雙向鏈表的尾巴節點的位置插入節點 node;

removeHead(): DLinkedNode 刪除頭節點相連的節點,并返回該節點;

remove(DLinkedNode node): void 在雙向鏈表中刪除節點 node;

moveToTail(DLinkedNode node): void 在雙向鏈表中刪除 node,并將 node 放在雙向鏈表尾巴節點的位置;

    public MyLRUCache(int capacity){this.head = new DLinkedNode();this.tail = new DLinkedNode();this.head.next = this.tail;this.tail.prev = this.head;cache = new HashMap<>();this.capacity = capacity;this.usedSize = 0;}public void put(int key, int val){// 1. 插入的時候要判斷節點是否已經存在DLinkedNode node = cache.getOrDefault(key, null);if(node == null){// 3. 如果不存在,就插入哈希表DLinkedNode cur = new DLinkedNode(key, val);cache.put(key, cur);// 4. 將它放在隊尾addToTail(cur);this.usedSize++;// 5. 判斷容量是否超了if(usedSize > capacity){// 6. 刪除頭結點DLinkedNode t = removeHead();cache.remove(t.key);}}else{// 2. 如果存在,就要更新對應 key 的值node.val = val;moveToTail(node);}}public int get(int key){DLinkedNode node = cache.get(key);if(node == null){return -1;}moveToTail(node);return node.val;}private void addToTail(DLinkedNode node){DLinkedNode prev = this.tail.prev;prev.next = node;node.prev = prev;node.next = this.tail;this.tail.prev = node;}private DLinkedNode removeHead(){DLinkedNode dLinkedNode = this.head.next;DLinkedNode next = dLinkedNode.next;this.head.next = next;next.prev = this.head;return dLinkedNode;}private void moveToTail(DLinkedNode node){remove(node);addToTail(node);}private void remove(DLinkedNode node){DLinkedNode prev = node.prev;DLinkedNode next = node.next;prev.next = next;next.prev = prev;}

3. LinkedHashMap

JDK?中可以使用 LinkedHashMap 實現 LRUCache;

構造方法:

initialCapacity: 初始容量;

loadFactor:哈希表的負載因子;

accessOrder:

true:每次查詢或者新增后,都會將該節點放在雙向鏈表的尾巴節點的位置;

false:會按照默認順序存放,默認為 false;

    public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {super(initialCapacity, loadFactor);this.accessOrder = accessOrder;}

afterNodeInsertion(boolean evict): void 插入節點后是否要刪除插入最早的節點;

    void afterNodeInsertion(boolean evict) { // possibly remove eldestLinkedHashMap.Entry<K,V> first;if (evict && (first = head) != null && removeEldestEntry(first)) {K key = first.key;removeNode(hash(key), key, null, false, true);}}

removeEldestEntry(Map.Entry<K, V> eldest): boolean 是否刪除最老的節點,默認為 false;

    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {return false;}

使用 LinkedHashMap 實現 LRUCache 一定要重寫這個方法;

4. 基于 LinkedHashMap 實現 LRUCache

基于 LinkedHashMap 實現需要指定容量,重寫 put() 和 get() 方法;

重寫 removeEldestEntry():當數據量超出指定容量后,會刪除最老的數據;?

public class LRUCacheOnLinkedHashMap extends LinkedHashMap<Integer, Integer> {public int capacity;public LRUCacheOnLinkedHashMap(int capacity){this.capacity = capacity;}@Overridepublic Integer put(Integer key, Integer value) {return super.put(key, value);}@Overridepublic Integer get(Object key) {return super.get(key);}@Overrideprotected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {return size() > capacity;}
}

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

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

相關文章

UE5多人MOBA+GAS 21、給升龍添加連段攻擊,從角色的按下事件中傳遞事件給GA

文章目錄給升龍制作可連段緩存下一連段用普攻鍵來觸發升龍后續的連段在角色中發送按下普攻標簽事件在升龍中接收按下事件&#xff0c;觸發連段以及傷害和力量的傳遞最后在藍圖中設置一下升龍技能的完整代碼給升龍制作可連段 給升龍技能添加一些連段 緩存下一連段 緩存下一連…

基于光柵傳感器+FPGA+ARM的測量控制解決方案

基于光柵傳感器結合FPGA與ARM的測量控制解決方案&#xff0c;通過硬件協同分工實現高精度、實時性及多場景適應性&#xff1a;?? ?一、系統架構分工??傳感層&#xff08;光柵傳感器&#xff09;?采用光柵尺輸出正交脈沖信號&#xff0c;分辨率達0.5μm&#xff0c;精度1μ…

NW831NW910美光固態閃存NW887NW888

美光固態閃存深度解析&#xff1a;NW831、NW910、NW887、NW888系列全方位評測一、技術根基與架構創新美光NW系列固態閃存的技術突破源于其先進的G9 NAND架構&#xff0c;該架構采用5納米制程工藝和多層3D堆疊技術&#xff0c;在單位面積內實現了高達256層的存儲單元堆疊&#x…

reasense api 文檔

API 架構 英特爾實感&#xff08;Intel RealSense?&#xff09;API 提供對深度攝像頭流數據的配置、控制和訪問功能。該 API 支持通過高層級 API 快速啟用攝像頭基礎功能&#xff0c;或通過底層級 API 全面控制所有攝像頭設置。請根據需求選擇合適的 API&#xff1a; 高層級 P…

ArkTs實現骰子布局

Entry Component struct workA {// 定義6種顏色數組&#xff0c;使用ResourceColor類型確保顏色值合法性State color: ResourceColor[] [#ef2816, #f0a200, #6ab002, #005868, #41192e, #141411]// 定義公共樣式裝飾器&#xff0c;避免重復樣式代碼Stylesys() {// 白色圓形基礎…

c語言內存函數以及數據在內存中的存儲

代碼見&#xff1a;登錄 - Gitee.com 1. memcpy使用和模擬實現 strcpy&#xff0c;strncpy是拷貝字符串的&#xff0c;有局限性 函數原型&#xff1a; void * memcpy ( void * destination, const void * source, size_t num ); 功能&#xff1a; memcpy 是完成內存塊拷?的…

Codeforces Round 787 (Div. 3)(A,B,C,D,E,F,G)

Codeforces Round 787 (Div. 3) - Codeforces A. Food for Animals 題意 有a袋狗糧,b袋貓糧,c袋通用糧食&#xff0c;問現在有x只狗y只貓,每一個動物都要吃一袋糧食,問糧食夠不夠吃 思路 首先肯定考慮貓吃貓糧&#xff0c;狗吃狗糧。然后再考慮如果不夠吃的話才會去吃通用…

LLaMA-Factory的webui快速入門

一、webui的啟動方式 LLaMA-Factory 支持通過 WebUI 零代碼微調大語言模型。 在完成安裝 后&#xff0c;您可以通過以下指令進入 WebUI: llamafactory-cli webui 使用上面命令啟動服務后&#xff0c;即可使用默認7860端口進行訪問。訪問地址&#xff1a;http://ip:7860,截止…

【第四節】ubuntu server安裝docker

首先更新軟件源 sudo apt update sudo apt upgrade安裝docker 下載 Docker 官方 GPG 密鑰 # 1. 下載 Docker 官方 GPG 密鑰 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg再次更新軟件源…

Kubernetes的微服務

用控制器來完成集群的工作負載&#xff0c;那么應用如何暴漏出去&#xff1f;需要通過微服務暴漏出去后才能被訪問Service是一組提供相同服務的Pod對外開放的接口。借助Service&#xff0c;應用可以實現服務發現和負載均衡。service默認只支持4層負載均衡能力&#xff0c;沒有7…

退出登錄后頭像還在?這個緩存問題坑過多少前端!

目錄 1. 為什么退出登錄后頭像還在&#xff1f; ① 緩存沒清理干凈 ② 頭像URL沒更新 ③ 后端會話失效&#xff0c;但靜態資源可訪問 2. 怎么解決&#xff1f;5種常見方案 ? 方案1&#xff1a;強制刷新頁面&#xff08;簡單粗暴&#xff09; ? 方案2&#xff1a;給頭像…

Windows下白嫖ClaudeCode

我的邀請鏈接&#xff1a;https://anyrouter.top/register?afffMJn 我的邀請鏈接&#xff1a;https://anyrouter.top/register?afffMJn 我的邀請鏈接&#xff1a;https://anyrouter.top/register?afffMJn 兄弟們&#xff0c;交個朋友啊&#xff01;一定要用我的呀&#xff0…

windows在anaconda中下載安裝fasttext

windows在anaconda中下載安裝fasttext 1.訪問fasttext-wheel&#xff0c;點擊對應鏈接&#xff0c;下載對應Python版本、操作系統類型 的.whl文件&#xff1a; 鏈接地址&#xff1a;https://pypi.org/project/fasttext-wheel/#files 打開anaconda終端&#xff0c;切換到上面的…

mysql5.7系列-索引下推(cover_index)

什么是索引下推 ICP&#xff08;Index Condition Pushdown&#xff09;是在MySQL 5.6版本上推出的查詢優化策略&#xff0c;把本來由Server層做的索引條件檢查下推給存儲引擎層來做&#xff0c;以降低回表和訪問存儲引擎的次數&#xff0c;提高查詢效率。 回顧下mysql的架構分…

計算機網絡(基礎概念)

計算機網絡&#xff08;基礎概念&#xff09;1 初識協議1.1 協議分層2 OSI七層模型2.1 物理層2.2 數據鏈路層2.3 網絡層2.4 傳輸層2.5 應用層3 TCP/IP協議族3.1 什么是TCP/IP協議?3.1.1 OS與網絡關系4 網絡傳輸的基本流程4.1 局域網4.2 MAC地址5 跨網絡傳輸5.1 IP地址6 Socket…

專題 JavaScript 函數基礎

你將知道&#xff1a;函數聲明和表達式函數聲明和表達式之間的區別什么是匿名函數什么是 IIFE命名函數表達式this 關鍵字函數是調用該函數時執行的代碼塊 。函數聲明和表達式讓我們回顧一下它的語法&#xff1a;functionfunctionName(param1, param2, ..., paramN) {// Functio…

數據結構——優先隊列(priority_queue)的巧妙運用

優先隊列是一種相對高級的數據結構&#xff0c;它的底層原理是二叉堆。然而本篇不會執著于深挖其背后的原理&#xff0c;更主要的是理一下它在題目中的一些實用方法&#xff0c;幫助你更快的上手使用。 優先隊列(priority_queue) 優先隊列的特別之處就在于它可以自動進行排序&…

Java:繼承和多態(必會知識點整理)

主要內容繼承多態向上轉型向下轉型方法重寫方法重載super關鍵字動態綁定封裝訪問控制構造方法規則一、繼承 1. 概念&#xff1a; 一句話說就是&#xff1a;“共性抽取&#xff0c;代碼復用”子類會將父類中的成員變量或者成員方法繼承到子類中子類繼承父類之后&#xff0c;必須…

基于esp32系列的開源無線dap-link項目使用介紹

基于esp32系列的開源無線dap-link項目使用介紹&#x1f516;有關esp32/8266相關項目&#xff1a;需要自己搭建編譯環境&#xff1a; https://github.com/windowsair/wireless-esp8266-dap/tree/master&#x1f33f;支持esp32/c3/s3,支持在線固件燒錄&#xff0c;支持AP配網&…

深入了解linux系統—— 進程信號的產生

前言 進程在收到信號之后&#xff0c;可以立即處理&#xff0c;也可以在合適的時間再處理&#xff08;1-31號普通信號可以不被立即處理&#xff09; 信號不是被立即處理&#xff0c;信號就要被保存下來&#xff0c;讓進程在合適的時間再去處理。 相關概念 在了解進程是如何保存…