并查集LRUCache

news2024/11/29 22:45:32

文章目录

  • 并查集
    • 1.概念
    • 2. 实现
  • LRUCache
    • 1. 概念
    • 2. 实现
      • 使用标准库实现
      • 自主实现


并查集

1.概念

并查集是一个类似于森林的数据结构,并、查、集指的是多个不相干的集合直接的合并和查找,并查集使用于N个集合。适用于将多个元素分成多个集合,在分成集合的过程中要反复查询某两个元素是否存在于同一个集合的场景

比如说:总共有10个人要去执行任务,他们想进行组队,他们的编号从0到9,入下图:假设-1表示队伍里存在一人。

在这里插入图片描述

他们组成了3个队伍, 0 , 6 , 7 , 8 {0,6,7,8} 0,6,7,8 一队, 0 0 0是队长,组队这个操作其实就是进行了合并。

在这里插入图片描述

上图的关系可以用数组来表示:

  1. 数组的下标对应集合中元素的编号
  2. 数组中如果为负数,负号代表根,数字代表该集合中元素个数
  3. 数组中如果为非负数,代表该元素双亲在数组中的下标

从图可以看出,0下标就是一个根节点(队长),他有三个子节点(队员),同样的下标1和小标2都是一个根节点,它们都有两个子节点。

在这里插入图片描述

2. 实现

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

    public UnionFind(int size) {
        this.elem = new int[size];
        Arrays.fill(elem,-1);
    }

    /**
     * 查找x的根
     * @param x
     * @return
     */
    public int findRoot(int x) {
        if (elem[x] == -1) {
            return x;
        }
        while (elem[x] >= 0) {
            x = elem[x];
        }
        return x;
    }

    /**
     * 判断两个元素是否在同一个集合
     * @param x
     * @param y
     * @return
     */
    public boolean isSameSet(int x, int y) {
        int xRoot = findRoot(x);
        int yRoot = findRoot(y);
        if (xRoot == yRoot) {
            return true;
        }
        return false;
    }

    /**
     * 合并x和y,x和y必须是根节点,先查找根
     * @param x
     * @param y
     * @return
     */
    public void union(int x,int y) {
        int xRoot = findRoot(x);
        int yRoot = findRoot(y);
        if (xRoot ==  yRoot) {
            // 在同一个集合的元素无需合并
            return;
        }
        elem[xRoot] += elem[yRoot];
        elem[yRoot] = xRoot;
    }

    /**
     * 获取并查集中有多少集合
     * @return
     */
    public int getCount() {
        int count = 0;
        for (int tmp : elem) {
            if (tmp < 0) {
                count++;
            }
        }
        return count;
    }

}

LRUCache

1. 概念

LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法 ,LRUCache其实就是淘汰最近最少使用的缓存的一种数据结构,Cache的容量有限,因此当Cache的容量用完后,而又有新的内容需要添加进来时, 就需要挑选并舍弃原有的部分内容,从而腾出空间来放新内容。LRU Cache 的替换原则就是将最近最少使用的内容替换掉

2. 实现

实现高效的LRUCache可以使用哈希表+双向链表来实现。哈希表的增删改查都是 O ( 1 ) O(1) O(1),而双向链表的任意位置的删除也是 O ( 1 ) O(1) O(1)

JDK中类似LRUCahe的数据结构LinkedHashMap

LinkedHashMap和HashMap有点类似,LinkedHashMap也是使用数组+链表实现,但LinkedHashMap使用的是双向链表且维护了一个头节点和尾巴节点,同时保证了插入顺序,也可以动态调整元素顺序。

LinkedHashMap的构造方法参数:

  • initialCapacity:初始容量大小,使用无参构造方法时,此值默认是16
  • loadFactor:加载因子,使用无参构造方法时,此值默认是 0.75f
  • access Order:false: 基于插入顺序 true: 基于访问顺序

如果accessOrder为false表示按照插入顺序进行排序,如果为true表示按照访问顺序来排序,假设访问元素全部插入后,访问了1获取了张三,那么张三就会被放到末尾去,反之设置为false则不会进行调整。

public static void main(String[] args) {
    Map<Integer,String> map = new LinkedHashMap<>(16,0.75f,false);
    map.put(1,"张三");
    map.put(2,"李四");
    map.put(3,"王五");
    map.put(4,"张六");
    map.put(5,"李七");
    System.out.println(map);

    map.get(2);
    map.get(1);
    map.put(4,"里");
    System.out.println(map);

}

运行结果

{1=张三, 2=李四, 3=王五, 4=张六, 5=李七}
调用后
{1=张三, 2=李四, 3=王五, 4=, 5=李七}

假设把LinkedHashMap的参数改为true

public static void main(String[] args) {
    Map<Integer,String> map = new LinkedHashMap<>(16,0.75f,true);
    map.put(1,"张三");
    map.put(2,"李四");
    map.put(3,"王五");
    map.put(4,"张六");
    map.put(5,"李七");
    System.out.println(map);
    System.out.println("调用后");
    map.get(2);
    map.get(1);
    map.put(4,"里");
    System.out.println(map);

}

运行结果

{1=张三, 2=李四, 3=王五, 4=张六, 5=李七}
调用后
{3=王五, 5=李七, 2=李四, 1=张三, 4=}

使用标准库实现

继承LinkedHashMap重写关键方法removeEldestEntry即可实现LRUCache,removeEldestEntry方法默认返回false,返回false表示LinkedHashMap没有容量限制,如果返回true表示如果元素超过了capacity就会抛弃最旧元素,最旧元素可能是最先插入的元素,也可能是插入后一直没有使用的元素也是最旧元素。

public class MyLRUCache extends LinkedHashMap<Integer,Integer> {
    private int capacity;

    /**
     *super(容量,负载因子,基于插入顺序/基于访问顺序);
     * @param capacity
     */
    public MyLRUCache(int capacity) {
        super(capacity,0.75f,true);
        this.capacity = capacity;
    }

    public int get(int key) {
        Integer ret = super.get(key);
        if (ret == null) {
            return -1;
        }
        return super.get(key);
    }



    /**
     * 重写关键方法,默认放回false,重写后如LRU中元素大于指定容量capacity就会删除最旧元素
     * @param eldest
     * @return
     */
    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > capacity;
    }


    public void put(int key, int value) {
        super.put(key, value);
    }

    public static void main1(String[] args) {
        Map<Integer,String> map = new LinkedHashMap<>(16,0.75f,true);
        map.put(1,"张三");
        map.put(2,"李四");
        map.put(3,"王五");
        map.put(4,"张六");
        map.put(5,"李七");
        System.out.println(map);
        System.out.println("调用后");
        map.get(2);
        map.get(1);
        map.put(4,"里");
        System.out.println(map);

    }
    public static void main(String[] args) {
        MyLRUCache lruCache = new MyLRUCache(4);
        lruCache.put(1,0);
        lruCache.put(2,0);
        lruCache.put(3,0);
        lruCache.put(4,0);
        lruCache.put(5,0);
        System.out.println(lruCache.get(1));
        System.out.println(lruCache);
    }

}

自主实现

自主实现一个LRU使用标准库中的HashMap和手写的带头尾傀儡节点的双向链表来实现。

  • HashMap的key存放的是存入的键值key,value是一个双向链表的节点
  • 双向链表节点属性有存放的键(key)和值(value),前驱节点和后继节点
  • LRU中有两个傀儡节点保证插入的顺序,每次插入元素都插入到tail节点前面
  • 每次元素达到最大缓存容量时,默认丢失head节点的后一个元素
  • 没一次get或者put,也就是获取或者更新元素,都把该元素移动到末尾,也就是tail前面

在这里插入图片描述

使用元素移动

在这里插入图片描述

public class LRUCache {
    static class Node {
        int key;
        int val;
        Node prev;
        Node next;
        public Node() {

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

        @Override
        public String toString() {
            return "Node{" +
                    "key=" + key +
                    ", val=" + val +
                    '}';
        }
    }
    // 最大容量
    private int capacity;
    // 头尾傀儡节点
    private Node head;
    private Node tail;
    private Map<Integer,Node> map;
    // 链表有效元素个数
    private int size;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.map = new HashMap<>();
        this.head = new Node();
        this.tail = new Node();
        this.head.next = tail;
        this.tail.prev = head;
    }

    /**
     * 从LRUCache中获取元素
     * @param key
     * @return
     */
    public int get(int key) {
        Node node = map.get(key);
        if (node != null) {
            // 把node放到链表尾部
            moveNodeIsTail(node);
            return node.val;
        }
        // 不存在返回-1
        return -1;
    }

    /**
     * 将node节点移动到链表尾部
     * @param node
     */
    private void moveNodeIsTail(Node node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;

        tail.prev.next = node;
        node.prev = tail.prev;
        node.next = tail;
        tail.prev = node;
    }

    /**
     * 添加元素到LRUCache中
     * @param key
     * @param val
     */
    public void put(int key,int val) {
        Node tmp = map.get(key);

        if (tmp == null) {
            // 不存在就创建节点添加到尾部
            Node node = new Node(key,val);
            map.put(key,node);
            tail.prev.next = node;
            node.prev = tail.prev;
            node.next = tail;
            tail.prev = node;
            this.size++;
            // 判断是否超过容量capacity
            if (this.size > capacity) {
                // 丢弃链表第一个元素(长时间未使用元素)
                Node cur = head.next;
                cur.next.prev = head;
                head.next = cur.next;
                map.remove(cur.key);
            }
        } else {
            // 存在就直接更新值并放到尾部
            tmp.val = val;
            moveNodeIsTail(tmp);
        }
    }

    public void print() {
        Node cur = head.next;
        while (cur != tail) {
            System.out.print(cur+" ");
            cur = cur.next;
        }
        System.out.println();
    }
}


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

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

相关文章

Python分支结构和循环结构

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 一.分支结构 分支结构是根据判断条件结果而选择不同向前路径的运行方式&#xff0c;分支结构分为&#xff1a;单分支&#xff0c;二分支和多分支。 1&#xff0…

No154.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

关于Java的IO流里面的方法read()的细究

我们会经常看到上面的代码&#xff0c;很显然read(b)&#xff0c;是把字节读入到b这个字节数组里&#xff0c;然后read()返回一个成功读取的字节长度&#xff0c;如果现在用不带参的read()去一个一个字节的读了&#xff1f;那么len bInput.read()就是把这一个节字读入到len&am…

消息队列实现进程间通信

write.c #include<myhead.h>typedef struct {long msgtype; //消息类型char data[1024]; //消息正文 }Msg_s;#define SIZE sizeof(Msg_s)-sizeof(long) //消息正文的大小int main(int argc, const char *argv[]) {key_t key; //定义一个键值if((key ftok("./&q…

【每日一题】种花问题

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;贪心 写在最后 Tag 【贪心】【数组】【2023-09-29】 题目来源 605. 种花问题 题目解读 种花要种在空地&#xff08;没有种过花的位置&#xff09;上&#xff0c;花不能种在相邻位置上否则会抢夺水资源无法正常生长&a…

统计模型----决策树

决策树 &#xff08;1&#xff09;决策树是一种基本分类与回归方法。它的关键在于如何构建这样一棵树。决策树的建立过程中&#xff0c;使用基尼系数来评估节点的纯度和划分的效果。基尼系数是用来度量一个数据集的不确定性的指标&#xff0c;其数值越小表示数据集的纯度越高。…

管理与系统思维

技术管理者不仅仅需要做事情&#xff0c;还需要以系统思维的方式推动组织变革&#xff0c;从而帮助团队和个人做到更好。原文: Management and Systems Thinking 图片来源: Dall-E "除非管理者考虑到组织的系统性&#xff0c;否则大多数提高绩效的努力都将注定失败。"…

有效括号——括号的匹配

给定一个只包括 ‘(’&#xff0c;‘)’&#xff0c;‘{’&#xff0c;‘}’&#xff0c;‘[’&#xff0c;‘]’ 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括号都有…

C++ 迭代器(iterator)

迭代器介绍 迭代器&#xff08;iterator&#xff09;&#xff1a;容器类型内置的“指针” - 使用迭代器可以访问某个元素&#xff0c;迭代器也能从一个元素移动到另一个元素。 - 有迭代器的类型都拥有 begin 和 end 成员- begin&#xff1a;返回指向第一个元素&#xff08;或字…

LaTex的学习(学习于b站西北农林科技大学耿楠教授的教学视频)

目录 一、LaTeX软件的安装与环境配置  1.LaTeX软件texlive的下载  2. texlive的安装 二、用命令行实现LaTeX文档的编写  1.通过命令行演示LaTeX编写的过程  2.将编译LaTeX并生成pdf文件的过程封装成一个bat文件  3.演示一个含有中文的LaTeX文件 三、用TexStudio IDE实…

性格孤僻怎么办?改变性格孤僻的4种方法

性格孤僻是比较常见的说法&#xff0c;日常中我们说某人性格孤僻&#xff0c;意思就是这人不太合群&#xff0c;喜欢独来独往&#xff0c;话少&#xff0c;人际关系不太好&#xff0c;其言行往往不符合大众的价值观。从性格孤僻的角度来看&#xff0c;可能跟很多种心理疾病存在…

布隆过滤器Bloom Filter

本章代码gitee仓库&#xff1a;布隆过滤器 文章目录 0. 前言1. 布隆过滤器的概念2. 布隆过滤器的实现2.1 哈希函数2.2 插入和判断 3. 布隆过滤器的删除4. 布隆过滤器的误判 0. 前言 我们在玩某款游戏的时候&#xff0c;刚注册的话&#xff0c;我们需要取一个昵称&#xff0c;这…

Kafka:安装与简单使用

文章目录 下载安装windows安装目录结构启动服务器创建主题发送一些消息启动消费者设置多代理集群常见问题 工具kafka tool 常用指令topic查看topic删除topic 常见问题参考文献 下载安装 下载地址&#xff1a;kafka-download windows安装 下载完后&#xff0c;找一个目录解压…

inndy_echo

inndy_echo Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)32位&#xff0c;只开了NX int __cdecl __noreturn main(int argc, const char **argv, const char **envp) {char s; // [espCh…

微信小程序引入字体在部分机型失效不兼容解决办法

写小程序页面&#xff0c;美工作图用了特殊字体 引入代码&#xff1a; font-face {font-family: huxiaobo;src: url("https://xxxxxxxx.top/assets/fonts/huxiaobonanshenti.woff") } .font-loaded {font-family: "huxiaobo"; } 上线后发现部分安卓机型不…

Java下Properties类的使用(写出和读入)

代码如下&#xff1a; public class MyWork {public static void main(String[] args) throws IOException {Properties pro1 new Properties();pro1.setProperty("0001","张三");pro1.setProperty("0002","李四");pro1.setProperty…

1600*E. Kolya and Movie Theatre(贪心优先队列规律)

Kolya and Movie Theatre - 洛谷 Problem - 1862E - Codeforces 解析&#xff1a; 可以观察到每次看电影所减少的舒畅值都和前一次有关&#xff0c;这样的话&#xff0c;可以发现减少的 d 只和最后一次看电影的时间有关。 所以枚举最后一次看电影的时间&#xff0c;并且维护一…

iOS设备管理器iMazing比iTunes好用吗?有哪些优势

虽然 iTunes 是 Apple 官方指定的 iPhone 数据备份和管理工具&#xff0c;但是一直以来 iTunes 卡顿的使用体验和过慢的备份过程为不少人诟病。如果大家也被 iTunes 体验不佳的备份和管理功能所困扰&#xff0c;那么简单易用、功能强大的iMazing 能为你解决这个问题。 iMazing…

java web+Mysql e-life智能生活小区物业管理系统

本项目为本人自己书写&#xff0c;主要服务小区业主和管理人员。 e-life智能生活小区涉及多个方面的智能化和便利化服务&#xff1a; 1. 用户模块&#xff1a;包含基本的登入登出操作&#xff0c;查看个人信息中用户可以查看 自己的个人资料但不可以修改个人信息。 a) 用户…

从零开始学习 Java:简单易懂的入门指南之IO字节流(三十)

IO流之字节流 1. IO概述1.1 什么是IO1.2 IO的分类1.3 IO的流向说明图解1.4 顶级父类们 2. 字节流2.1 一切皆为字节2.2 字节输出流【OutputStream】2.3 FileOutputStream类构造方法写出字节数据数据追加续写写出换行 2.4 字节输入流【InputStream】2.5 FileInputStream类构造方法…