当前位置: 首页 > news >正文

深入理解 并查集LRUCaChe

并查集&LRUCaChe

在这里插入图片描述

个人主页:顾漂亮
文章专栏:Java数据结构

1.并查集的原理

在一些应用问题中,需要将n个不同的元素划分成一些不相交的集合。开始时,每个元素自成一个单元素集合,然后根据一定规律将归于同一组元素的集合合并。在此过程中要反复运用到查询某一个元素归属于哪一个集合的运算。适合于描述这类问题的抽象数据类型称为并查集,(union - find set)并查集的底层运用的是数组

举例

​ 1.初始情况:假设现在有10个元素(0 - 9),开始情况是每一个元素相互独立,每个元素自成一个单元素集合。

在这里插入图片描述

提问:为什么初始情况下数组元素全为-1?

在回答上述问题之前,我们应该先了解以下并查集的使用规则

  • 数组的下标对应集合中的元素,例如数组9下标对应的就是9这个元素
  • 数组中的值如果为负数,负号表示根,数字代表该集合中元素的个数
  • 数组中的值如果是非负数,代表该元素双亲结点在数组中的下标

根据上述规则,我们可以知道初始情况下10个元素,每个元素自成一个单元素集合,因此-1代表,每个单元素集合中只有一个元素,并且这个元素的根也是其本身。

​ 2.**初次合并:**将单元素集合按照下图所示规律进行合并成三个集合

在这里插入图片描述

可以得到并查集图形为:

在这里插入图片描述

3.第二次合并:将单元素集合按照下图所示规律进行合并成三个集合

在这里插入图片描述

可以得到并查集图形为:

在这里插入图片描述

2.并查集可以解决的问题

  1. 查找元素属于哪一个集合
    • 根据数组表示的树形关系向上寻找,一直找到根
  2. 查看两个元素是否属于同一个集合
    • 比较两个集合的根是否相同,相同属于同一个集合,反之则不属于一个集合
  3. 将两个集合合并成一个集合
    • 先将两个集合的根合并
  4. 集合的个数
    • 遍历数组,数组中元素为非负数的个数即为集合的个数

3.并查集的代码实现

import java.util.Arrays;public class UnionFindSet {public int[] elem;//底层为数组public UnionFindSet(int n){//初始化数组大小为nelem = new int[n];//将数组中元素初始化为-1Arrays.fill(elem,-1);}//查找根public int findRoot(int val){if(val < 0){throw new IndexOutOfBoundsException("val不合法");}while(elem[val] >= 0){val = elem[val];}return val;}//合并操作public void union(int x1, int x2){//确定两个元素根节点int index1 = findRoot(x1);int index2 = findRoot(x2);//如果两个元素属于同一个集合if(index2 == index1){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(index2 == index1){return true;}return false;}//求数组中集合的个数public int getCount(){int count = 0;for (int i = 0; i < elem.length; i++) {if(elem[i] < 0){count++;}}return count;}//打印数组public void printSet(){for (int i = 0; i < elem.length; i++) {System.out.print(elem[i] + " ");}System.out.println();}
}

4.并查集相关面试题

省份问题:

  • 求解思路
    • 实现一个并查集
    • 如果两个城市联通,放在一个集合中
    • 返回并查集中元素小于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 < isConnected[i].length; j++){if(isConnected[i][j] == 1){//合并ufs.union(i,j);}   }}return ufs.getCount();}}class UnionFindSet {public int[] elem;//底层为数组public UnionFindSet(int n){elem = new int[n];//初始化为n大小的数组Arrays.fill(elem, -1);//将数组初始化为-1,注意,为什么初始化为-1?}//查找根public int findRoot(int val){if(val < 0){throw new IndexOutOfBoundsException("数据不合法");}//注意这个循环算法易错while(elem[val] >= 0){val = elem[val];}return val;}//合并操作public void union(int x1, int x2){int index1 = findRoot(x1);int index2 = findRoot(x2);//如果根相同,直接返回if(index2 == index1){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(index2 == index1){return true;}return false;}//求数组中集合的个数public int getCount(){int count = 0;for (int i = 0; i < elem.length; i++) {if(elem[i] < 0){count++;}}return count;}//打印数组public void printSet(){for (int i = 0; i < elem.length; i++) {System.out.print(elem[i] + " ");}System.out.println();}
}

等式方程可满足性:

  • 解题思路
    • 将"=="两边数据放入一个集合中
    • 检查"!="两边数据是否在同一个集合中,如果在返回false,如果不再返回true
class Solution {public boolean equationsPossible(String[] equations) {UnionFindSet set = new UnionFindSet(26);//一共有26个英文字母//1. 将“=”号左右元素合并成同一个集合for(int i = 0; i < equations.length; i++){if(equations[i].charAt(1) == '='){set.union(equations[i].charAt(0) - 'a', equations[i].charAt(3) - 'a');}}//2. 检查“!”左右两边是否在同一个集合中for(int i = 0; i < equations.length; i++){if(equations[i].charAt(1) == '!'){if(set.isSameSet(equations[i].charAt(0)  - 'a', equations[i].charAt(3)  - 'a')){return false;}}}return true;}
}class UnionFindSet {public int[] elem;//底层为数组public UnionFindSet(int n){elem = new int[n];//初始化为n大小的数组Arrays.fill(elem, -1);//将数组初始化为-1,注意,为什么初始化为-1?}//查找根public int findRoot(int val){if(val < 0){throw new IndexOutOfBoundsException("数据不合法");}//注意这个循环算法易错while(elem[val] >= 0){val = elem[val];}return val;}//合并操作public void union(int x1, int x2){int index1 = findRoot(x1);int index2 = findRoot(x2);//如果根相同,直接返回if(index2 == index1){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(index2 == index1){return true;}return false;}//求数组中集合的个数public int getCount(){int count = 0;for (int i = 0; i < elem.length; i++) {if(elem[i] < 0){count++;}}return count;}//打印数组public void printSet(){for (int i = 0; i < elem.length; i++) {System.out.print(elem[i] + " ");}System.out.println();}
}

LRUCaChe

1.概念解析:

1.1什么是LRU?

LRU(Last recently used)的缩写,意思是最近最少使用,是一种CaChe替换算法。

1.2什么是Cache?

狭义上:Cache是指位于CPU和主存间的快速RAM,通常它不像系统主存那样使用DRAM技术,而是用昂贵但是较为快速的SRAM技术

广义上:位于速度相差较大的两种硬件之间,用于协调两者数据传输速度差异的结构。处理CPU与主内存之间有Cache,内存与磁盘之间也有, 乃至在硬件与网络之间也有某种意义上的Cache–称为Internet临时文件夹或网络内容缓存等

Cache的内存容量有限,因此当Cache的容量用完后,而又有新的内容需要添加进来时候,就需要挑选并舍弃相应的元素,从而腾出空间来存放新的内容。LRUCaChe的替换原则就是将最近最少使用的内容替换掉

2.LRUCache的实现

其实现方式可以有很多,但是为了追求最高的效率,我们采用哈希表和双向链表来实现LRUCaChe,哈希表的增删查改是O(1),双向链表可以实现任意位置插入删除为O(1)

2.1JDK中的LinkedHashMap

在这里插入图片描述

参数说明

  • initialCapacity:容量大小

  • loadFacto:加载因子,使用无参构造方法时,此值默认为0.75f

  • accessOrder:默认为false->基于插入顺序存放; true ->基于访问顺序

使用案例

import java.util.LinkedHashMap;
import java.util.Map;public class LRUCache extends LinkedHashMap<Integer, Integer> {public int capacity;//容量public LRUCache(int capacity){super(capacity, 0.75f, true);//调用父类构造函数,必须放在构造函数第一行this.capacity = capacity;}public int get(int key){return super.getOrDefault(key, -1);}public void put(int key, int value){super.put(key, value);}//必须要重写上述方法@Overrideprotected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {return size() > capacity;//默认返回false,如果为true,则需要进行移除最近未使用的元素}
}

2.2LRUCaChe的实现

package LRUCache;import java.util.HashMap;
import java.util.Map;public class MyLRUCache {//双向链表节点static class LRUNode{public int val;public int key;public LRUNode prev;public LRUNode next;//给一个无参构造方法,初始化带头带尾双向链表头节点public LRUNode(){}public LRUNode(int key, int val){this.key = key;this.val = val;}//重写Object类中的方法,将双向链表节点值以字符串形式输出@Overridepublic String toString() {return "{" +"key=" + key +", val=" + val +'}';}}//需要声明一个哈希表,用来检查节点值是否存在private Map<Integer, LRUNode> cache;private int usedSize;//双向链表中有效数据的个数private int capacity;//双向链表的容量大小private LRUNode head, tail;public MyLRUCache(int capacity){this.usedSize = 0;this.capacity = capacity;cache = new HashMap<>();//实例化哈希表//伪头节点/尾节点head = new LRUNode();tail = new LRUNode();//先将链表头尾节点相连head.next = tail;tail.prev = head;}//存储元素public void put(int key, int val){//1.查找当前的key是否存储过LRUNode node = cache.get(key);//2.判断key在链表中是否存储过if (node != null){//存储过,更新节点对应的valnode.val = val;//将节点移动到末端moveToTail(node);}else{//没有存储过,新建一个节点值LRUNode cur = new LRUNode(key, val);//先插入哈希中cache.put(key, cur);//将节点添加到链表尾部addToTail(cur);usedSize++;//判断容量是否充足if(usedSize > capacity){//删除最近未使用的节点removeNode(head.next);//删除哈希表中的节点cache.remove(head.next.key);usedSize--;}}}private void addToTail(LRUNode node) {tail.prev.next = node;node.next = tail;node.prev = tail.prev;tail.prev = node;}private void moveToTail(LRUNode node) {//先删除节点removeNode(node);//将节点尾插addToTail(node);}//删除节点private void removeNode(LRUNode node) {node.prev.next = node.next;node.next.prev = node.prev;}//获取元素public int get(int key){//判断元素是否在链表中LRUNode node = cache.get(key);if(node == null) {return -1;}else{moveToTail(node);}return node.val;}public void printLRU(){LRUNode cur = head.next;while(cur != tail){System.out.print(cur);cur = cur.next;}System.out.println();}public static void main(String[] args) {MyLRUCache lruCache = new MyLRUCache(3);lruCache.put(100,10);lruCache.put(110,11);lruCache.put(120,12);lruCache.printLRU();System.out.println("获取元素");System.out.println(lruCache.get(110));System.out.println(lruCache.get(100));lruCache.printLRU();System.out.println("存放元素,会删除头节点,因为头节点是最近最少使用的: ");lruCache.put(999,99);lruCache.printLRU();}}

相关文章:

深入理解 并查集LRUCaChe

并查集&LRUCaChe 个人主页&#xff1a;顾漂亮 文章专栏&#xff1a;Java数据结构 1.并查集的原理 在一些应用问题中&#xff0c;需要将n个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个单元素集合&#xff0c;然后根据一定规律将归于同一组元素的…...

详解 c++ 中的 namespage

C 中的命名空间很特别&#xff0c;其他编程语言基本都没有。命名空间介于函数与类之间&#xff0c;兼顾了二者的一些优点。这篇博客根据 chatgpt 的回答整理。 文章目录 **1. 什么是 namespace&#xff08;命名空间&#xff09;&#xff1f;****2. 语法****3. 使用 namespace 访…...

50周学习go语言:第五周 复合类型与词频统计

以下是第五周复合类型&#xff08;数组、切片与映射&#xff09;的详细学习内容&#xff0c;按照第四周的深度要求设计&#xff1a; 第五周&#xff1a;复合类型与词频统计 一、复合类型详解 1. 数组&#xff08;Array&#xff09; // 声明与初始化 var arr1 [3]int …...

HTTP非流式请求 vs HTTP流式请求

文章目录 HTTP 非流式请求 vs 流式请求一、核心区别 服务端代码示例&#xff08;Node.js/Express&#xff09;非流式请求处理流式请求处理 客户端请求示例非流式请求&#xff08;浏览器fetch&#xff09;流式请求处理&#xff08;浏览器fetch&#xff09; Python客户端示例&…...

深圳南柯电子|医疗设备EMC测试整改检测:零到一,保障医疗安全

在当今医疗科技飞速发展的时代&#xff0c;医疗设备的电磁兼容性&#xff08;EMC&#xff09;已成为确保其安全、有效运行的关键要素之一。EMC测试整改检测不仅关乎设备的性能稳定性&#xff0c;更是保障患者安全、避免电磁干扰引发医疗事故的重要措施。 一、医疗设备EMC测试整…...

详解:事务注解 @Transactional

创作内容丰富的干货文章很费心力&#xff0c;感谢点过此文章的读者&#xff0c;点一个关注鼓励一下作者&#xff0c;激励他分享更多的精彩好文&#xff0c;谢谢大家&#xff01; Transactional 是 Spring Framework 中常用的注解之一&#xff0c;它可以被用于管理事务。通过使…...

【虚拟仪器技术】labview操作指南和虚拟仪器技术习题答案(一)

今天是2025年2月24日&#xff0c;画的是fate/Grand Order里面的阿尔托莉雅.卡斯特&#xff0c;武内老师的画。 目录 第1章 第2章 第3章 第4章 第5章 关注作者了解更多 我的其他CSDN专栏 毕业设计 求职面试 大学英语 过程控制系统 工程测试技术 虚拟仪器技术 可编程…...

在Linux桌面上创建Idea启动快捷方式

1、在桌面新建idea.desktop vim idea.desktop [Desktop Entry] EncodingUTF-8 NameIntelliJ IDEA CommentIntelliJ IDEA Exec/home/software/idea-2021/bin/idea.sh Icon/home/software/idea-2021/bin/idea.svg Terminalfalse TypeApplication CategoriesApplication;Developm…...

渗透测试(WAF过滤information_schema库的绕过,sqllib-46关,海洋cms9版本的注入)

1.sqlin-lib 46关 打开网站配置文件发现 此网站的对ID进行了排序&#xff0c;我们可以知道&#xff0c;order by接不了union &#xff0c;那我们可以通过测试sort&#xff0c;rond等函数&#xff0c;观察网页的反馈来判断我们的盲注是否正确 我们发现 当参数有sort来排序时&…...

Unity基础——资源导出分享为Unity Package

一.选中要打包的文件夹&#xff0c;右击&#xff0c;点击Exporting package 二.勾选 Include Dependencies&#xff0c;点击Export Include Dependencies&#xff1a;代表是否包含资源依赖的选项 三.选择保存的位置&#xff0c;即可生成Unity package 最终形成文件&#xff1a…...

C语言【指针篇】(三)

C语言【指针篇】&#xff08;三&#xff09; 前言正文1. 数组名的理解2. 使用指针访问数组3. 一维数组传参的本质4. 冒泡排序5. 二级指针6. 指针数组7. 指针数组模拟二维数组 总结 前言 本文主要基于前面对指针的掌握&#xff0c;进一步学习&#xff1a;数组名的理解、使用指针…...

DevSecOps普及:安全与开发运维的深度融合

一、引言 随着软件开发模式的演进&#xff0c;DevOps已成为现代软件工程的主流实践。然而&#xff0c;在传统的DevOps流程中&#xff0c;安全往往被视为开发和运维之外的额外环节&#xff0c;导致安全漏洞在产品交付后才被发现&#xff0c;增加了修复成本和风险。为了解决这一…...

【JAVA-数据结构】Map和Set

上一篇我们聊到了排序相关内容&#xff0c;这一篇我们对Map和Set进行一系列说明&#xff0c;大家自取。 1.搜索树 1.1 概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树: 若它的左子树不为空&#xff0c;则左子树上所有节…...

从 0 到 1,用 Python 构建超实用 Web 实时聊天应用

从 0 到 1&#xff0c;用 Python 构建超实用 Web 实时聊天应用 本文深入剖析如何运用 Python 的 Flask 框架与 SocketIO 扩展&#xff0c;搭建一个功能完备的 Web 实时聊天应用。从环境搭建、前后端代码实现&#xff0c;到最终运行展示&#xff0c;逐步拆解关键步骤&#xff0…...

轻松搭建:使用Anaconda创建虚拟环境并在PyCharm中配置

一、使用Anaconda创建虚拟环境 1. 安装Anaconda 2..conda常用的命令 3. 创建虚拟环境-以搭建MachineVision为例 4. 激活虚拟环境 5. 安装依赖包 二、PyCharm配置环境 在进行Python项目开发时&#xff0c;合理的环境管理是必不可少的&#xff0c;特别是当你在多个项目中…...

【新人系列】Python 入门专栏合集

✍ 个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4dd; 专栏地址&#xff1a;https://blog.csdn.net/newin2020/category_12801353.html &#x1f4e3; 专栏定位&#xff1a;为 0 基础刚入门 Python 的小伙伴提供详细的讲解&#xff0c;也欢迎大佬们…...

linux ununtu安装mysql 怎么在my.cnf文件里临时配置 无密码登录

在 Ubuntu 中&#xff0c;若需通过修改 my.cnf 临时禁用 MySQL 的密码验证&#xff08;例如忘记 root 密码需要重置&#xff09;&#xff0c;可以通过添加 skip-grant-tables 选项实现。以下是具体步骤&#xff1a; 步骤 1&#xff1a;编辑 MySQL 配置文件 1. 打开 MySQL 配置…...

git,bash - 从一个远端git库只下载一个文件的方法

文章目录 git,bash - 从一个远端git库只下载一个文件的方法概述笔记写一个bash脚本来自动下载get_github_raw_file_from_url.shreanme_file.shfind_key_value.sh执行命令 END git,bash - 从一个远端git库只下载一个文件的方法 概述 github上有很多大佬上传了电子书库&#xf…...

python生成的exe文件防止反编译(pyinstaller加密)

python生成的exe文件可以轻松的被破解&#xff0c;为了防止反编译&#xff0c;知乎友友们给出了很多不同的见解&#xff0c;其中主流的回答是pyinstaller加密和niutka打包python&#xff0c;本篇介绍的方法是第一种&#xff0c;pyinstaller打包的时候进行加密&#xff0c;防破解…...

Android移动应用开发实践-1-下载安装和简单使用Android Studio 3.5.2版本(频频出错)

一、下载安装 1.Android Studio3.5.2下载地址&#xff1a;Android Studio3.5.2下载地址 其他版本下载地址&#xff1a;其他版本下载地址 2.安装教程&#xff08;可以多找几个看看&#xff09; 安装 | 手把手教你Android studio 3.5.2安装&#xff08;安装教程&#xff09;_a…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

srs linux

下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935&#xff0c;SRS管理页面端口是8080&#xff0c;可…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)

引言 在人工智能飞速发展的今天&#xff0c;大语言模型&#xff08;Large Language Models, LLMs&#xff09;已成为技术领域的焦点。从智能写作到代码生成&#xff0c;LLM 的应用场景不断扩展&#xff0c;深刻改变了我们的工作和生活方式。然而&#xff0c;理解这些模型的内部…...

Vite中定义@软链接

在webpack中可以直接通过符号表示src路径&#xff0c;但是vite中默认不可以。 如何实现&#xff1a; vite中提供了resolve.alias&#xff1a;通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...