并查集LRU Cache

news2024/12/23 12:36:22

目录

前言

一 . 并查集

1.1  并查集原理

1.2 并查集的实现

二 . LRU Cache

2.1 什么是LRU Cache

2.2 LRU Cache实现

2.3 JDK中类似LRUCahe的数据结构LinkedHashMap

2.4 自己实现链表

总结


前言

大家好,今天给大家介绍两种数据结构并查集&LRU Cache


一 . 并查集

1.1  并查集原理

在一些应用问题中,需要将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个人,总共两个朋友圈。

通过以上例子可知,并查集一般可以解决一下问题:

1. 查找元素属于哪个集合

沿着数组表示树形关系以上一直找到根(即:树中中元素为负数的位置)

2. 查看两个元素是否属于同一个集合

沿着数组表示的树形关系往上一直找到树的根,如果根相同表明在同一个集合,否则不在

3. 将两个集合归并成一个集合

将两个集合中的元素合并 将一个集合名称改成另一个集合的名称

4. 集合的个数 

遍历数组,数组中元素为负数的个数即为集合的个数。


1.2 并查集的实现

没什么难度,大家自己看看就好

/**
 * 并查集
 */
public class UnionFindSet {
    private int[] elem;
    private int usedSize;

    public UnionFindSet(int capacity) {
        elem = new int[capacity];
        Arrays.fill(elem,-1);
    }

    /**
     * 查找一个数据的根节点
     * @param x
     * @return 下标
     */
    public int findRoot(int x){
        if(x < 0){
            throw new IndexOutOfBoundsException("数组越界,下标不合法!");
        }
        while(elem[x] >= 0){
            x = elem[x];
        }
        return x;
    }

    /**
     * 查询两个数字是否在同一个集合
     * @param x1 数字一
     * @param x2 数字二
     * @return 在同一个集合返回true反之返回false
     */
    public boolean isSameUnionFindSet(int x1,int x2){
        return findRoot(x1) == findRoot(x2);
    }

    /**
     * 合并操作[合并的是根节点]
     * @param x1
     * @param x2
     */
    public void union(int x1,int x2){
        int rootIndex1 = findRoot(x1);
        int rootIndex2 = findRoot(x2);
        if(rootIndex1 == rootIndex2){
            return;
        }
        elem[rootIndex1] = elem[rootIndex1]+elem[rootIndex2];
        elem[rootIndex2] = rootIndex1;
    }

    /**
     * 集合的个数
     * @return
     */
    public int getCount(){
        int count = 0;
        for (int i : elem) {
            if(i < 0){
                count++;
            }
        }
        return count;
    }

    public void print(){
        for (int i : elem) {
            System.out.print(i+" ");
        }
    }


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

        unionFindSet.print();

        System.out.println("合并: 8和1");
        unionFindSet.union(8,1);

        unionFindSet.print();

        System.out.println(unionFindSet.isSameUnionFindSet(6, 9));
    }
}

练手题 

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

class Solution {
    private int[] elem;
    private int usedSize;

    public int findCircleNum(int[][] isConnected) {
        elem = new int[isConnected.length];
        Arrays.fill(elem,-1);
        for(int i = 0; i<isConnected.length; i++){
            for(int j = 0; j<isConnected[0].length; j++){
                if(isConnected[i][j] == 1)
                    union(i,j);
            }
        }
        return getCount();
    }

    /**
     * 查找一个数据的根节点
     * @param x
     * @return 下标
     */
    public int findRoot(int x){
        if(x < 0){
            throw new IndexOutOfBoundsException("数组越界,下标不合法!");
        }
        while(elem[x] >= 0){
            x = elem[x];
        }
        return x;
    }

    /**
     * 合并操作[合并的是根节点]
     * @param x1
     * @param x2
     */
    public void union(int x1,int x2){
        int rootIndex1 = findRoot(x1);
        int rootIndex2 = findRoot(x2);
        if(rootIndex1 == rootIndex2){
            return;
        }
        elem[rootIndex1] = elem[rootIndex1]+elem[rootIndex2];
        elem[rootIndex2] = rootIndex1;
    }

    /**
     * 集合的个数
     * @return
     */
    public int getCount(){
        int count = 0;
        for (int i : elem) {
            if(i < 0){
                count++;
            }
        }
        return count;
    }
}
class Solution {
    public boolean equationsPossible(String[] equations) {
        UnionFindSet unionFind = new UnionFindSet(26);
        for(String s : equations){
            if(s.charAt(1) == '='){
                unionFind.union(s.charAt(0)-'a',s.charAt(3)-'a');
            }
        }

        for(String s : equations){
            if(s.charAt(1) == '!'){
                boolean flag = unionFind.isSameUnionFindSet(s.charAt(0)-'a',s.charAt(3)-'a');
                if(flag){
                    return false;
                }
            }
        }

        return true;
    }

     /**
     * 并查集
     */
    public class UnionFindSet {
        private int[] elem;
        private int usedSize;
    
        public UnionFindSet(int capacity) {
            elem = new int[capacity];
            Arrays.fill(elem,-1);
        }
    
        /**
         * 查找一个数据的根节点
         * @param x
         * @return 下标
         */
        public int findRoot(int x){
            if(x < 0){
                throw new IndexOutOfBoundsException("数组越界,下标不合法!");
            }
            while(elem[x] >= 0){
                x = elem[x];
            }
            return x;
        }
    
        /**
         * 查询两个数字是否在同一个集合
         * @param x1 数字一
         * @param x2 数字二
         * @return 在同一个集合返回true反之返回false
         */
        public boolean isSameUnionFindSet(int x1,int x2){
            return findRoot(x1) == findRoot(x2);
        }
    
        /**
         * 合并操作[合并的是根节点]
         * @param x1
         * @param x2
         */
        public void union(int x1,int x2){
            int rootIndex1 = findRoot(x1);
            int rootIndex2 = findRoot(x2);
            if(rootIndex1 == rootIndex2){
                return;
            }
            elem[rootIndex1] = elem[rootIndex1]+elem[rootIndex2];
            elem[rootIndex2] = rootIndex1;
        }
    
    }
}

二 . LRU Cache

2.1 什么是LRU Cache

LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法。 什么是Cache?狭义 的Cache指的是位于CPU和主存间的快速RAM, 通常它不像系统主存那样使用DRAM技术,而使用昂贵但较 快速的SRAM技术。 广义上的Cache指的是位于速度相差较大的两种硬件之间, 用于协调两者数据传输速度 差异的结构。除了CPU与主存之间有Cache, 内存与硬盘之间也有Cache,乃至在硬盘与网络之间也有某种 意义上的Cache── 称为Internet临时文件夹或网络内容缓存等。

Cache的容量有限,因此当Cache的容量用完后,而又有新的内容需要添加进来时, 就需要挑选并舍弃原有 的部分内容,从而腾出空间来放新内容。LRU Cache 的替换原则就是将最近最少使用的内容替换掉。其实, LRU译成最久未使用会更形象, 因为该算法每次替换掉的就是一段时间内最久没有使用过的内容。

2.2 LRU Cache实现

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

2.3 JDK中类似LRUCahe的数据结构LinkedHashMap

参数说明:

1. initialCapacity    初始容量大小,使用无参构造方法时,此值默认是16

2. loadFactor    加载因子,使用无参构造方法时,此值默认是 0.75f

3. accessOrder false: 基于插入顺序 true: 基于访问顺序

示例1:当accessOrder的值为false的时候

public static void main(String[] args) {

         Map 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);

}

输出结果: {1=a, 2=b, 4=e, 3=c}

以上结果按照插入顺序进行打印

示例2:当accessOrder的值为true的时候

public static void main(String[] args) {

         Map 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("1");

         map.get("2");

         System.out.println(map);

}

输出结果: {4=e, 3=c, 1=a, 2=b}

每次使用get方法,访问数据后,会把数据放到当前双向链表的最后。

当accessOrder为true时,get方法和put方法都会调用recordAccess方法使得最近使用的Entry移到双向链表的末尾;当accessOrder为默认值false时,从源码中可以看出recordAccess方法什么也不会做。

2.4 自己实现链表

/*
* 最近最久未使用cache替换算法
* 哈希+双向链表
* */
public class LruCache extends LinkedHashMap {

    private final ListNode head; // 傀儡头节点,避免分情况讨论
    private final ListNode tail; // 傀儡尾节点,避免分情况讨论
    private final int capacity;
    private int usedSize;

    private final Map<Integer,ListNode> hash;

    public LruCache(int capacity) {
        this.capacity = capacity;
        hash = new HashMap<>(capacity);
        head = new ListNode();
        tail = new ListNode();
        head.next = tail;
        tail.pre = head;
    }


    /**
     * 获取当前key对应的value
     * @param key
     * @return
     */
    public int get(int key){
        ListNode node = hash.get(key);
        // key 不存在,返回-1
        if(node == null) return -1;
        // key存在,将该节移动至尾部
        moveTail(node);
        return node.val;
    }

    private void moveTail(ListNode node){
        removeNode(node);
        addToTail(node);
    }

    private void addToTail(ListNode node) {
        tail.pre.next = node;
        node.pre = tail.pre;
        node.next = tail;
        tail.pre = node;
    }


    private void removeNode(ListNode node){
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }


    /**
     * 删除第一个节点的数据
     */
    public ListNode removeHead(){
        ListNode next = head.next;
        head.next = head.next.next;
        head.next.pre = head;
        usedSize--;
        return next;
    }

    /**
     * 添加节点
     * @param key
     * @param value
     */
    public void put(int key,int value){
        /*
        * 1.查看是否存在该key对应的节点
        * 2.如果不存在,在map中添加该key,并在尾部添加节点
        * 3.如果usedSize > capacity 移除头部元素
        * 4.如果存在,将该节点拖到尾部即可
        * */
        ListNode node = hash.get(key);
        // 1.查看是否存在该key对应的节点
        if(node == null){
            node = new ListNode(key,value);
            hash.put(key,node);
            usedSize++;
            // 3.如果usedSize > capacity 移除头部元素
            if(usedSize > capacity){
                ListNode listNode = removeHead();
                hash.remove(listNode.key);
                usedSize--;
            }
            //  4.如果存在,将该节点拖到尾部即可
            addToTail(node);
        }else{
            // 2.如果不存在,在map中添加该key,并在尾部添加节点
            node.val = value;
            // 移动至尾部
            moveTail(node);
        }
    }

    /**
     * 节点内部类
     */
    static class ListNode {
        int val;
        int key;
        ListNode pre;
        ListNode next;

        public ListNode(int key,int val) {
            this.key = key;
            this.val = val;
        }

        public ListNode() {

        }
    }

    public static void main(String[] args) {
        LinkedHashMap<String,Integer> l = new LinkedHashMap(16,0.75f,false);
    }
}

总结

代码都很简单,大家多多理解,下一篇博客见!

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

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

相关文章

Wnmp本地搭建结合内网穿透实现远程访问本地Wnmp服务

文章目录 前言1.Wnmp下载安装2.Wnmp设置3.安装cpolar内网穿透3.1 注册账号3.2 下载cpolar客户端3.3 登录cpolar web ui管理界面3.4 创建公网地址 4.固定公网地址访问 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xff0c; 通俗易懂&a…

智能优化算法应用:基于和声算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于和声算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于和声算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.和声算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…

分享86个焦点幻灯JS特效,总有一款适合您

分享86个焦点幻灯JS特效&#xff0c;总有一款适合您 86个焦点幻灯JS特效下载链接&#xff1a;https://pan.baidu.com/s/1Gm2jwN_AAF9QjFzQ9bCM_g?pwd6666 提取码&#xff1a;6666 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;…

OWASP SAMM 软件保障成熟度模型

软件保障成熟度模型 我们的使命是为您提供一种有效且可衡量的方式来分析和改进您的安全开发生命周期。 SAMM 支持完整的软件生命周期&#xff0c;并且与技术和流程无关。我们构建的 SAMM 本质上是不断发展和风险驱动的&#xff0c;因为没有一种单一的配方适用于所有组织。奥瓦…

无公网IP环境固定地址远程SSH访问本地树莓派Raspberry Pi

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《Linux》《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;…

「Swift」取消UITableView起始位置在状态栏下方开始

前言&#xff1a;在写页面UI时发现&#xff0c;当隐藏了NavigationBar时&#xff0c;即使UITableView是从(0,0)进行布局&#xff0c;也会一直在手机状态栏下方进行展示布局&#xff0c;而我的想法是希望UITableView可以从状态栏处就进行展示布局 当前页面展示&#xff1a; 问题…

适合学生备考的护眼台灯有哪些?五款公认优质台灯推荐

根据近两年的卫计委数据统计&#xff0c;我国的近视率全球第一。其中小学生平均近视率36%&#xff0c;初中平均近视率71.6%&#xff0c;高中生平均近视率81%。看到这些数据真让作为家长的我们触目惊心。 而这里面&#xff0c;先天的遗传近视并不多&#xff0c;很多的学生近视都…

leecode | 从二叉搜索树到更大和树

官方的题目解释永远晦涩难懂 这就是最大的拦路虎 简单介绍&#xff0c;将二叉搜索树&#xff0c;转换成“更大和树”&#xff0c;“最大的和树”&#xff0c;就是更新节点val&#xff0c;二叉树中所有大于等于该节点的的val 总和&#xff0c;包括本身 #对着图看&#xff0c;会更…

【CANN训练营】ROS2系统及使用介绍

ROS2系统及使用介绍 ROS2介绍及特点介绍 ROS2简介 要说ROS2&#xff0c;那就不得不提起ROS&#xff0c;ROS就是机器人操作系统英文全称(Robot Operating System)&#xff0c;但ROS本身并不是一个操作系统&#xff0c;而是可以安装在现在已有的操作系统上(Linux、Windows、Ma…

自动化测试的4大注意事项

自动化测试能够提高测试效率、覆盖率&#xff0c;降低测试成本和工作量&#xff0c;是软件开发中不可或缺的一部分。但前提是要确保自动化测试的有效性和可靠性&#xff0c;否则无效或错误的自动化测试&#xff0c;往往会对项目造成负面影响&#xff0c;如维护成本高、假阳性和…

面试必会-JAVA基础篇-01

文章目录 1. Final 有什么用&#xff1f;2. 什么是重载&#xff08;Overload&#xff09;和重写&#xff08;Override&#xff09; ?3. 重载的方法能否根据返回类型进行区分&#xff1f;4. 和 equals 的区别是什么5. 什么是反射机制&#xff1f;6. 反射机制优缺点7. 在你进行…

揭秘MySQL索引世界:概念、分类、应用场景一网打尽

一、索引概念 MySQL索引是一种用于提高数据库查询性能的数据结构。它允许数据库系统更有效地检索数据行&#xff0c;减少了在大型数据集中搜索特定数据的时间。索引的作用类似于书籍的目录&#xff0c;通过提供关键字与实际数据位置之间的映射&#xff0c;加速对数据库表中数据…

vue.js el-table 动态单元格列合并

一、业务需求&#xff1a; 一个展示列表&#xff0c;表格中有一部分列是根据后端接口动态展示&#xff0c;对于不同类型的数据展示效果不一样。如果接口返回数据是’类型1‘的&#xff0c;则正常展示&#xff0c;如果是’类型2‘的数据&#xff0c;则合并当前数据的动态表格。…

中国信通院公布2023下半年“可信数据库”测试结果

什么是可信数据库&#xff1f;定义具有强制和自主访问控制、审计、数据完整性、身份识别和鉴别、主客体分离等功能的数据库系统。是经过中国信通院评测的数据库产品及周边工具、数据库服务商和应用侧为评价目标的权威评测体系。 该体系包括基础能力、安全、性能、稳定性、服务商…

外包干了4年,技术退步太明显了。。。。。

先说一下自己的情况&#xff0c;本科生生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年国庆&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测…

基于HTML、CSS、JavaScript的网页设计

一、网页界面效果&#xff1a; 二、HTML代码&#xff1a; <!DOCTYPE html> <!-- 声明文档类型--> <html lang"en"> …

go自定义端口监听停用-------解决端口被占用的问题

代码 package mainimport ("fmt""log""net""os/exec""strconv""strings" )func getSelect(beign int, end int) int {var num intfor {_, err : fmt.Scan(&num)if err ! nil {fmt.Println("输入错误&am…

BUUCTF 小易的U盘 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 小易的U盘中了一个奇怪的病毒&#xff0c;电脑中莫名其妙会多出来东西。小易重装了系统&#xff0c;把U盘送到了攻防实验室&#xff0c;希望借各位的知识分析出里面有啥。请大家加油噢&#xff0c;不过他特别关照&a…

Unity加载配置文件【解析Json】

Json 文件 Json文件的存储&#xff1a; 存储在StreamingAssets目录下的&#xff1a;//这里用了游戏配置表常用的Json存储格式-对象数组 {"data":[{"id": 1001,"name": "ScreenFront_1",},{"id": 1002,"name": &…

企业计算机服务器中了Mallox勒索病毒如何解密,Mallox勒索病毒数据恢复

随着计算机技术的不断应用与发展&#xff0c;网络为企业的生产运营提供了极大帮助&#xff0c;越来越多的企业开始利用网络办公&#xff0c;因此&#xff0c;随之而来的网络安全威胁也在不断增加。近期&#xff0c;云天数据恢复中心陆续接到很多企业的求助&#xff0c;企业的计…