数据结构 - Set 与 Map 接口介绍(TreeMap,HashMap,TreeSet,HashSet类)

news2024/9/23 1:57:36

文章目录

  • 前言
  • 1. Set / Map接口
  • 2. TreeSet类
  • 3. TreeMap 类
  • 4. HashSet 与 HashMap
    • 4.1 HashSet / HashMap 底层哈希表
    • 4.2 解决哈希冲突
  • 总结

✨✨✨学习的道路很枯燥,希望我们能并肩走下来!

编程真是一件很奇妙的东西。你只是浅尝辄止,那么只会觉得枯燥乏味,像对待任务似的应付它。但你如果深入探索,就会发现其中的奇妙,了解许多所不知道的原理。知识的力量让你沉醉,甘愿深陷其中并发现宝藏。


前言

本篇是介绍数据结构Set与Map接口,了解TreeMap,HashMap,TreeSet,HashSet类;如有错误,请在评论区指正,让我们一起交流,共同进步!


本文开始

1. Set / Map接口

Set接口继承Collection,Map是独立的接口
进一步认识TreeSet和TreeMap就需要知道底层搜索树是如何查找,插入,删除的;
红黑树过于复杂,先认识一下简单的二叉搜索树,为以后学习做一个铺垫;

简单二叉搜索树特点:
左孩子 < 根节点,右孩子 > 根节点;

简单二叉搜索树查找代码:

//查找key是否存在: 二叉搜索树特点:左孩子小于根节点,右孩子大于根节点
    public TreeNode search(int key) {
        //重新定义访问的头节点
        TreeNode cur = root;
        //遍历二叉树,查找值
        while (cur != null) {
            if(cur.key == key) {
                return cur;
            }else if(cur.key < key) {
                //节点值大于查找值
                cur = cur.right;
            }else {
                //节点值小于查找值
                cur = cur.left;
            }
        }
        return null;//没找到
    }

简单二叉搜索树插入代码

    //定义根节点
    public TreeNode root;
    //插入一个元素:发现插入二叉搜索树的元素都会插入叶子节点
    public boolean insert(int key) {
   		 //空树直接插入
        if(root == null) {
            root = new TreeNode(key);
            return true;
        }
        TreeNode cur = root;
        TreeNode parent = null;
        //遍历, 并记录父节点位置;
        //真正找到插入位置,cur==null无法插入,就需要记录它的父节点
        while (cur != null) {
            if(cur.key < key) {
                //先记录父节点
                parent = cur;
                cur = cur.right;
            }else if(cur.key == key) {
                //插入一样的直接返回
                return false;
            }else {
                 //cur.key > key
                parent = cur;
                cur = cur.left;
            }
        }
        //找到位置,判断在父节点左还是右
        if(parent.key < key) {
            parent.right = new TreeNode(key);
        }else {
            parent.left = new TreeNode(key);
        }
        return true;
    }
 

简单二叉搜索树删除代码:
删除的7种情况:
删除节点左为空:是否为根节点;不为根节点,是父节点的左孩子还是右孩子(3种)
删除节点右为空:是否为根节点;不为根节点,是父节点的左孩子还是右孩子(3种)
删除节点左右都不为空:替换法,右树最小值与删除值替换或者左树最大值替换;最后删除替换的值;(1种)

 //删除key的值
    public boolean remove(int key) {
        TreeNode cur = root;
        TreeNode parent = null;//记录父节点,删除的情况复杂需要判断
        //先找到删除节点位置
        while (cur != null) {
            if(cur.key < key) {
                parent = cur;
                cur = cur.right;
            }else if(cur.key == key) {
                removeNode(parent,cur);
                return true;
            }else {
                parent = cur;
                cur = cur.left;
            }
        }
        return false;
    }
    //cur: 为删除节点位置,parent:为删除节点的父节点位置
    private void removeNode(TreeNode parent, TreeNode cur) {
        //判断删除节点左右是否为空
        if(cur.left == null) {
            //左为空,在判断的是否为根节点
            if(cur.key == root.key) {
                //删除节点为根节点,直接移动根节点即可
                root = cur.right;
            }else if(cur == parent.left) {
                //删除节点不为根,为父节点的左位置
                parent.left = cur.right;
            }else {
                //删除节点不为根,为父节点的右位置
                parent.right = cur.right;
            }
        }else if(cur.right == null) {
            //右为空,在判断的是否为根节点
            if(cur.key == root.key) {
                //删除节点为根节点,直接移动根节点即可
                root = cur.left;
            }else if(cur == parent.left) {
                //删除节点不为根,为父节点的左位置
                parent.left = cur.left;
            }else {
                //删除节点不为根,为父节点的右位置
                parent.right = cur.left;
            }
        }else {
            //删除节点左右孩子都不为空
            //使用替换法,记录该位置tarP,在其右子树找到最小值与删除值替换
            TreeNode tar = cur.right;
            TreeNode tarP = cur;
            //找右树最小值
            while (tar.left != null) {
                tarP = tar;
                tar = tar.left;
            }
            //替换
            cur.key = tar.key;
            //替换后删除tar位置
            if(tarP.left == tar) {
                tarP.left = tar.right;
            }else {
                //可能没有左子树位置
                tarP.right = tar.right;
            }
        }
    }

在这里插入图片描述

2. TreeSet类

TreeSet是Set接口的实现类,它的底层是一颗搜索树(红黑树),红黑树是一颗高度平衡的二叉搜索树;
时间复杂度:O(logn); - 这里是以2为底的
特点:只能存储key, 重复的值集合中不添加

Set 集合方法 代码测试:

public static void main(String[] args) {
        Set<Integer> set = new TreeSet<>();
        //Set里增加 key - 且不重复添加
        set.add(1);
        set.add(2);
        set.add(1);
        set.add(3);
        System.out.println(set);
        //判断Set集合中是否存在key
        if(set.contains(2)) {
            System.out.println("存在");
        }
        //删除集合中的值 - key
        set.remove(1);
        System.out.println(set);
        //set集合是否为空
        System.out.println(set.isEmpty());
        //判断set集合大小
        System.out.println(set.size());
        //判断集合中的元素是否在set中全部存在
        List<Integer> list = new ArrayList<>();
        list.add(3);
        list.add(2);
        System.out.println(set.containsAll(list));
        //将集合添加到set去重
        List<Integer> list2 = new ArrayList<>();
        list2.add(4);
        list2.add(4);
        set.addAll(list2);
        System.out.println(set);
        //迭代器打印set集合
        Iterator<Integer> it = set.iterator();
        while (it.hasNext()) {
            System.out.print(it.next() + " ");
        }
        System.out.println();
    }

3. TreeMap 类

TreeMap : 底层也是搜索树 - 红黑树;
时间复杂度:O(logn)
特点:存储 key-val, key值唯一且不为空null,val值可替换;key-val可用null值
Map集合的常用方法:
【注】 map中的entrySet() =》每个元素类型 Entry<T,V> entry
entrySet获得所有键值对,可用getKey,getValue获取键或值;

public static void main(String[] args) {
        Map<String,Integer> map = new TreeMap<>();
        //Map集合中增加值
        map.put("a",1);
        map.put("ab",3);
        //map.put("ab",4);
        map.put("abc",2);
        map.put("abd",2);
        //Map集合根据key,获得val
        System.out.println(map.get("a"));
        //key不存在,返回默认值
        System.out.println(map.getOrDefault("c", 666));
        //map集合中的keySet(): 返回集合中所有不重复的key
        for (String s : map.keySet()) {
            System.out.print(s + " ");
        }
        System.out.println();
        //s
        // map.values将集合中的val返回 - val可重复
        for (int x : map.values()) {
            System.out.print(x + " ");
        }
        System.out.println();
        //entrySet() - 返回集合中的所有key-val
        for (Map.Entry<String,Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + "---" + entry.getValue());
        }
        //移除map集合中的值,返回被移除的值val
        System.out.println(map.remove("ab"));
        //判断是否包含key
        System.out.println(map.containsKey("abc"));
    }

4. HashSet 与 HashMap

4.1 HashSet / HashMap 底层哈希表

HashSet / HashMap: 底层是哈希表一种数组 + 链表 + 红黑树的结构
时间复杂度:O(1)
哈希表特点:数组中存储每个链表的头节点,根据源码,当链表长度超过8,数组长度超过64,链表会变成红黑树;
哈希表:在数组中插入元素,需要根据得到的哈希函数得到在数组中的存储地址存储元素,例如:存储元素 / 数组长度 = 数组中存储地址;

计算哈希地址,可能得到一样的地址,就会产生哈希冲突 <=>地址冲突(地址一样)

接下来看一下如何解决?

4.2 解决哈希冲突

方式一:闭散列(开地址法)
①线性探测:
存入数组的数字为3,33,根据 哈希函数(index = 插入的数字 % 数组长度) 得到的余数下标插入,假设长度为10,3插在3位置,33根据哈希函数得到的位置也是3位置,在3位置之后依次寻找没有插入的位置插入即可(3位置后面有地方就插入,没有就接着往后找);

在这里插入图片描述

②二次探测:
当根据哈希函数找到地址一样时(哈希冲突),找下一个地址根据Hi = (H0 + i^2) % len; (i = 1,2,3…), Hi => 下一个地址,H0 => 根据哈希函数计算得到的位置,len: 数组长度 ;

在这里插入图片描述

方式二:开散列(链地址法)
哈希表:一个数组,数组中每个元素存储单链表的头节点(每个元素都是链表);

代码实现简单的哈希桶:实现put(插入),get(获取)方法

public class HashBucket {
    //数组每个元素为链表,链表每个元素为节点 =》定义节点
    private static class Node {
        private int key;
        private int value;
        Node next;

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

    private Node[]  array;
    private int size;   // 当前的数据个数
    private static final double LOAD_FACTOR = 0.75;//默认的负载因子,不能超过0.75
    private static final int DEFAULT_SIZE = 8;//默认桶的大小,扩容时需要

    public int put(int key, int value) {
        //获取插入的地址:根据哈希函数计算
        //除留余数法
        int index = key % array.length;
        //找到位置,遍历该位置链表,元素是否插入过
        Node cur = array[index];//重头遍历
        while (cur != null) {
            //判断元素是否一样
            if(cur.key == key) {
                //找到,就更新
                cur.value = value;
                return cur.value;
            }
            cur = cur.next;
        }
        //没找到一样的,头插元素
        Node node = new Node(key,value);
        node.next = array[index];
        array[index] = node;//移动头节点
        size++;//计数+1
        //判断是否需要扩容: 超过负载因子需要扩容
        if(loadFactor() > LOAD_FACTOR) {
            resize();
        }
        return cur.value;
    }


    private void resize() {
        //扩容时数组长度改变,会产生新的插入位置,所以原数组中所有元素需要重新插入
        //扩容
        Node[] newArr = new Node[2*array.length];
        //遍历原来数组
        for (int i = 0; i < array.length; i++) {
            Node cur = array[i];//每个链表的头节点
            //就地址更改进新数组
            while (cur != null) {
                //记录下一个节点位置
                Node curNext = cur.next;
                //获取插入新数组中的地址
                int index = cur.key % newArr.length;
                //重新头插
                cur.next = newArr[index];
                newArr[index] = cur;
                cur = curNext;
            }
        }
        array = newArr;//新数组给旧数组,扩容完毕
    }

    //计数当前负载因子:数组中元素个数 / 数组长度;
    private double loadFactor() {
        return size * 1.0 / array.length;
    }


    public HashBucket() {
        //初始化数组可以存储几个元素
        Node[] array = new Node[10];
    }

    public int get(int key) {
        //获取得到节点下标
        int index = key % array.length;
        Node cur = array[index];
        while (cur != null) {
            //判断是否得到节点
            if(cur.key == key) {
                //得到返回值
                return cur.value;
            }
            cur = cur.next;
        }
        return -1;//没找到
    }
}


总结

✨✨✨各位读友,本篇分享到内容如果对你有帮助给个👍赞鼓励一下吧!!
感谢每一位一起走到这的伙伴,我们可以一起交流进步!!!一起加油吧!!!

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

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

相关文章

神经网络基础知识

神经网络基础知识 文章目录神经网络基础知识一、人工神经网络1.激活函数sigmod函数Tanh函数Leaky Relu函数分析2.过拟合和欠拟合二、学习与感知机1.损失函数与代价函数2. 线性回归和逻辑回归3. 监督学习与无监督学习三、优化1.梯度下降法2.随机梯度下降法(SGD)3. 批量梯度下降法…

JavaScript系列之new运算符

文章の目录一、什么是new&#xff1f;二、new经历了什么过程&#xff1f;三、new的过程分析四、其他作用参考写在最后一、什么是new&#xff1f; 众所周知&#xff0c;在JS中&#xff0c;new的作用是通过构造函数来创建一个实例对象。 像下面这样&#xff1a;&#xff08;和普…

Centos篇-Centos Minimal安装

安装Centos Minimal 下载镜像 由于使用Centos主要是安装K8s以及使用K8s或者docker安装各种服务&#xff0c;可以理解为就是单纯的服务器使用&#xff0c;所以不需要GUI&#xff0c;直接使用Centos的Server版本。 所以选择centos的minimal版本进行下载&#xff1a; 地址&#…

FreeRTOS队列集、事件标志组 | FreeRTOS十一

目录 说明&#xff1a; 一、队列集 1.1、队列集简介 1.2、队列集作用 二、队列集相关API函数 2.1、创建队列集函数 2.2、往队列集添加队列函数 2.3、队列集移除队列函数 2.4、获取队列集中有有效队列 三、事件标志组 3.1、什么是事件标志组 3.2、事件标志组的特点 …

Matlab傅里叶谱方法求解二维波动方程

傅里叶谱方法求解基本偏微分方程—二维波动方程 二维波动方程 将一维波动方程中的一维无界弦自由振动方程推广到二维空间上, 就得到了描述无界 (−∞<x,y<∞)(-\infty<x, y<\infty)(−∞<x,y<∞) 弹性薄膜的波动方程: ∂2u∂t2a2(∂2∂x2∂2∂y2)u(1)\frac…

HTML img和video object-fit 属性

简介 Css中object-fit主要是应用到img标签和Video标签的&#xff0c;来控制显示缩放效果的。 首先我们存在一张图片&#xff0c;原始图片的尺寸是 1080px x 600px, 展示效果如下&#xff1a; 如果我们的css样式中的img大小设定并不能满足图片的原始大小&#xff0c;比如我们的…

Syzkaller学习笔记---更新syz-extract/syz-sysgen(一)

Syzkaller学习笔记Syzkaller 安装文件系统内核Android common kernel参考文献syzkaller 源码阅读笔记-1前言syz-extractmainarchListcreateArchesworkerprocessArchprocessFileextractcheckUnsupportedCallsarchList小结syz-sysgenmainprocessJob()generateExecutorSyscalls()w…

Linux ALSA 之十一:ALSA ASOC Path 完整路径追踪

ALSA ASOC Path 完整路径追踪一、ASoc Path 简介二、ASoc Path 完整路径2.1 tinymix 设置2.2 完整路径 route一、ASoc Path 简介 如前面小节所描述&#xff0c;ASoc 中 Machine Driver 是 platform driver 和 codec driver 的粘合剂&#xff0c;audio path 离不开 FE/BE/DAI l…

绕过Nginx Host限制

目录绕过Nginx Host限制SNI第三种方法&#xff1a;总结绕过Nginx Host限制 SNI SNI&#xff08;Server Name Indication&#xff09;是 TLS 的扩展&#xff0c;这允许在握手过程开始时通过客户端告诉它正在连接的服务器的主机名称。 作用&#xff1a;用来解决一个服务器拥有…

机器视觉 多模态学习11篇经典论文代码以及解读

此处整理了深度学习&#xff0d;机器视觉&#xff0c;最新的发展方向&#xff0d;多模态学习&#xff0c;中的11篇经典论文&#xff0c;整理了相关解读博客和对应的Github代码&#xff0c;看完此系列论文和博客&#xff0c;相信你能快速切入这个方向。每篇论文、博客或代码都有…

【C++1】函数重载,类和对象,引用,string类,vector容器,类继承和多态,/socket,进程信号,public,ooci

文章目录1.函数重载&#xff1a;writetofile()&#xff0c;Ctrue和false&#xff0c;C0和非02.类和对象&#xff1a;vprintf2.1 构造函数&#xff1a;对成员变量初始化2.2 析构函数&#xff1a;一个类只有一个&#xff0c;不允许被重载3.引用&#xff1a;C中&取地址&#x…

Elasticsearch在Windows系统下的安装

Elasticsearch在Windows系统下的安装Elasticsearch在Windows系统下的安装1、安装 Java 环境2、安装 Elasticsearch&#xff08;1&#xff09;下载 Elasticsearch 的 zip 安装包&#xff08;2&#xff09;下载安装包后解压文件&#xff08;3&#xff09;启动 Elasticsearch 服务…

C++多态(下)

大家好&#xff01;上一篇文章&#xff0c;主要是说了多态的概念和使用。这篇文章就会说一下多态的底层原理&#xff0c;如果对多态的使用和概念不清的可以看一下上篇文章(多态概念)。 文章目录1. 多态的原理1.1 虚函数表1.2 多态的原理1.3 动态绑定与静态绑定2. 多继承关系的…

第四章 MergeTree原理分析

一、存储结构 1.1 表引擎语法结构 CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster] (name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],...INDEX index_name1 expr1 TYPE type1(...) GRANULARITY va…

【遇见青山】项目难点:解决超卖问题

【遇见青山】项目难点&#xff1a;解决超卖问题1.乐观锁方案2.悲观锁方案1.乐观锁方案 原始实现下单功能的方法&#xff1a; /*** 秒杀实现** param voucherId 秒杀券的ID* return Result*/ Override Transactional public Result seckillVoucher(Long voucherId) {// 查询优…

备战蓝桥杯【高精度加法和高精度减法】

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…

IDEA中使用自定义的maven

步骤 1.下载maven maven下载 2.配置maven 2.1设置环境变量 1.在“环境环境“–“系统环境“–“path”中加入&#xff08;设置到bin目录下&#xff09; 2.测试环境变量是否成功 C:\Users>mvn -v //在控制台输入mav -v,看是否输出以下结果 Apache Maven 3.9.0 (9b…

《MySQL系列-InnoDB引擎23》文件-InnoDB存储引擎文件-重做日志文件

InnoDB存储引擎文件 之前介绍的文件都是MySQL数据库本身的文件&#xff0c;和存储引擎无关。除了这些文件外&#xff0c;每个表存储引擎都有其自己独有的文件。本节将具体介绍与InnoDB存储引擎密切相关的文件&#xff0c;这些文件包括重做日志文件、表空间文件。 重做日志文件…

Docker的资源控制管理

目录 一、CPU控制 1、设置CPU使用率上限 2、设置CPU资源占用比&#xff08;设置多个容器时才有效&#xff09; 3、设置容器绑定指定的CPU 二、对内存使用进行限制 1、创建指定物理内存的容器 2、创建指定物理内存和swap的容器 3、 对磁盘IO配额控制&#xff08;blkio&a…

使用Docker容器部署java运行环境(java8 + mysql5.7 + redis5.0 + nginx1.14.1

环境&#xff1a;阿里云ECS服务器一.Docker环境安装1.1 安装工具sudo yum install -y yum-utils device-mapper-persistent-data lvm21.2 为yum源添加docker仓库位置yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo1.3 将软件…