文章目录
- TreeSet和TreeMap
- 二叉搜索树模拟TreeMAp
- 定义
- 基本操作
- 插入
- 查找
- 删除(难点)
- 遍历
- 性能分析
- 应用场景
- Map&&Set
- 模型
- HashMap
- 常用方法
- HashSet
- 常用方法
- HashMap和HashSet区别
- 数据结构不同
- 元素类型不同
- 方法不同
- 使用场景不同
TreeSet和TreeMap
定义:
TreeSet是Java集合框架中的一种有序集合,它实现了Set接口,因此具有
不允许重复元素的特性
。与HashSet不同,TreeSet使用红黑树数据结构来存储元素
,这使得元素在集合中保持有序
TreeMap是基于
红黑树数据结构的键值对映射
。它保证键的有序性
,键按照其自然顺序(通过键的compareTo方法确定的顺序)进行排序。
联系
:TreeSet的底层是TreeMap,TreeMap的底层是一颗红黑树,而红黑树是一颗近似平衡的二叉搜索树
二叉搜索树模拟TreeMAp
定义
是一颗特殊的二叉树,具有以下性质:
- 若左子树不为空,则左子树上的所有节点的值都小于根节点的值
- . 若右子树不为空,则右子树上的所有节点的值都大于根节点的值
- 左右子树也都是二叉搜索树
- 不存在键值相等的节点
static class TreeNode{
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int val) {
this.val = val;
}
}
private TreeNode root;
基本操作
插入
- 如果是空树,直接插入
- 如果不是空树,创建新节点,比较根节点的值
- 如果新节点的键小于根节点的键,则将新节点插入到根节点的左子树中。
- 如果新节点的键大于根节点的键,则将新节点插入到根节点的右子树中
//插入
//定义一个parent
public void insert(int key){
if(root==null){
root=new TreeNode(key);
return ;
}
TreeNode parent=null;
TreeNode cur=root;
TreeNode node=new TreeNode(key);
while(cur!=null){
if(cur.val<key){
//先让parent到cur的位置,后cur往后走
parent=cur;
cur=cur.right;
}else if(cur.val==key){
//不能插入两个相同的数
return;
}else {
parent=cur;
cur=cur.left;
}
}
//走到这里说明要开始插入了,比较子树上根节点与要插入的值
if(parent.val<key){
parent.right=node;
}else{
parent.left=node;
}
}
查找
- 从根节点开始比较。
- 如果查找的键小于当前节点的键,则递归地在左子树中查找
- 如果查找的键大于当前节点的键,则递归地在右子树中查找
- 如果找到节点,则返回该节点
- 如果没有找到,则返回null。
public TreeNode search(int key) {
TreeNode cur=root;
while(cur!=null){
if(cur.val<key){
cur=cur.right;
}else if(cur.val==key){
return cur;
}else{
cur=cur.left;
}
}
return null;
}
删除(难点)
(1) cur.left==null
cur为root,则root=cur.right
cur不为root,cur是parent.left,则parent.left=cur.right
cur不为root,cur是parent.right,则parent.right=cur.right
(2) cur.right==null
cur为root,则root=cur.left
cur不为root,cur是parent.left,则parent.left=cur.left
cur不为root,cur是parent.right,则parent.right=cur.left
(3)cur的左右侧都不为空,需要进行替换法删除
定义tp=cur,target,右树的找到最小值,右树最左边的节点,最左边的节点说明左树为空
public boolean remove(int key){
TreeNode cur=root;
TreeNode parent=null;
//parent要记录下cur的前一个节点,要不然删不了
while(cur!=null){
if(cur.val<key){
parent=cur;
cur=cur.right;
}else if(cur.val>key){
parent=cur;
cur=cur.left;
}else {
removeNode(parent,cur);
return true;
}
}
return false;
}
private void removeNode(TreeNode parent,TreeNode cur) {
if(cur.left==null){
if(cur==root){
root=cur.right;
}else if(parent.left==cur){
parent.left=cur.right;
}else {
parent.right=cur.right;
}
}else if(cur.right==null){
if(root==cur){
root=cur.left;
}else if(parent.left==cur){
parent.left=cur.left;
}else {
parent.right=cur.left;
}
}else{//cur的左右都不为空,替换删除
TreeNode targetParent=cur;
TreeNode target=cur.right;
while(target.left!=null){
targetParent=target;
target=target.left;
}
cur.val=target.val;
if(targetParent.left==target){
targetParent.left=target.right;
}else{
targetParent.right=target.right;
}
}
}
遍历
二叉搜索树的遍历操作与普通二叉树相同,可以使用前序、中序和后序遍历。
前序遍历举例
public void prevOreder(TreeNode root){
if (root==null){
return;
}
prevOreder(root.left);
System.out.print(root.key+" ");
prevOreder(root.right);
}
注意:
-
如果TreeMap实现排序,那么存放的对象如果是引用类型那么就必须要实现Comparable或者Comparator接口如果没有实现就会报错,因为无法比较两个对象的大小.
-
在TreeMap插入键值对时,key不能为null,否则或抛出NullPointerException异常,value可以为空
性能分析
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:log以2为底N
最差情况下,二叉搜索树退化为单支树,其平均比较次数为:N/2
应用场景
TreeMap的应用场景
在实际应用中,如果你需要一个有序的映射表
,并且不允许键重复
,那么TreeMap是一个很好的选择。它既满足了有序性的需求,又提供了高效的操作性能
- 需要保持键的有序性且需要根据
键快速查找值
- 常用于需要按照特定顺序处理键值对的情况。
TreeSet的应用场景
适用于需要保持元素有序
并且去除重复元素
的场景。由于其基于红黑树实现,可以高效地支持元素的查找、插入和删除操作。因此,在需要有序集合且不允许重复元素的情况下,TreeSet是一个十分实用的选择
- 当需要
保持元素的有序性且不允许重复
- 常用于需要按照特定顺序处理元素的情况。
Map&&Set
Map和set是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例化子类有关
常见的搜索方式:
直接遍历,时间复杂度为O(N),元素如果比较多效率会非常慢
二分查找,时间复杂度为O(logN) ,但搜索前必须要求序列是有序的
上述排序比较适合静态类型的查找,即一般不会对区间进行插入和删除操作了,而现实中的查找比如:
- 根据姓名查询考试成绩
- 通讯录,即根据姓名查询联系方式
- 不重复集合,即需要先搜索关键字是否已经在集合中
可能在查找时进行一些插入和删除的操作,即动态查找,那上述两种方式就不太适合了,下面介绍的Map和Set是一种适合动态查找的集合容器
模型
一般把搜索的数据称为关键字(Key),和关键字对应的称为值(Value),将其称之为Key-value的键值对,所以模型会有两种:
- 纯 key 模型,比如:
有一个英文词典,快速查找一个单词是否在词典中,这个单词就是Key - Key-Value 模型,比如:
统计文件中每个单词出现的次数,统计结果是每个单词都有与其对应的次数:<单词,单词出现的次数>
Map中存储的就是key-value的键值对
Set中只存储了Key。
Map是一个接口类,该类没有继承自Collection,该类中存储的是<K,V>结构的键值对,并且K一定是唯一的,不
能重复。
注意
:Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者HashMap
Set是继承自Collection的接口类,Set中只存储了Key
实现 Set 接口的常用类有 TreeSet 和 HashSet ,还有一个 LinkedHashSet
LinkedHashSet 是在 HashSet 的基础上维护了一个双向链表来记录元素的插入次序,本质上还是HashMap
区别
:map带有伴随数据,set没有伴随数据并且能去重
HashMap
常用方法
关于Entry<K,V>说明:
查看源码可知,Entry是Map接口的一个内部接口
HashMap实现了Map接口,必然也实现了Entry接口
可以看到在HashMap的内部类中,提供了获取Key,value的方法以及设置value的方法,没有设置Key的方法
注意
-
HashMap存放键值对的Key是唯一的,value是可以重复的
-
HashMap中的key可以全部分离出来,存储到Set中来访问(因为key不能重复),即keySet()方法.
-
HashMap中的value也可以全部分离出来,存储在Collection中(value可能有重复),即 values()方法.
-
entrySet()方法的使用:
entrySet()方法就是把HashMap中的每个键值对打包成一个整体,然后放入到一个集合中
HashSet
常用方法
注意
-
HashSet中只存储了key,并且key不会重复
-
每个构造方法都会初始化一个HashMap
-
HashSet底层HashMap的每个value都是一个空的Object类型的对象
-
HashSet中的key不能修改,如果要修改,需要先删除再重新插入.
-
HashMap和HashSet的key和value都可以存放null.
HashMap和HashSet区别
-
HashSet是继承自Collection的接口类,Set中只存储key。
-
HashMap是继承于Map接口,存储的是key-value键值对。
数据结构不同
HashSet: HashSet 内部使用哈希表(或哈希集合)来存储元素。哈希表是一个无序的数据结构,元素之间没有特定的顺序。
HashMap: HashMap 内部也使用哈希表,但它存储键值对,其中键和值之间有关联关系。HashMap 具有键的集合和值的集合,键是唯一的,值可以重复。
元素类型不同
HashSet: HashSet 存储的是单一的元素类型,如整数、字符串等。它用于存储不重复的对象,通过元素的哈希码来判断重复性。
HashMap: HashMap 存储键值对,键和值可以是不同类型的对象。键用于检索值,每个键都必须是唯一的,值可以重复。
方法不同
HashSet: HashSet 提供了添加、删除、查找元素的方法,例如 add(), remove(), contains() 等。它没有提供根据键查找值的方法。
HashMap: HashMap 提供了添加键值对、删除键值对、根据键查找值的方法,例如 put(), remove(), get() 等。它可以根据键来查找对应的值。
使用场景不同
HashSet 的适用场景
数据去重:当你需要存储一组数据,但不关心顺序和关联信息,只关心数据是否重复时,使用 HashSet 是合适的。
例如,存储一组唯一的用户名或标签。
集合运算:HashSet 适合用于集合运算
比如求交集、并集、差集等。
HashMap 的适用场景
键值存储:当你需要将数据与关联的键一起存储时,使用 HashMap 是合适的。
例如,存储学生的成绩,其中学生名是键,成绩是值。
数据索引:HashMap 适合用于构建索引,提供快速的查找能力。
例如,建立一个电话簿,根据姓名查找电话号码。
需要键值对的功能:如果你需要存储关联数据,并且需要使用键来查找值、替换值或遍历键值对,那么 HashMap 是最好的选择。