当前位置: 首页 > news >正文

Java数据结构8(map和set)

目录

1,搜索树

2,哈希表

3,注意点

一,搜索树

1,前提引入

可以通过这张表看出,set和map分别都有通过红黑树或者哈希表来实现的,我们先从红黑树来讲起,红黑树的底层就是搜索树,我们先自己从自己创建的搜索树开始

2,概念

可以是一颗空树,或者具有这些性质:左边的都比根结点要小,右边都比根结点要大

3,方法的实现

(1)查询

//查找 public static TreeNode search(TreeNode root,int key){ if (root==null){ return null; } TreeNode cur=root; while (cur!=null){ if (key>cur.val){ cur=cur.right; }else if (cur.val==key){ return cur; }else { cur=cur.left; } } return null; }

(2)增加元素

//增加元素 public static void insert(TreeNode root,int key){ if (root==null){ root=new TreeNode(key); } TreeNode node=new TreeNode(key); TreeNode cur=root; TreeNode parent=null; while (cur!=null){ if (key>cur.val){ parent=cur; cur=cur.right; }else if (cur.val==key){ return; }else { parent=cur; cur=cur.left; } } if (parent.val>key){ parent.left=node; }else if (parent.val<key){ parent.right=node; } }

(3)删除元素

//删除 public static void remove(TreeNode root,int key){ if (root==null){ return; } TreeNode cur=root; TreeNode parent=null; //先要找到cur的位置在进行删除 while (cur!=null){ if (key> cur.val){ parent=cur; cur=cur.right; }else if (key==cur.val){ removecur(parent,cur); return; }else{ parent=cur; cur=cur.left; } } } private static void removecur(TreeNode parent, TreeNode cur) { //核心就是要让变的部分等于cur.right if (cur.left==null){ //cur为root if (cur==root){ root=cur.right; }else if (cur==parent.left){ parent.left=cur.right; }else if (cur==parent.right){ parent.right=cur.right; } }//核心就是要让变的部分等于cur.left else if (cur.right==null){ if (cur==root){ root=cur.left; }else if (cur==parent.left){ parent.left=cur.left; }else if (cur==parent.right){ parent.right=cur.left; } }//左右都不为空的情况 //我们选择替换->让右边的最左树替换掉cur else { TreeNode target=cur.right; TreeNode targetparent=cur; while (target.left!=null){ targetparent=parent; target=target.left; } cur.val=target.val; //也就是有左树 if (target==targetparent.left){ targetparent.left=target.right; }//没有左树,只有右树,那么target就是合适的替换人选 else { targetparent.right=target.right; } } }

4,模型的介绍

(1)K 模型(只存 Key,对应 Set)

容器里只存储关键字 Key,没有配套的 value,唯一作用:判断某个 key 是否存在、自动去重

(2)KV 模型(Key+Value 成对存储,对应 Map)

容器存储一对数据 <Key, Value>,key 唯一,value 是 key 附带的业务信息,通过 key 快速查找对应 value。

map的存储是kv模型,而set是纯k模型

5,treemap的方法实现

(1)常见的方法:

方法名解释
get(Object key)返回 key 对应的 value
getOrDefault(Object key, V defaultValue)返回 key 对应的 value,key 不存在,返回默认值
put(K key, V value)设置 key 对应的 value
remove(Object key)删除 key 对应的映射关系
keySet()返回所有 key 的不重复集合
values()返回所有 value 的可重复集合
entrySet()返回所有的 key-value 映射关系
containsKey(Object key)判断是否包含 key
containsValue(Object value)判断是否包含 value
Map.Entry<a,b>是map内部类用来实现存放kv键值对映射关系的内部类,下面的方法只有单个map.entry才能实现,用set来点是可以发现没有对应的方法的,因为我下面代码中的set已经表达的是一个map.entry的一个集合,里面装着左右的map.entry,所以我们要拿出一个的话可以使用循环或者迭代器来实现
方法解释
K getKey()返回 entry 中的 key
V getValue()返回 entry 中的 value
V setValue(V value)将键值对中的 value 替换为指定 value
public static void main(String[] args) { Map<String,Integer> map=new TreeMap<>(); map.put("ab",1); map.put("cd",2); map.put("ef",2); map.put("hello",5); Set<String> strings=map.keySet(); System.out.println(strings); Collection<Integer> collection=map.values(); System.out.println(collection); Set<Map.Entry<String,Integer>> set=map.entrySet(); for (Map.Entry<String, Integer> entry : set) { System.out.println("key:"+entry.getKey()+" "+"val:"+entry.getValue()); } //System.out.println(set); }

(2)注意点

1,Map是⼀个接⼝,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者 ,HashMap

2,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删除掉,然后再来进⾏重新插⼊。

6,treeset的方法实现

(1)常见的方法:

方法解释
boolean add(E e)添加元素,重复元素不会添加成功
void clear()清空集合中所有元素
boolean contains(Object o)判断元素 o 是否存在于集合
Iterator<E> iterator()返回集合的迭代器,用于遍历元素
boolean remove(Object o)删除集合中的指定元素 o
int size()返回集合内元素的总个数
boolean isEmpty()判断集合是否为空,空返回 true,否则 false
Object[] toArray()将集合所有元素转为 Object 数组并返回
boolean containsAll(Collection<?> c)判断传入集合 c 的所有元素是否都在当前 Set 中,全部存在返回 true
boolean addAll(Collection<? extends E> c)将集合 c 全部元素添加进当前 Set,自动去重
public static void main(String[] args) { Set<Integer> set=new TreeSet<>(); set.add(2); set.remove(2); System.out.println(set.contains(2)); }

(2)注意点

1,key要唯一

2,本质也就是treemap,treemap里面的key就是唯一的,所以也就相当于是对应的,但是treemap里面不是还有一个val项,treeset默认传给一个object类

二,哈希表

1,概念

我们通过哈希函数来放数据,构造出来的结构称为哈希表

我们常用的函数为key/capacity

2,冲突-避免

面对上面说的冲突,我们有避免和解决两种方法,我们首先来讲避免

(1)改善哈希函数

面对不同的要求,我们可以定制不同的哈希函数,但是哈希函数一般我们都不会去改变他,我们都是用的常用的,也就是除留余数法:key/capacity

(2)调节负载因子

散列表的负载因子:填入表中的元素个数/散列表的长度

负载因子和冲突率的函数图:

可以发现负载因子越大,冲突率越大

3,冲突-解决

解决我们一般有两种方法:闭散列开散列

(1)闭散列

(1.1)线性探测

直接理解就是我们找到冲突后面的第一个空的元素填进去

这样会导致冲突的元素都聚集在一起,删除元素的时候会很麻烦,因为如果我们要删除4的时候,4下标的位置就空出来了,当我们要找14的时候,我们理应根据公式找到4下标,但是我们会发现4下标是没有的,所以这时候我们要删除的时候要标记一下那个下标是否已经被删除过了

(1.2)二次探测

这个方法比线性探测好一点,但是元素多了也会导致矛盾元素的聚集,也称为二次聚集,也不太好

(2)开散列(重点)

开散列是我们重点要掌握的方法,也俗称哈希桶

(3)自己实现的哈希桶

我们插入方法选择头插法

1,int类型
public class HushBuck { static class Node{ //成员 public int key; public int val;//默认给999 public Node next; //构造方法 public Node(int key, int val) { this.key = key; this.val = val; } } public static Node[] array; public int usedSize; public static final double LOAD_FACTOR = 0.75; public HushBuck(){ array=new Node[10]; } public void push(int key, int val){ //确定下标 int index=key%array.length; //更新val Node cur=array[index]; while (cur!=null){ if (cur.key==key){ cur.val=val; return; } cur=cur.next; } //头插法 Node newNode=new Node(key, val); newNode.next=array[index]; array[index]=newNode; usedSize++; //确认负载因子 if (usedSize*1.0/array.length>=LOAD_FACTOR){ //进行扩容并且重新哈希 resize(); } } private void resize(){ Node[] newArray=new Node[array.length*2]; for (int i = 0; i < array.length; i++) { Node cur=array[i]; while (cur!=null){ int newIndex=cur.key%newArray.length; Node curN=cur.next; cur.next=newArray[newIndex]; newArray[newIndex]=cur; cur=curN; } } array=newArray; } public int get(int key){ int index = key % array.length; Node cur = array[index]; while (cur != null) { if(cur.key == key) { return cur.val; } cur = cur.next; } return -1; } }
2,泛型
public class HushBuck2 <K,V>{ static class Node<K,V>{ //成员 public K key; public V val;//默认给999 public Node<K,V> next; //构造方法 public Node(K key, V val) { this.key = key; this.val = val; } } public Node<K,V>[] array; public int usedSize; public static final double LOAD_FACTOR = 0.75; public HushBuck2(){ array=(Node<K,V>[]) new Node[10]; } public void push(K key, V val){ //确定下标 int hash=key.hashCode(); int index=hash%array.length; //更新val Node<K,V> cur=array[index]; while (cur!=null){ if (cur.key.equals(key)){ cur.val=val; return; } cur=cur.next; } //头插法 Node<K,V> newNode=new Node<K,V>(key, val); newNode.next=array[index]; array[index]=newNode; usedSize++; //确认负载因子 if (usedSize*1.0/array.length>=LOAD_FACTOR){ //进行扩容并且重新哈希 resize(); } } private void resize(){ Node<K,V>[] newArray=new Node[array.length*2]; for (Node<K, V> kvNode : array) { Node<K, V> cur = kvNode; while (cur != null) { int newIndex = cur.key.hashCode() % newArray.length; Node<K, V> curN = cur.next; cur.next = newArray[newIndex]; newArray[newIndex] = cur; cur = curN; } } array=newArray; } public V get(K key){ int index = key.hashCode() % array.length; Node<K,V> cur = array[index]; while (cur != null) { if(cur.key.equals(key)) { return cur.val; } cur = cur.next; } return null; } }

三,注意点

1,hashcode 和 equals 的区别:

当我们想加自己写的类的时候,我们需要再类里面重写hashcode和equals方法,保证一样的key哈希出来的值是一样的

(1)equals负责判断内容相等

(2)hashCode负责哈希容器快速分区

(3)相等对象哈希值必须相同,哈希值相同对象不一定相等;

(4)重写 equals,一定要同步重写 hashCode,否则哈希集合失效。

2,系统里的哈希方法只有在你put的时候才会第一次定义数组的长度,才会分配内存,调用不带参数的构造方法,第一次的容量会是16,用带参数传容量的构造方法,会给到最接近你数字的二次幂,比如你给15,会给你16,你要20,会给你32

3,当你扩容的时候记得要二次哈希

http://www.jsqmd.com/news/1195706/

相关文章:

  • 2026年时尚家具工程定制指南:核验资质、环保与案例,规避项目风险 - 行业观察日记
  • ChatGPT高阶表达力跃迁实战指南(逻辑熵值量化评估体系首次公开)
  • 苏州姑苏区2026奢侈品包包回收防骗指南|套路避坑干货+维权解决方案+正规门店汇总 - 生活时报
  • 不输进口车衣!东营bba专修配纳尔车,瑞达一站式服务 - 资讯纵览
  • 郑州金水闲置名表长期闲置贬值?及时回收止损保值 - 全城热点
  • 如何定位与参与碎片化创意项目:从OC原创角色到社区协作
  • 2026年新消息:合肥婚纱照推荐机构,知名品牌深度解析 - 速递信息
  • MSPM0L-实战:ADC定时器触发与DMA传输的配置与性能优化
  • 别只盯着 Python,Java 程序员做 AI 大模型落地的真实优势在哪
  • 【协议篇】I2C总线仲裁与多主模式实战解析
  • [Dify实战] 知识库回答总是跑偏,先别急着改提示词,先查分段、召回和引用
  • 自己动手写一个spring之系列
  • 广安本地装修公司哪家好?2026 年业主真实反馈:服务 4000 户家庭的十年本土老店给出了答案 - 资讯纵览
  • AI SaaS冷启动全链路攻坚手册(含可复用的MVP技术栈清单)
  • STM32实战笔记(五):FSMC驱动外部SRAM的时序配置与性能优化
  • 基于SpringBoot的团多多社团管理系统任务书
  • ## 郑州金水名表回收全天服务,晚上可上门不耽误时间 - 全城热点
  • 时间管理经典好书推荐,一本书带你读懂时间管理
  • 郑州金水闲置手表变现,百达翡丽爱彼全系高价回收 - 全城热点
  • 专栏总目录
  • (论文速读)Stable Audio 2:用潜在扩散模型生成完整歌曲
  • 短剧出海翻译性价比实测:小预算能不能换来高质量译制
  • TDA3通信接口时序详解:I2C、UART、SPI、McASP设计与调试指南
  • TDA2E-17 DPI与GPMC接口时序配置实战:从手册参数到稳定信号
  • Python数据分析三剑客实战:从Numpy、Pandas到Matplotlib完整指南
  • 药品冷藏箱哪家质量好?哪个企业药品冷藏箱口碑好?永生仪器在制药企业的应用案例 - 品牌推荐大师
  • 福州回收手表先查资质!靠谱手表回收店铺持证鉴定,成色定级标准化不随意压价 - 每日生活报
  • AI 视频生成时代,自媒体人如何通过多模型组合提升原创度?
  • 天津回收劳力士万国欧米茄店铺|三大品牌的鉴定门槛到底有多高? - 讯息早知道
  • WebSocket 实战:从协议握手到生产级应用