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

Java-数构map和set

1.搜索树

1.1概念

二叉搜索树又称为二叉排序树,它或者是一颗空树要么是满足一下性质的二叉树:

  • 若左子树不为空,左子树上的所有节点的值都小于根节点的值
  • 若右子树不为空,那么右子树上所有节点的值都大于根节点的值
  • 它左右子树也分别是二叉搜索树

1.2查找操作

public  boolean search(int val){TreeNode cur=root;while(cur!=null){if(cur.val<val){cur=cur.right;}else if(cur.val>val){cur=cur.left;}else {return true;}}return false;}

1.3 插入操作

1.若是空树,即根==null,直接插入

2.若不是空入,查找到插入位置后插入,利用父亲节点保存前置节点。

public boolean insert(int val){TreeNode parent=null;TreeNode cur=root;TreeNode node=new TreeNode(val);if(root==null){root=node;return true;}while (cur!=null){if(cur.val<val){parent=cur;cur=cur.right;} else if (cur.val>val) {parent=cur;cur=cur.left;}else {return false;}}if(parent.val>val){parent.left=node;} else if (parent.val<val) {parent.right=node;}return true;}

1.4删除操作

将待删除结点设置为cur,待删除结点的双亲结点为parent。

1. cur.left == null
  1. cur root,则 root = cur.right
  2. cur 不是 rootcur parent.left,则 parent.left = cur.right
  3. cur 不是 rootcur parent.right,则 parent.right = cur.right
2. cur.right == null
  1. cur root,则 root = cur.left
  2. cur 不是 rootcur parent.left,则 parent.left = cur.left
  3. cur 不是 rootcur parent.right,则 parent.right = cur.left
3. cur.left != null && cur.right != null
  需要使用替换法进行删除,即在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题
public void remove(int val){TreeNode cur=root;TreeNode parent=null;while(cur!=null){//寻找要删除的结点if(cur.val<val){parent=cur;cur=cur.right;} else if (cur.val>val) {parent=cur;cur=cur.left;}else {removeNode(parent,cur);return;}}}private void removeNode(TreeNode parent, TreeNode cur) {if(cur.left==null){if(cur==root){root=cur.right;} else if (cur==parent.left) {parent.left=cur.right;} else if (cur==parent.right) {parent.right=cur.right;}} 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;}}else {//根节点左右都不为空:"替罪羊法"TreeNode targetParent=cur;TreeNode target=cur.right;while(target.left!=null){targetParent=target;target=target.left;}cur.val=target.val;//cur:95if(target==targetParent.left){targetParent.left=target.right;}else {targetParent.right=target.right;}}}

1.5性能分析

插入和删除操作都需要进行查找,而查找过程中会进行两两比较。

最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:\log_2 N                                              最差情况下,二叉搜索树会退化为单支树,其平均比较次数为\frac{N}{2}

TreeMap TreeSet java 中利用搜索树实现的 Map Set;实际上用的是红黑树,而红黑树是一棵近似平衡的二叉搜索树,即在二叉搜索树的基础之上 + 颜色以及红黑树性质验证。

2.map的使用

Map是一个接口类,没有继承Collection该类中存储的是<K,V>结构的键值对,并且K一定是唯一的,不能重复

2.1关于Map.Entry<K,V>的说明

Map.Entry<K, V> Map内部实现的用来存放<key, value>键值对映射关系的内部类,该内部类中主要提供了<key, value>的获取,value的设置以及Key的比较方式

2.2map的常用方法说明

注意:

  1. Map是一个接口,不能实例化对象,如果要实例化对象要实现TreeMap类或者HashMap
  2. Map中存放键值对的Key是唯一的,value是可以重复的
  3. 在Treemap中插入键值对的时候,key不能为空,否则会抛出空指针异常。但是HashMap的key和value可以为空
  4. map中的key可以分离出来存储到set中进行访问
  5. map中的value可以分离出来存储到collection中的任意自己
  6. map中键值对key不能直接修改,value可以修改。但是只能进行重新插入
public static void main(String[] args) {TreeMap<String,Integer> map=new TreeMap<>();map.put("this",3);map.put("phe",5);map.put("a",7);map.put("a",8888);int val = map.getOrDefault("phe8",1999);//不存在返回1999System.out.println(map.get("a"));System.out.println(val);Set<String> set=map.keySet();//返回所有key不重复的集合Collection<Integer> collection=map.values();//返回所有value的可重复集合Set<Map.Entry<String,Integer>> entries = map.entrySet();for(Map.Entry<String,Integer> entry:entries){System.out.println("key: "+entry.getKey()+"value: "+entry.getValue() );}}

2.3TreeMap和HashMap的区别

map底层结构TreeMapHashMap
底层结构红黑树

哈希桶

插入/删除/查找时间复杂度O(\log_2 N)O(1)
是否有序关于Key有序无序
线程安全不安全不安全
插入/删除/查找的区别元素需要比较通过哈希函数计算哈希地址
比较与覆写key必须能够比较,否则类型异常自定义类要覆写equals和hashcode方法
应用场景需要key有序场景下key是否有序不关心,用空间换时间

3.Set的使用

sei继承自collection接口类,set中只存储了key

public static void main(String[] args) {Set<String> set = new TreeSet<>();set.add("hello");set.add("abc");set.add("plo");set.remove("abc");System.out.println(set.contains("abc"));int count=0;count=set.size();System.out.println(count);System.out.println(set.isEmpty());for (String s : set) {System.out.println(s);}}
map底层结构TreeSetHashSet
底层结构红黑树

哈希桶

插入/删除/查找时间复杂度O(\log_2 N)O(1)
是否有序关于Key有序无一定有序
线程安全不安全不安全
插入/删除/查找的区别按照红黑树特性进行插入和删除1.先计算key的哈希地址2.进行插入删除操作
比较与覆写key必须能够比较,否则类型异常自定义类要覆写equals和hashcode方法
应用场景需要key有序场景下key是否有序不关心,用空间换时间

4.哈希表

4.1概念

可以不经过任何比较,一次直接从表中获得搜索的元素。如果构造一种存储结构,通过某种函数使得元素的存储位置和它的关键码之间能够建立一一映射关系,那么查找的时候通过函数可以很快的查找到该元素。

哈希方法种使用的转换函数乘坐哈希函数,构造出来的结构称为哈希表。我们一般将哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小。

4.2冲突的概念

对于两个数据元素的关键字 和 (i != j),有 != ,但有:Hash( ) == Hash( ),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞

4.3冲突的避免

冲突的发生是必然的,但是我们要尽可能降低冲突率。尽可能的去设计较好的哈希函数。

常见的哈希函数:

  1. 直接定制法:取关键字的某个线性函数为散列地址:HashKey= A*Key + B 优点:简单、均匀 缺点:需要事先知道关键字的分布情况 使用场景:适合查找比较小且连续的情况。
  2. 除留余数法:设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址。
  3. 平方取中法
  4. 随机数法
  5. 数学分析法

4.4负载因子的调节

当冲突率达到无法忍受的程度时候,我们要通过降低负载因子来变相的降低冲突率。关键字的个数是不可变的,那么我们只能调整哈希表种数组的大小。

5.解决冲突

解决哈希冲突的两种常见的方法是:闭散列和开散列

闭散列:
1.也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。
2.二次探测法,线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为:H_i = (H_0 + i^2) \% m 或者:H_i = (H_0 - i^2) \% m 其中:i = 1,2,3…,是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置,m是表的大小。
开散列:
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
下面我们自己来实现一下key-value 模型
public class HashBuck {static class Node{public int key;public int val;public  Node next;public Node(int key, int val) {this.key = key;this.val = val;}}public Node[] array;public  int usedSize;public HashBuck(){array=new Node[10];}public void put(int key,int val){int index=key%array.length;Node cur=array[index];while(cur!=null){if(cur.key==key){cur.val=val;return;}cur=cur.next;}Node node=new Node(key,val);node.next=array[index];array[index]=node;usedSize++;//头插法if(loadFoat()>=0.75){resize();}}private void resize() {Node[] tmpArray=new Node[array.length*2];for (int i = 0; i < array.length; i++) {Node cur=array[i];while(cur!=null){Node curNext=cur.next;int newIndex= cur.key;}}}private double loadFoat() {return usedSize*1.0/array.length;}
}

5.2性能分析

在实际过程种,哈希表的冲突率是不高的,冲突个数是可控的,也就是说每一个中的链表长度是一个常数,所以,通常意义下,我们认为哈希表的插入/删除/查找时间复杂度是O(1).

5.3java类集的关系

  • HashMap和HashSet在Java中是利用哈希表实现的Map和Set(HashMap的默认长度树16)
  • Java中使用的是哈希桶的方式解决冲突(默认负载因子是0.75)
  • Java会在冲突链表长度大于一定阈值后,将链表转换为搜索树(红黑树)(链表长度超过8或者数组长度超过64则变成红黑树)
  • Java中计算哈希值实际上是调用HashMap的key或者HashSet的值,必须覆写hashCode和equals方法,而且要做到 equals 相等的对象,hashCode 一定是一致的。
public class HashBuck2 <K,V>{static class Node<K,V>{public K key;public V val;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 HashBuck2(){array=(Node<K,V>[]) new Node[10];}public void put(K key,V val){int hash=key.hashCode();int index=hash%array.length;Node<K,V> cur=array[index];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=array[index];array[index]=node;usedSize++;}public V get(K key){int hash=key.hashCode();int index=hash%array.length;Node<K,V> cur=array[index];while(cur!=null){if(cur.key.equals(key)){return cur.val;}cur=cur.next;}return null;}
}

当不知道具体key和value的类型的时候可以借助泛型传入。

http://www.dtcms.com/a/331075.html

相关文章:

  • 高效解决 pip install 报错 SSLError: EOF occurred in violation of protocol
  • P5967 [POI 2016] Korale 题解
  • Transformer之多头注意力机制和位置编码(二)
  • Canon PowerShot D30相机 CHDK 固件 V1.4.1
  • 5.Ansible-playbook-模块介绍(知识点补充)
  • 【Postgresql】实现 PostgreSQL 全量审计日志:记录所有 SQL 操作及来源
  • 【C++】细说继承(2w字详解)
  • ROS机器人云实践案例博客建议和范文-AI版本
  • imx6ull-驱动开发篇24——Linux 中断API函数
  • MATLAB绘制各种心形曲线
  • window显示驱动开发—在混合系统中使用跨适配器资源
  • nginx-集成prometheus监控(k8s)
  • GitHub 热榜项目 - 日榜(2025-08-14)
  • 一、linux内存管理学习(1):物理内存探测
  • 京东商品列表API开发指南
  • OpenCV对椒盐处理后的视频进行均值滤波处理
  • Opencv 边界填充 图像运算 阈值处理 和图像平滑处理
  • 文件上传接口接收不到文件入参
  • 题解:P4777 【模板】扩展中国剩余定理(EXCRT)
  • Qt项目查找依赖库打包
  • IDEA、Pycharm、DataGrip等激活破解冲突问题解决方案之一
  • Springboot项目重启后Session依旧存在
  • Python包性能优化与并发编程:构建高性能应用的核心技术(续)
  • 轻量级开源全文搜索引擎:Manticore Search 入门介绍
  • C++基础(①入门教程)
  • 本地jar导入到本地仓科和远程仓库
  • Maven学习笔记
  • 92、23种设计模式-单例模式
  • 项目日志框架与jar中日志框架冲突 解决
  • 《多级缓存架构设计与实现全解析》