并查集与LRUCache(Java数据结构)

前言:

        学习过二叉树之后就应该知道了如何构建一颗二叉树,双亲结点和孩子节点的关系,甚至可以放在顺序表中去构建一棵二叉树!

        接下来我们要以另一种方式去组织一棵树:       

        如何表示一棵树之间的关系?(这棵树只能存放整型数据)

        有这样的一个思路,我们可以继续借助顺序表,用顺序表的下标表示每个整型,每个下表里面放的内容有两种情况:

        1、如果该节点就是根节点的话,就需要存放 -(所有孩子(包括孙子等)的个数)。(不要忘记加负号)

        2、如果该节点不是根节点的时候,就需要存放双亲结点的下标!

        接下就以这种方式去组织一棵二叉树!

并查集:

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

        比如:某公司今年校招全国总共招生10 人,西安招 4 人,成都招 3 人,武汉招 3 人, 10 个人来自不同的学校,起先互不相识,每个学生都是一个独立的小团体,现给这些学生进行编号:{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 给以下数组用来存储该小集体,数组中的数字代表:该小集体中具有成员的个数。( 负号下文解释 )
        毕业后,学生们要去公司上班,每个地方的学生自发组织成小分队一起上路,于是:
西安学生小分队 s1={0,6,7,8} ,成都学生小分队 s2={1,4,9} ,武汉学生小分队 s3={2,3,5} 就相互认识了, 10 个人形成了三个小团体。假设右三个群主0,1,2 担任队长,负责大家的出行。
一趟火车之旅后,每个小分队成员就互相熟悉,称为了一个朋友圈。
从上图可以看出:编号 6,7,8 同学属于 0 号小分队,该小分队中有 4 ( 包含队长 0) ;编号为 4 9 的同学属于 1
小分队,该小分队有 3 ( 包含队长 1) ,编号为 3 5 的同学属于 2 号小分队,该小分队有 3 个人 ( 包含队长 1)
仔细观察数组中内融化,可以得出以下结论:
1. 数组的下标对应集合中元素的编号
2. 数组中如果为负数,负号代表根,数字代表该集合中元素个数
3. 数组中如果为非负数,代表该元素双亲在数组中的下标
在公司工作一段时间后,西安小分队中 8 号同学与成都小分队 1 号同学奇迹般的走到了一起,两个小圈子的学
生相互介绍,最后成为了一个小圈子:
现在 0 集合有 7 个人, 2 集合有 3 个人,总共两个朋友圈。

以上的例子很清楚的说明并查集的组织的和过程!

当然此时组织好并查集之后还是可以进行增删查找的工作!

接下来先来手搓一个并查集:

手动实现一个并查集:

         

public class MyUnionfindset {
    private int[] array;
    public MyUnionfindset(int num) {
        this.array = new int[num];
        Arrays.fill(array,-1);//整体初始化位-1
    }
    /**
     * 查找x的根*
     */
    public int findRoot(int x) {
        if(x >= array.length) {
            throw new ArrayIndexOutOfBoundsException("数据不合法");
        }
        while(array[x] >=0) {
            x = array[x];
        }
        return x;
    }
    /**
     * 合并x1和x2,前提是x1和x2都必须是根节点
     */
    public void union(int x1,int x2) {
        //找x1的根
        int root1 = findRoot(x1);
        //找x2的根
        int root2 = findRoot(x2);
        //说明root1和root2是同一个节点
        if(root1 == root2) {
            return ;
        }
        //合并,root2合并在root1中
        array[root1] = array[root1] + array[root2];
        array[root2] = root1;
    }
    /**
     * 判断两个数字是否在同一个集合当中
     */
    public boolean isSameSet(int x1,int x2) {
        //找x1的根节点
        int root1 = findRoot(x1);
        //找x2的根节点
        int root2 = findRoot(x2);
        if (root1 == root2) {
            return true;
        } else return false;
    }
        /**
         * 求数组当中集合的个数
         */
        public int getCount(int[] array) {
            int count = 0;
            for (int i = 0;i<array.length;i++) {
                if(array[i] < 0) {
                    count++;
                }
            }
            return count;
        }
    public void printArr() {
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]+" ");
        }
        System.out.println();
    }
}

以上述的例子未测试对象:

    public static void main(String[] args) {
        MyUnionfindset myUnionfindset = new MyUnionfindset(10);
        myUnionfindset.union(0,6);
        myUnionfindset.union(0,7);
        myUnionfindset.union(0,8);
        myUnionfindset.union(1,4);
        myUnionfindset.union(1,9);
        myUnionfindset.union(2,3);
        myUnionfindset.union(2,5);
        myUnionfindset.union(0,1);
        myUnionfindset.printArr();
    }

测试结果如下:

并查集的应用:

        547. 省份数量 - 力扣(LeetCode)

    public int findCircleNum(int[][] isConnected) {
        int n = isConnected.length;
        MyUnionfindset myUnionfindset = new MyUnionfindset(n);
        for(int i = 0;i<n;i++) {
            for(int j = 0;j < isConnected[i].length;j++) {
                //两个城市相连就合并
                if(isConnected[i][j] == 1) {
                    myUnionfindset.union(i,j);
                }
            }
        }
        return myUnionfindset.getCount();
    }

        990. 等式方程的可满足性 - 力扣(LeetCode)

    public boolean equationsPossible(String[] equations) {
        MyUnionfindset myUnionfindset = new MyUnionfindset(26);
        for(String x: equations) {
          if(x.charAt(1) == '=') {
            myUnionfindset.union(x.charAt(0)-'a',x.charAt(3)-'a');
          }
        }
        for(String x: equations) {
          if(x.charAt(1) == '!') {
            //找根节点
            int n1 = myUnionfindset.findRoot(x.charAt(0)-'a');
            int n2 = myUnionfindset.findRoot(x.charAt(3)-'a');
            if(n1 == n2) {
                return false;
            }
          }
        }
        return true;
    }

LRUCache:

        什么是LRUCache?

        LRU是 Least Recently Used 的缩写,意思是 最近最少使用 ,它是一种 Cache 替换算法。 什么是 Cache ?狭义的Cache 指的是位于 CPU 和主存间的快速 RAM , 通常它不像系统主存那样使用 DRAM 技术,而使用昂贵但较快速的SRAM 技术。 广义上的 Cache 指的是位于速度相差较大的两种硬件之间, 用于协调两者数据传输速度差异的结构。除了CPU 与主存之间有 Cache , 内存与硬盘之间也有 Cache ,乃至在硬盘与网络之间也有某种意义上的Cache ── 称为 Internet 临时文件夹或网络内容缓存等。
Cache 的容量有限,因此当 Cache 的容量用完后,而又有新的内容需要添加进来时, 就需要挑选并舍弃原有 的部分内容,从而腾出空间来放新内容。 LRU Cache 的替换原则就是将最近最少使用的内容替换掉

 LRUCache的实现:

        实现LRU Cache的方法和思路很多,但是要保持高效实现O(1)putget,那么使用双向链表和哈希表的搭配是最高效和经典的。使用双向链表是因为双向链表可以实现任意位置O(1)的插入和删除,使用哈希表是因为哈希表的增删查改也是O(1)

.JDK 中类似 LRUCahe 的数据结构 LinkedHashMap
    public static void main(String[] args) {
        Map<String, String> map = new LinkedHashMap<>(16,0.75f,false);
        map.put("1", "a");
        map.put("2", "b");
        map.put("4", "e");
        map.put("3", "c");
        System.out.println(map);
    }

参数说明:

LInkedHashMap类的构造方法有很多种,但是其中最全面的就是有三个参数的构造方法:

        1、initialCapacity表示的是初始容量,可以自己传入初始容量,初始容量默认是16

        2、loadFactor表示的是负载因子,在学习HashMap的时候已将介绍过了负载因子,为了降低冲突,系统默认负载因子位0.75。

        3、accessOrder表示的是访问顺序:

                当传入true时,如果进行get按访问顺序。

                当传入false时,如果进行get按插入顺序。

        (如果不太能理解,下面会进行示范)

如下所示:

        如果传入的参数是false时,此时我get一个数据:

    public static void main(String[] args) {
        Map<String, String> map = new LinkedHashMap<>(16,0.75f,true);
        map.put("1", "a");
        map.put("2", "b");
        map.put("4", "e");
        map.put("3", "c");
        map.get("2");
        System.out.println(map);
    }

结果如下:

        如果传入的参数是true时,此时我继续get一个数据:

    public static void main(String[] args) {
        Map<String, String> map = new LinkedHashMap<>(16,0.75f,true);
        map.put("1", "a");
        map.put("2", "b");
        map.put("4", "e");
        map.put("3", "c");
        map.get("2");
        System.out.println(map);
    }

结果如下:

发先如果是true时,此时打印的顺序将发生改变!!

将刚刚get的数据放在了链表的最后!!

 手动实现LRUCache:

        还是一样我们先手动实现一个,更加清楚的了解内部的原理:
 

public class LRUCache {
    //链表节点
    public class DLinkedNode {
        int key;
        int value;
        DLinkedNode prve;
        DLinkedNode next;
        public DLinkedNode() {}
        public DLinkedNode(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
    Map<Integer,DLinkedNode> map = new HashMap<>();
    private int size;
    private int capacity;
    //带哨兵位的双向链表
    private DLinkedNode head,tail;
    public LRUCache(int capacity) {
        size = 0;
        this.capacity = capacity;
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head.next = tail;
        tail.prve = head;
    }
    public int get(int key) {
        DLinkedNode node = map.get(key);
        if(node == null) {
            return -1;
        }
        moveTail(node);
        return node.value;
    }
    private void moveTail(DLinkedNode node) {
    removeNode(node);
    addToTail(node);
    }
    private void removeNode(DLinkedNode node) {
        node.prve.next = node.next;
        node.next.prve = node.prve;
    }
    private void addToTail(DLinkedNode node) {
        tail.prve.next = node;
        node.prve = tail.prve;
        node.next = tail;
        tail.prve = node;
    }
    public void put(int key,int value) {
        //查看K是否存在
        DLinkedNode node = map.get(key);
        if(node == null) {
            //如果不存在那么就新创建一个,并且放入哈希表中
            DLinkedNode newNode = new DLinkedNode(key,value);
            map.put(key,newNode);
            size++;
            addToTail(newNode);
            if(size > capacity) {
                //如果插入的数量大于容量,就需要删除头节点
                DLinkedNode tail = removeHead();
                //删除哈希表对应的相
                map.remove(tail.key);
                size--;
            }
        }else {
            //如果key存在先通过哈希表找到对应的位置修改value
            node.value = value;
            //移动到链表末尾
            moveTail(node);
        }
    }
    //删除头节点
    private DLinkedNode removeHead() {
        DLinkedNode ret = head.next;
        head.next = ret.next;
        ret.next.prve = head;
        return ret;
    }
}

带哨兵位的双向链表+哈希表的结合,完美组成了快捷方便的数据结构 ——LRUCache(最近最少使用缓存)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Nature Communications|基于深度学习的HE染色组织向特殊染色的转换

工作速览 病理学是通过视觉检查组织切片来进行的&#xff0c;这些切片通常用组织化学染色法染色。虽然苏木精和伊红&#xff08;H&E&#xff09;染色最为常用&#xff0c;但特殊染色可以为不同的组织成分提供额外的对比度。 **在这里&#xff0c;作者展示了从H&E染色…

阿里国际2025届校园招聘 0826算法岗笔试

目录 1. 第一题2. 第二题3. 第三题 ⏰ 时间&#xff1a;2024/08/26 &#x1f504; 输入输出&#xff1a;ACM格式 ⏳ 时长&#xff1a;100min 本试卷分为单选&#xff0c;多选&#xff0c;编程三部分&#xff0c;这里只展示编程题。 1. 第一题 题目描述 小红有一个大小为 n …

goframe开发一个企业网站 模版界面5

html或者说是模板的控制 以下是是系统的设置 server:address: ":8000"serverRoot: "resource/public" #这里要加上&#xff0c;为以后的静态文件的引入准备openapiPath: "/api.json"swaggerPath: "/swagger"cookieMaxAge: "365…

适配器模式:类适配器与对象适配器

适配器模式是一种结构性设计模式&#xff0c;旨在将一个接口转换成客户端所期望的另一种接口。它通常用于解决由于接口不兼容而导致的类之间的通信问题。适配器模式主要有两种实现方式&#xff1a;类适配器和对象适配器。下面&#xff0c;我们将详细探讨这两种方式的优缺点及适…

如何在Linux系统中使用Ansible进行自动化部署

如何在Linux系统中使用Ansible进行自动化部署 Ansible简介 安装Ansible 在Debian/Ubuntu系统中安装 在CentOS/RHEL系统中安装 Ansible的基本概念 Inventory文件 Playbooks Modules 创建Inventory文件 编写第一个Playbook 创建Playbook文件 运行Playbook 使用Handlers 编写包…

Spring Boot 3.x 整合 Druid 数据库连接池(含密码加密)

Spring Boot 3.x 整合 Druid 数据库连接池&#xff08;含密码加密&#xff09; 1. 为什么需要数据库连接池&#xff1f; 在传统的数据库连接中&#xff0c;每一次与数据库连接都会消耗大量的系统资源和时间。数据库连接池会提前创建一定数量的数据库连接保存在池中&#xff0…

【论文#码率控制】Rate Control for H.264 Video With Enhanced Rate and Distortion Models

目录 摘要1.前言2.编码器框架3.增强RD模型3.1 头部比特的速率模型3.2 源比特的码率模型3.3 失真模型3.4 块类型的确定 4.面向H264 Baseline Profile的码率控制算法5.实验结果6.结论和未来工作 《Rate Control for H.264 Video With Enhanced Rate and Distortion Models》 Auth…

vue和django接口联调

vue访问服务端接口 配置跨域 前端跨域 打开vite.config.js&#xff0c;在和resolve同级的地方添加配置。 proxy代表代理的意思 "/api"是以/api开头的路径走这个配置 target代表目标 changeOrigin: true,是开启跨域请求 rewrite是编辑路径。 (path) > pa…

HTML鼠标移动的波浪线动画——页面将会初始化一个Canvas元素,并使用JavaScript代码在Canvas上绘制响应鼠标移动的波浪线动画

代码如下 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Wave Animation</title><style&…

玩转Docker | Docker基础入门与常用命令指南

玩转Docker | Docker基础入门与常用命令指南 引言基本概念help帮助信息常用命令管理镜像运行容器构建镜像其他Docker命令整理结语引言 Docker 是一种开源的应用容器引擎,它允许开发者将应用程序及其依赖打包进一个可移植的容器中,然后发布到任何流行的 Linux 机器上。这大大简…

信息学科平台设计与实现:Spring Boot技术详解

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

现代化水电管理:Spring Boot在大学城的实践

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

Rust 力扣 - 289. 生命游戏

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们记录上一行和当前行转换之后的状态&#xff0c;当前行转换之后的状态计算完毕后调整上一行状态&#xff0c;直至最后一行状态计算完毕后调整最后一行状态 题解代码 pub fn game_of_life(board: &mut V…

015:地理信息系统开发平台ArcGIS Engine10.2与ArcGIS SDK for the Microsoft .NET Framework安装教程

摘要&#xff1a;本文详细介绍地理信息系统开发平台ArcGIS Engine10.2与ArcGIS SDK for the Microsoft .NET Framework的安装流程。 一、软件介绍 ArcGIS Engine 10.2是由Esri公司开发的一款强大的GIS&#xff08;地理信息系统&#xff09;开发平台。该软件基于ArcGIS 10.2 fo…

如何进行PDF高效合并?盘点11款PDF编辑器给你

是不是经常觉得烦&#xff1a;一个PDF文件特别长&#xff0c;里面好多章节或者报告&#xff0c;每次要看或者给别人发都得翻半天&#xff1f;别担心&#xff0c;今天我就来教你们几个合并PDF的小技巧&#xff0c;保证让你在2024年变成整理文件的高手&#xff01; 1、福昕PDF文…

PPT素材、模板免费下载!

做PPT一定要收藏好这6个网站&#xff0c;PPT模板、素材、图表、背景等超多素材全部免费下载。 1、菜鸟图库 ppt模板免费下载|ppt背景图片 - 菜鸟图库 菜鸟图库网有非常丰富的免费素材&#xff0c;像设计类、办公类、自媒体类等素材都很丰富。PPT模板种类很多&#xff0c;全部都…

Cpp二叉搜索树的讲解与实现(21)

文章目录 前言一、二叉搜索树的概念定义特点 二、二叉树的实现基本框架查找插入删除当只有0 ~ 1个孩子的时候当有2个孩子的时候 三、二叉树的应用K模型KV模型 四、二叉树的性能分析总结 前言 这是全新的一个篇章呢&#xff0c;二叉搜索树是我们接下来学习set、map的前提 迈过它…

群控系统服务端开发模式-应用开发-菜单功能开发

为什么优先开发菜单&#xff0c;而不是优先开发管理员&#xff1f;查看一下程序草图就明白&#xff0c;还有一个重点就是&#xff0c;管理员需要添加图片&#xff0c;而我还没有封装上传工具及上传目标。 一、添加路由 在根目录下route文件夹下的app.php文件里面&#xff0c;添…

2024年,Rust开发语言,现在怎么样了?

Rust开发语言有着一些其他语言明显的优势&#xff0c;但也充满着争议&#xff0c;难上手、学习陡峭等。 Rust 是由 Mozilla 主导开发的通用、编译型编程语言&#xff0c;2010年首次公开。 在 Stack Overflow 的年度开发者调查报告中&#xff0c;Rust 连续多年被评为“最受喜爱…

c# 值类型

目录 1、c#类型2、值类型2.1 结构体2.2 枚举 1、c#类型 类型&#xff08;Type&#xff09;又叫数据类型&#xff08;Data Type&#xff09;。 A data type is a homogeneous collection of values,effectively prensented,equipped with a set of operations which manipulate…