数据结构——Map和Set
1. 搜索树
1. 概念
2. 查找

3. 插入
插入步骤
1. 如果树为空,则新节点成为根节点。
2. 如果树不为空
-
从根节点开始,比较插入值与当前节点的值。
-
根据比较结果,决定向左子树或右子树移动。
-
重复上述过程,直到找到一个空位置(即当前节点的左子节点或右子节点为空)。
-
将新节点插入到该空位置。
4. 删除
-
找到右子树的最小节点(或左子树的最大节点)。
-
用最小节点的值替换被删除节点的值。
-
删除右子树的最小节点。
public class BinarySearchTree {
//创建内部类,作为节点static class Node{public int val;public Node left;public Node right;//构造方法,初始化节点public Node(int val){this.val = val;}}public Node root;//查找public boolean search(int data){if(root==null){return false;}Node cur = root;while(cur!=null){//如果大于data,右边寻找if(cur.val<data){cur = cur.right;}else if(cur.val>data){//小于data,左边寻找cur = cur.left;}else {return true;}}return false;}//插入public void insert(int data){Node node = new Node(data);//如果根节点为空if(root==null){root = node;//根节点为新节点return;}Node cur = root;Node parent = null;while(cur!=null){parent = cur;//存放父节点//向右子树移动if(cur.val<data){cur = cur.right;}else if(cur.val>data){//向左子树移动cur = cur.left;}else{return;//如果val已经存在,直接退出}}//将新节点插入到末尾空节点if(parent.val<data){parent.right = node;}else if(parent.val > data){parent.left = node;}}//删除public boolean remove(int data){if(root==null){return false;}Node cur = root;Node parent = null;//查找datawhile(cur!=null){if(cur.val<data){parent = cur;cur = cur.right;}else if(cur.val>data){parent = cur;cur = cur.left;}else {//删除delete(parent,cur);return true;}}return false;}private void delete(Node parent,Node cur){//如果cur左节点为空if(cur.left==null){//如果cur为根节点if(cur==root){root = cur.right;root.left=null;}//如果cur在左边if(parent.left==cur){parent.left = cur.right;}//如果cur在右边if(parent.right==cur){parent.right=cur.right;}}else if(cur.right==null){//cur右节点为空if(cur==root){root = cur.left;root.right=null;}if(parent.left==cur) {//在左边parent.left = cur.left;}if(parent.right==cur){//在右边parent.right = cur.right;}}else{//左右节点均不为空leftChild(parent,cur);//rightChild(parent,cur);}}//1.找到cur左节点的最右边节点private void leftChild(Node parent,Node cur){parent = cur;Node child = cur.left;while(child.right!=null){parent = child;child = child.right;}//与cur交换,再删除交换后的节点cur.val = child.val;//如果cur左节点没有右孩子if(parent.left==child){parent.left = child.left;}else{parent.right = child.left;}}//2.找到cur右节点的最左边节点private void rightChild(Node parent,Node cur){parent = cur;Node child = cur.right;//cur右节点的最左边节点while(child.left!=null){parent = child;child = child.left;}//与cur交换,再删除交换后的节点cur.val = child.val;//如果cur右节点没有左孩子if(parent.right==child){parent.right = child.right;}else{parent.left = child.right;}}
} 5. 性能分析
2. 搜索
1. 模型
3. Map 的使用
1. 关于Map.Entry<K, V>的说明
2. Map 的常⽤⽅法

注意:1. Map是⼀个接⼝,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者HashMap2. Map中存放键值对的Key是唯⼀的,value是可以重复的3. 在TreeMap中插⼊键值对时,key不能为空,否则就会抛NullPointerException异常,value可以为空。但是HashMap的key和value都可以为空。4. Map中的Key可以全部分离出来,存储到Set中来进⾏访问(因为Key不能重复)。5. Map中的value可以全部分离出来,存储在Collection的任何⼀个⼦集合中(value可能有重复)。6. Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进⾏重新插⼊。
import java.util.Collection;
import java.util.TreeMap;
import java.util.Map;
import java.util.Set;public class TestMap {public static void main(String[] args){Map<String,Integer> map = new TreeMap<>();TreeMap<String,Integer> treeMap = new TreeMap<>();//TreeMap重写了接口Map的所有方法map.put("the",3);map.put("early",5);map.put("bird",4);map.put("catches",7);map.put("the",6);//key已存在,可以改变key对应的value值map.put("worm",4);System.out.println(map.size());//返回此地图中键值映射的数量System.out.println(map.get("the"));//key存在,返回key对应的valueSystem.out.println(map.get("word"));//key不存在,返回nullSystem.out.println(map.getOrDefault("bird",0));//key存在,返回key对应的valueSystem.out.println(map.getOrDefault("time",0));//key不存在,返回自定义valueSystem.out.println(map.remove("the"));//删除key对应的映射关系,并返回key对应的value值System.out.println(map.remove("the"));//key不存在则返回nullSet<String> keyset = map.keySet();//将map中所有的key放入keyset中System.out.println(keyset);for(String s : map.keySet()){System.out.print(s+" ");}System.out.println();Collection<Integer> con =map.values();//将map中所有的value放入con中System.out.println(con);for(Integer cot:map.values()){System.out.print(cot+" ");}System.out.println();//将map中的所有映射关系放入entry中Set<Map.Entry<String,Integer>> entry = map.entrySet();System.out.println(entry);for(Map.Entry<String,Integer> entry1:map.entrySet()){System.out.println("key: "+entry1.getKey()+" ; value: "+entry1.getValue());}}
}
4. set 的使用
Set (Java Platform SE 8 )
1. set 的常用方法
1. Set是继承⾃Collection的⼀个接⼝类2. Set中只存储了key,并且要求key⼀定要唯⼀3. TreeSet的底层是使⽤Map来实现的,其使⽤key与Object的⼀个默认对象作为键值对插⼊到Map中的4. Set最⼤的功能就是对集合中的元素进⾏去重5. 实现Set接⼝的常⽤类有TreeSet和HashSet,还有⼀个LinkedHashSet,LinkedHashSet是在HashSet的基础上维护了⼀个双向链表来记录元素的插⼊次序。6. Set中的Key不能修改,如果要修改,先将原来的删除掉,然后再重新插⼊7. TreeSet中不能插⼊null的key,HashSet可以。
import java.util.*;public class TestSet {public static void main(String[] args) {Set<String> set = new TreeSet<>();TreeSet<String> treeSet = new TreeSet<>();//TreeSet重写了接口set的所有方法//为set添加元素System.out.println(set.add("the"));System.out.println(set.add("early"));System.out.println(set.add("bird"));System.out.println(set.add("catches"));System.out.println(set.add("the"));//key已存在,不添加System.out.println(set.add("worm"));System.out.println(set.size());//返回set中元素的个数System.out.println(set.isEmpty());//判断set是否为空,不为空返回false//判断元素是否存在System.out.println(set.contains("the"));//存在,trueSystem.out.println(set.contains("world"));//不存在,false//删除set中指定的元素System.out.println(set.remove("the"));//存在要删除的元素,删除并返回trueSystem.out.println(set.remove("the"));//不存在要删除的元素,返回false//创建顺序表,作为集合使用ArrayList<String> arrayList = new ArrayList<>();arrayList.add("bird");arrayList.add("worm");判断set是否包含顺序表中的所有元素System.out.println(set.containsAll(arrayList));//包含,truearrayList.add("the");System.out.println(set.containsAll(arrayList));//不包含,false//将set中的元素全部转换为数组String[] s = set.toArray(new String[0]);//原代码直接使用 set.toArray() 会返回 Object[] 而不是 String[]System.out.print(Arrays.toString(s));//打印数组System.out.println();System.out.println(set.addAll(arrayList));//将集合arraylist中的元素添加到set中,重复元素不添加//迭代器Iterator<String> it = set.iterator();while(it.hasNext()){System.out.print(it.next()+" ");}System.out.println();set.clear();//清空集合System.out.println(set.isEmpty());//set为空,返回true}
}

5. 哈希表
1. 概念
2. 冲突
1. 概念
2. 避免
-哈希函数设计引起哈希冲突的⼀个原因可能是:哈希函数设计不够合理。哈希函数设计原则:• 哈希函数的定义域必须包括需要存储的全部关键码,⽽如果散列表允许有m个地址时,其值域必须在0到m-1之间• 哈希函数计算出来的地址能均匀分布在整个空间中• 哈希函数应该⽐较简单
3. 常⻅哈希函数
4. 负载因⼦调节

5. 解决
1. 闭散列
1. 线性探测
2. ⼆次探测
3. 总结
2. 开散列/哈希桶
public class HashBack {//创建节点static class Node{public int key;//存放关键码public int val;//存在value值public Node next;//存在下一个节点的地址//构造方法public Node(int key,int val){this.key = key;this.val = val;}}//创建一个数组,存放具有相同地址的关键码public Node[] arr;//数组中关键码的个数public int usedSize;//构造方法public final int capacity = 1;public HashBack(){this.arr = new Node[capacity];}//插入数据public void put(int keys,int value){//判断负载因子loadFactor();//尾插//lastPut(keys,value);//头插headPut(keys,value);}private void lastPut(int keys,int value){int n = keys % arr.length;//寻找关键码的地址//如果头节点为空,将新节点作为头节点if(arr[n]==null){arr[n] = new Node(keys,value);}else{Node cur = arr[n];Node parent = null;while(cur!=null){parent = cur;//保存当前节点的地址if(cur.key==keys){cur.val = value;//更新value值return ;}cur = cur.next;//更新节点}//创建新节点Node node = new Node(keys,value);parent.next = node;//尾插}usedSize++;}//头插private void headPut(int keys,int value){int n = keys % arr.length;//计算关键码地址Node cur = arr[n];//查找keys是否存在while(cur!=null){if(cur.key == keys){cur.val = value;return;}cur = cur.next;}Node node = new Node(keys,value);//创建新节点//头插node.next = arr[n];arr[n] = node;usedSize++;}//判断负载因子private void loadFactor(){double factor = (usedSize+1)*1.0/arr.length;if(factor<=0.75){return;}//大于0.75,2倍扩容Node[] temp = new Node[2*arr.length];int n = temp.length;//将cur中的节点全部放入temp中for(int i =0;i<arr.length;i++){Node cur = arr[i];while (cur!=null){Node curNext = cur.next;//保存下一个节点地址//计算该cur中的关键码的地址int k = cur.key % n;//头插cur.next = temp[k];temp[k] = cur;cur = curNext;//更新节点}}arr = temp;}//获取keys对应的valpublic int get(int keys){int n = keys % arr.length;Node cur = arr[n];while (cur!=null){if(cur.key == keys){return cur.val;}cur = cur.next;}return -1;}//删除数据public int remove(int keys){//此步骤可以不用,因为在实例化对象时,我们已经将数组实例化,不可能为null
// if(arr==null){
// return -1;
// }int n = keys % arr.length;Node cur = arr[n];Node parent = cur;while(cur!=null){//如果要删除的是头节点if(arr[n].key==keys){arr[n] = arr[n].next;usedSize--;return cur.val;}if(cur.key == keys){parent.next = cur.next;usedSize--;return cur.val;}parent = cur;cur = cur.next;}return -1;}
} key和val不为int类型:需要重写equals和hashcode方法
import java.util.Objects;//创建学生类
class Student{public String name;//构造方法public Student(String name){this.name = name;}//重写equals方法,比较值是否相同@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;return Objects.equals(name, student.name);}//重写hashCode方法,获取对象地址@Overridepublic int hashCode() {return Objects.hashCode(name);}
}
public class CEHashBack<K,V> {//创建静态内部类,作为节点static class Node<K,V>{public K key;//存放关键码public V val;//存放value值public Node<K,V> next;//存放下一个节点地址//构造方法public Node(K key,V val){this.key = key;this.val = val;}}public Node<K,V>[] arr;//创建存放节点地址数组public int usedSize;//数组中节点个数public final double loadFactor = 0.75;//负载因子的最大值public final int capacity =10;//初始化数组长度//构造方法public CEHashBack(){this.arr = (Node<K, V>[]) new Node[capacity];//实例化数组}//插入(头插)public void put(K key,V val){if(key==null){System.out.println("key为空,无法插入");return;}if((double)(usedSize+1)/arr.length>loadFactor){expansion();//2倍扩容}int hash = Math.abs(key.hashCode());//防止为负数int n = hash % arr.length;//求key对应的数组下标Node<K,V> cur = arr[n];//查找key是否存在while(cur!=null){if(cur.key.equals(key)){cur.val = val;return;}cur = cur.next;}Node<K,V> node = new Node<>(key,val);//创建新节点//头插node.next = arr[n];arr[n] = node;usedSize++;}//2倍扩容private void expansion(){//创建一个新2倍数组Node<K,V>[] temp = (Node<K, V>[])new Node[2*arr.length];//将arr中的节点全部放入temp中for(int i=0;i< arr.length;i++){Node<K,V> cur =arr[i];while(cur!=null){int hash = Math.abs(cur.key.hashCode());//计算关键码的数字地址(防止为负数)int n = hash % temp.length;//在新数组中的下标Node<K,V> curNext = cur.next;//保存下一个节点地址//头插cur.next = temp[n];temp[n] =cur;cur = curNext;//更新节点}}arr = temp;//更新数组arr的地址}//获取key对应的valpublic V get(K key){if(key==null){System.out.print("key为空,无法查找");return null;}int hash = Math.abs(key.hashCode());int n = hash%arr.length;Node<K,V> cur = arr[n];while(cur!=null){if(cur.key.equals(key)){return cur.val;}cur = cur.next;}return null;}//删除public V remove(K key){if(key==null){System.out.print("key为空,无法删除");return null;}int hash = Math.abs(key.hashCode());int n = hash%arr.length;Node<K,V> cur = arr[n];Node<K,V> parent = null;while(cur!=null){//如果要删除头节点if(arr[n].key.equals(key)){arr[n] = arr[n].next;usedSize--;return cur.val;}if(cur.key.equals(key)){parent.next = cur.next;usedSize--;return cur.val;}parent = cur;cur = cur.next;}return null;}
} 3. 冲突严重时的解决办法
6. 性能分析
7. 和 java 类集的关系
例题:
138. 随机链表的复制 - 力扣(LeetCode)
相关文章:
数据结构——Map和Set
1. 搜索树 1. 概念 ⼆叉搜索树⼜称⼆叉排序树,它可以是⼀棵空树,或者是具有以下性质的⼆叉树: • 若它的左⼦树不为空,则左⼦树上所有节点的值都⼩于根节点的值 • 若它的右⼦树不为空,则右⼦树上所有节点的值都⼤于根节点的值…...
树莓派超全系列文档--(18)树莓派配置音频
这里写目录标题 音频更改音频输出通过桌面音量控制专业音频设备配置文件 通过 raspi-config 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 音频 Raspberry Pi OS 有多种音频输出模式: 默认情况下,Raspberry Pi OS 将音频…...
Flutter中实现拍照识题的功能
文章目录 **1. 功能拆解****2. 具体实现步骤****(1) 拍照或选择图片****(2) 图片预处理(可选)****(3) 文字识别(OCR)****(4) 数学公式识别 → LaTeX****方案1:Mathpix API(高精度,付费ÿ…...
装饰器模式:如何用Java打扮一个对象?
引言装饰器模式具体实例共有接口类具体被装饰类抽象装饰器类具体装饰器类 测试装饰器模式的实际应用Java I/O 体系游戏开发中的角色装备系统 总结 引言 在生活中,我们都知道一句话,“人靠衣装马靠鞍”,如果想要让自己在别人眼里看起来更加好…...
OpenCV 图形API(或称G-API)(1)
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 引言 OpenCV 图形API(或称G-API)是一个新的OpenCV模块,旨在使常规图像处理更快且更便携。通过引入一种新的基于图的执行…...
学以致用,基于OpenCV的公摊面积估算程序
由于很多户型图并没有标注各个房间或者走廊的面积,亦或比较模糊,且很多人并不具备迅速口算多个小数相加再做除法的能力,本帖通过程序粗略计算公摊比例。由于非专业人士,公摊面积涉及到很多建筑学的专业公式,因此本帖只…...
爬虫:网络请求(通信)步骤,http和https协议
电脑(浏览器):www.baidu.com——url DNS服务器:IP地址标注服务器——1.1.38 DNS服务器返回IP地址给浏览器 浏览器拿到IP地址去访问服务器,返回响应 服务器返回给响应数据:html/css/js/jpg... html:文本 cs…...
d2025331
目录 一、删除有序数组中的重复项II 二、删除有序数组中的重复项 三、数字转罗马格式 一、删除有序数组中的重复项II 一下写过,挺舒服! 1、统计超出2的数量有多少,仅保留2个重复数字 2、有多少次就从后往前覆盖几次 public int removeDupl…...
QT6开发指南笔记(1)QT简介,安装
(1)刚刚结束了 C 的学习,谢谢阿西老师的教导,开始 QT 的学习,运用 C ,而非 QML 。 保持知识的连贯性。 QT 公司 : (2)接着介绍 QT 的安装: 提取到的…...
Redis BitMap 实现签到及连续签到统计
一、引言 用户签到功能是很多应用都离不开的一个板块,单词打开、QQ达人等等为我们所熟知,这项功能该如何实现呢,一些朋友可能想当然的觉得无非将每日的签到数据记录下来不就好了,不会去细想用谁记录,如何记录才合适。 …...
全面解析 Spring AOP 切入点表达式
全面解析 Spring AOP 切入点表达式 大家好,我是钢板兽! Spring AOP(面向切面编程)是我们日常开发中实现日志记录、权限控制、事务管理等功能的神器。而切入点表达式(Pointcut Expression)则是这个神器的“…...
去中心化稳定币机制解析与产品策略建议
去中心化稳定币机制解析与产品策略建议(以Maker/DAI为例) 一、核心机制对比:法币抵押型 vs. 加密货币抵押型 法币抵押型(如USDT) 技术逻辑:1:1美元储备托管于中心化机构(如银行)&…...
GO语言杂记(文章持续更新)
1、MAIN冲突 在一个文件夹下有两个go文件同时写了main函数,将会报错,main函数只能在main包中。 实则不然,有些环境下并不会报错。 2、gofmt命令---自动对齐 命令作用:将go文件代码自动缩进。 gofmt -w escapecharprac.go...
OS6.【Linux】基本指令入门(5)
目录 1.配置公网IP到XShell中 2.日志 定义和作用 3.一些指令 date %Y、%m、%d、%H、%M、%S、%X、%F %s 时间戳的特点 时间戳的转换 cal cal 年份 其他选项 ★find★ whereis grep 练习 -v选项 -n选项 -i选项 多文件查找 特定目录下查找 1.配置公网IP到XShe…...
Moo0 VideoResizer,简单高效压缩视频!
Moo0 VideoResizer 是一款免费、轻量级的视频压缩工具,支持通过调整文件大小、屏幕尺寸或比特率等方式实现高效视频压缩。其核心优势在于操作简单且无需破解,可直接下载安装使用。软件注重用户友好性,采用非破坏性压缩技术,所有…...
【开发问题记录】高德地图 Web 端开发详解:高德地图 API 最佳实践指南(安装、marker添加、逆向地理编码、实际业务案例实操)
文章目录 1、引入高德地图的准备工作2、高德地图 JS API 使用方式2.1 JS API Loader2.1.1 使用 script 标签加载loader2.1.2 NPM 安装loader 2.2 script 标签加载 JS API 脚本2.2.1 同步加载2.2.2 异步加载 3、在 vue3 项目中使用3.1 安装 js api loader3.2 在组件中使用 4、实…...
Unity 简单使用Addressables加载SpriteAtlas图集资源
思路很简单,传入图集名和资源名,利用Addressables提供的异步加载方式从ab包中加载。加载完成后存储进缓存字典里,以供后续使用。 添加引用计数,防止多个地方使用同一图集时,不会提前释放 using UnityEngine; using U…...
LangChain 结构化输出:用 Pydantic + PydanticOutputParser 驯服 LLM 的“自由发挥”
目录 一、Pydantic 二、PydanticOutputParser 1、为什么需要 PydanticOutputParser? 2、Pydantic和PydanticOutputParser核心区别 3、Pydantic的不足 (1)无法直接解析非结构化文本 (2)缺乏对 LLM 输出的适配性 …...
快速入手-基于Django-rest-framework的自身组件权限认证(九)
1、在对应的视图函数里增加认证(局部起作用,不全局生效) 导入类: from rest_framework.authentication import ( BasicAuthentication, SessionAuthentication, ) from rest_framework.permissions import IsAuthentica…...
【复活吧,我的爱机!】Ideapad300-15isk拆机升级:加内存条 + 换固态硬盘 + 换电源
写在前面:本博客仅作记录学习之用,部分图片来自网络,如需引用请注明出处,同时如有侵犯您的权益,请联系删除! 文章目录 前言升级成本升级流程电池健康度加内存条和换内存条光驱位加装机械硬盘更换电池重装系…...
基于Spring AI开发本地Jenkins MCP Server服务
前言 首先介绍下MCP是什么? MCP是由开发了 Claude 模型的 Anthropic 公司2024年11月提出并开源的一项开放标准,全称:Model Context Protocol,它是一个开放协议,它使 LLM 应用与外部数据源和工具之间的无缝集成成为可能…...
基于简单神经网络的线性回归
一、概述 本代码实现了一个简单的神经网络进行线性回归任务。通过生成包含噪声的线性数据集,定义一个简单的神经网络类,使用梯度下降算法训练网络以拟合数据,并最终通过可视化展示原始数据、真实线性关系以及模型的预测结果。 二、依赖库 …...
【nvidia】Windows 双 A6000 显卡双显示器驱动更新问题修复
问题描述:windows自动更新nvidia驱动会导致只检测得到一个A6000显卡。 解决方法 下载 A6000 驱动 572.83-quadro-rtx-desktop-notebook-win10-win11-64bit-international-dch-whql.exehttps://download.csdn.net/download/qq_18846849/90554276 不要直接安装。如…...
《SRv6 网络编程:开启IP网络新时代》第2章、第3章:SRv6基本原理和基础协议
背景 根据工作要求、本人掌握的知识情况,仅针对《SRv6 网络编程:开启IP网络新时代》书籍中涉及的部分知识点进行总结梳理,并与工作小组进行分享,不涉及对原作的逐字搬运。 问题 组内同事提出的问题:本文缺扩展头描述…...
如何将AI模型返回的字符串转为html元素?
场景: 接入deepseek模型的api到我们平台,返回的字符串需要做下格式化处理。 返回的数据是这样的: {"role": "assistant","content": "<think>\n嗯,用户问的是“星体是什么”。首先&am…...
Citus源码(1)分布式表行为测试
最近对citus的实现非常好奇,本篇对citus的行为做一些测试。本篇只测行为,不分析源码。后面会继续写一系列文章分析citus源码。 环境:3节点 PG17 with citus。 SELECT citus_set_coordinator_host(127.0.0.1, 3001); SELECT citus_add_node(1…...
装饰器模式与模板方法模式实现MyBatis-Plus QueryWrapper 扩展
pom <dependency><groupId>com.github.yulichang</groupId><artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 --> </dependency>MPJLambdaWrapperX /*** 拓展 MyBatis Plus Join QueryWrapper 类&…...
【PCIE711-214】基于PCIe总线架构的4路HD-SDI/3G-SDI视频图像模拟源
产品概述 PCIE711-214是一款基于PCIE总线架构的4路SDI视频模拟源。该板卡为标准的PCIE插卡,全高尺寸,适合与PCIE总线的工控机或者服务器,板载协议处理器,可以通过PCIE总线将上位机的YUV 422格式视频数据下发通过SDI接口播放出去&…...
突破反爬困境:SDK开发,浏览器模块(七)
声明 本文所讨论的内容及技术均纯属学术交流与技术研究目的,旨在探讨和总结互联网数据流动、前后端技术架构及安全防御中的技术演进。文中提及的各类技术手段和策略均仅供技术人员在合法与合规的前提下进行研究、学习与防御测试之用。 作者不支持亦不鼓励任何未经授…...
rce操作
Linux命令长度突破限制 源码 <?php $param $_REQUEST[param];if ( strlen($param) < 8 ) {echo shell_exec($param); } echo执行函数,$_REQUEST可以接post、get、cookie传参 源码中对参数长度做了限制,小于8位,可以利用临时函数&…...
