深入理解 并查集LRUCaChe

news2025/2/27 13:58:23

并查集&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){
        //初始化数组大小为n
        elem = new int[n];
        //将数组中元素初始化为-1
        Arrays.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);
    }
    //必须要重写上述方法
    @Override
    protected 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类中的方法,将双向链表节点值以字符串形式输出
        @Override
        public 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){
            //存储过,更新节点对应的val
            node.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();
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2306916.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最新版本SpringAI接入DeepSeek大模型,并集成Mybatis

当时集成这个环境依赖冲突&#xff0c;搞了好久&#xff0c;分享一下依赖配置 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instan…

滑动验证组件-微信小程序

微信小程序-滑动验证组件&#xff0c;直接引用就可以了&#xff0c;效果如下&#xff1a; 组件参数&#xff1a; 1.enable-close&#xff1a;是否允许关闭&#xff0c;默认true 2.bind:onsuccess&#xff1a;验证后回调方法 引用方式&#xff1a; <verification wx:if&qu…

IDEA配置JSP环境

首先下载IDEA2021.3&#xff0c;因为最新版本不能简单配置web开发环境。然后新建一个java开发项目&#xff1a; 然后右键创建的项目&#xff0c;添加web框架&#xff1a; 选择web appliciation 在web inf文件夹下创建classes和lib文件夹&#xff1a; 点击file &#xff0c;选择…

Idea 中 Project Structure简介

在 IntelliJ IDEA 中&#xff0c;Project Structure&#xff08;项目结构&#xff09;对话框是一个非常重要的配置界面&#xff0c;它允许你对项目的各个方面进行详细的设置和管理。下面将详细介绍 Project Structure 中各个主要部分的功能和用途。 1. Project&#xff08;项…

旁挂负载分担组网场景

旁挂负载分担组网场景&#xff08;到路由策略&#xff09; 1.拓扑 2.需求 使用传统三层架构中MSTPVRRP组网形式VLAN 2—>W3,SW4作为备份 VLAN 3—>SW4,SW3作为备份 MSTP设计—>SW3、4、5运行 实例1:VLAN 2 实例2:VLAN 3 3.配置 交换层 SW3配置 抢占延时&#xff…

网络安全防御模型

目录 6.1 网络防御概述 一、网络防御的意义 二、被动防御技术和主动防御技术 三、网络安全 纵深防御体系 四、主要防御技术 6.2 防火墙基础 一、防火墙的基本概念 二、防火墙的位置 1.防火墙的物理位置 2.防火墙的逻辑位置 3. 防火墙的不足 三、防火墙技术类型 四…

Qt 开源音视频框架模块之QtAV播放器实践

Qt 开源音视频框架模块QtAV播放器实践 1 摘要 QtAV是一个基于Qt的多媒体框架&#xff0c;旨在简化音视频播放和处理。它是一个跨平台的库&#xff0c;支持多种音视频格式&#xff0c;并提供了一个简单易用的API来集成音视频功能。QtAV的设计目标是为Qt应用程序提供强大的音视…

uake 网络安全 reverse网络安全

&#x1f345; 点击文末小卡片 &#xff0c;免费获取网络安全全套资料&#xff0c;资料在手&#xff0c;涨薪更快 本文首发于“合天网安实验室” 首先从PEID的算法分析插件来介绍&#xff0c;要知道不管是在CTF竞赛的REVERSE题目中&#xff0c;还是在实际的商业产品中&#xf…

vue实现根据点击或滑动展示对应高亮

页面需求&#xff1a; 点击左侧版本号&#xff0c;右侧展示对应版本内容并置于顶部右侧某一内容滚动到顶部时&#xff0c;左侧需要展示高亮 实现效果&#xff1a; 实现代码&#xff1a; <template><div><div class"historyBox pd-20 bg-white">…

Magma:多模态 AI 智体的基础模型

25年2月来自微软研究、马里兰大学、Wisconsin大学、韩国 KAIST 和西雅图华盛顿大学的论文“Magma: A Foundation Model for Multimodal AI Agents”。 Magma 是一个基础模型&#xff0c;可在数字和物理世界中服务于多模态 AI 智体任务。Magma 是视觉-语言 (VL) 模型的重要扩展…

02_linux系统命令

一、绝对路径与相对路径 1.以 ./ 开始的路径名是相对路径 2.以 / 开始的路径是绝对路径. 相对路径:会随着用户当前所在的目录发生改变. 绝对路径:不会根据用户所在的路径而改变. 3.gcc 编译器 编译器把高级语言(C语言/JAVA语言/C语言)生成二进制代码的一种工具.gcc 是专用…

【leetcode hot 100 11】移动零

一、暴力解法&#xff1a;两个 for 循环&#xff0c;外层循环遍历所有可能的左边界&#xff0c;内层循环遍历所有可能的右边界 class Solution {public int maxArea(int[] height) {int max_area0;for(int i0; i<height.length; i){for(int ji1; j<height.length; j){in…

AI绘画软件Stable Diffusion详解教程(2):Windows系统本地化部署操作方法(专业版)

一、事前准备 1、一台配置不错的电脑&#xff0c;英伟达显卡&#xff0c;20系列起步&#xff0c;建议显存6G起步&#xff0c;安装win10或以上版本&#xff0c;我的显卡是40系列&#xff0c;16G显存&#xff0c;所以跑大部分的模型都比较快&#xff1b; 2、科学上网&#xff0…

轨迹控制--odrive的位置控制---负载设置

轨迹控制 此模式使您可以平滑地使电机旋转&#xff0c;从一个位置加速&#xff0c;匀速和减速到另一位置。 使用位置控制时&#xff0c;控制器只是试图尽可能快地到达设定点。 使用轨迹控制模式可以使您更灵活地调整反馈增益&#xff0c;以消除干扰&#xff0c;同时保持平稳的运…

【安卓逆向】逆向APP界面UI修改再安装

1.背景 有一客户找到我&#xff0c;说能不能把APP首页的底部多余界面去掉。 逆向实战 想要去除安卓应用软件中的内容&#xff0c;需要对APP逆向进行修改再打包。 通过工具 MIT管理器工具 提取APK包&#xff0c;点击apk文件&#xff0c;点击查看反编译apk。 搜索关键字。这里关键…

SAP Webide系列(7)- 优化FreeStyle新建项目预设模板

目录 一、背景 二、优化目标 三、定位调整点 四、调整步骤 五、效果展示 六、附言 一、背景 在每次通过Webide进行FreeStyle方式自开发SAP UI5应用的时候&#xff0c;新建项目&#xff0c;得到的模板文件都是只有很少的内容&#xff08;没有路由配置、没有设置默认全屏等…

python读取sqlite温度数据,并画出折线图

需求&#xff1a; 在Windows下请用python画出折线图&#xff0c;x轴是时间&#xff0c;y轴是温度temperature 和体感温度feels_like_temperature 。可以选择县市近1小时&#xff0c;近1天&#xff0c;近1个月的。sqlite文件weather_data.db当前目录下&#xff0c;建表结构如下…

【Linux网络编程】高效I/O--select/poll服务器

目录 多路转接之select select服务器实现 获取连接 handlerEvent select服务器代码链接 select的优缺点 多路转接之poll poll服务器实现(select服务器改写) poll的优缺点 多路转接之select select的作用 I/O的本质 等 拷贝 多路转接就是通过同时等待多个文件描述…

C语言实战项目(1)---------->猜数字游戏

在学习完循环和选择结构之后&#xff0c;我们可以做一个猜数字游戏。在此项目之前&#xff0c;如果还不会C语言的if语句、switch语句等组成选择结构的语句&#xff0c;while循环、for循环、do-while循环等组成循环结构的语句。可以参考我之前的博客&#xff1a; C语言&#xf…

Failed to start The PHP FastCGI Process Manager.

报错如下&#xff1a; Job for php-fpm.service failed because the control process exited with error code. See "systemctl status php-fpm.service" and "journalctl -xe" for details. 2月 25 21:49:00 nginx systemd[1]: Starting The PHP FastC…