目录
1.前言
我们上次介绍了数据结构中的7中常见的排序算法,今天要给大家分享数据结构路上的一个新征程——Map和Set,让我们一起接着往下了解并学习。
2.搜索树
2.1概念
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
比如{5,3,4,1,7,8,2,6,0,9},画成二叉搜索树如下所示:
我们首先构建二叉搜索树。
public class BinarySearchTree {
static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int val) {
this.val = val;
}
}
}
2.2操作-插入
![](https://i-blog.csdnimg.cn/direct/81b78a078328470a9ada56176da6e55f.png)
2. 如果树不是空树,按照查找逻辑确定插入位置,插入新结点。
//插入
public void insert(int val) {
TreeNode node = new TreeNode(val);
if (root == null) {
root = node;
return;
}
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 {
return;
}
}
//parent 指向的节点 就是需要插入的节点位置 父节点
if(parent.val >val){
parent.left =node;
}else {
parent.right =node;
}
}
2.3操作-查找
//查询
public TreeNode root = null;
public TreeNode search(int k) {
TreeNode cur = root;
while (cur != null) {
if (cur.val > k) {
cur = cur.right;
} else if (cur.val < k) {
cur = cur.left;
} else {
return cur;
}
}
return null;
}
2.4操作-删除
//删除
public void remove(int key) {
TreeNode parent = null;
TreeNode cur = root;
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;
}
}
}
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 {
parent.right = cur.right;
}
} else if (cur.right == null) {
if (cur == root) {
root = cur.left;
}
if (cur == parent.left) {
parent.left = cur.left;
} else {
parent.right = cur.left;
}
} else {
TreeNode target = cur.right;
TreeNode targetP = cur;
while (target.left != null) {
targetP = target;
target = target.left;
}
cur.val = target.val;
if (target == target.left) {
targetP.left = target.right;
} else {
targetP.right = target.right;
}
}
}
2.5性能分析
- 插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
- 对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
- 但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树。
下面举个例子如{6,4,3,5,8,7,9}
最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:log₂N
最差情况下,二叉搜索树退化为单支树,其平均比较次数为: N/2
3.搜索
3.1概念及场景
1. 直接遍历,时间复杂度为 O(N) ,元素如果比较多效率会非常慢;2. 二分查找,时间复杂度为 O(log₂N), , 但搜索前必须要求序列是有序的。
上述排序比较适合静态类型的查找,即一般不会对区间进行插入和删除操作了,而现实中的查找比如:
1. 根据姓名查询考试成绩
2. 通讯录,即根据姓名查询联系方式
3. 不重复集合,即需要先搜索关键字是否已经在集合中
可能在查找时进行一些插入和删除的操作,即动态查找,那上述两种方式就不太适合了,本次介绍的Map和Set是一种适合动态查找的集合容器。
3.2模型
- 有一个英文词典,快速查找一个单词是否在词典中
- 快速查找某个名字在不在通讯录中
- 统计文件中每个单词出现的次数,统计结果是每个单词都有与其对应的次数:<单词,单词出现的次数>
- 梁山好汉的江湖绰号:每个好汉都有自己的江湖绰号
而Map中存储的就是key-value的键值对,Set中只存储了Key。
3.3Map 的使用
具体可以参考[Map的官方文档]:
3.3.1关于Map的说明
3.3.2关于Map.Entry<K, V>的说明
Map.Entry<K, V> 是Map内部实现的用来存放键值对映射关系的内部类,该内部类中主要提供了<key, value> 的获取,value的设置以及Key的比较方式。
方法
|
解释
|
K getKey() | 返回 entry 中的 key |
V getValue() | 返回 entry 中的 value |
V setValue(V value) | 将键值对中的value替换为指定value |
注意:Map.Entry<K,V>并没有提供设置Key的方法。
3.3.3Map 的常用方法说明
方法 | 解释 |
V get(Object key) | 返回 key 对应的 value |
V getOrDefault(Object key, V defaultValue) | 返回 key 对应的 value,key 不存在,返回默认值 |
V put(K key, V value) | 设置 key 对应的 value |
V remove(Object key) | 删除 key 对应的映射关系 |
Set<K> keySet() | 返回所有 key 的不重复集合 |
Collection values() | 返回所有 value 的可重复集合 |
Set<Map.Entry<K, V>> entrySet() | 返回所有的 key-value 映射关系 |
boolean containsKey(Object key) | 判断是否包含 key |
boolean containsValue(Object value) | 判断是否包含 value |
注意:
Map底层结构 | TreeMap | HashMap |
底层结构 |
红黑树
|
哈希桶
|
插入/删除/查找时间复杂度 | O(log₂N) | O(1) |
是否有序
| 关于Key有序 | 无序 |
线程安全
| 不安全 | 不安全 |
插入
/
删除
/
查找区别
| 需要进行元素比较 | 通过哈希函数计算哈希地址 |
比较与覆写
| key必须能够比较,否则会抛出 ClassCastException异常 | 自定义类型需要覆写equals和 hashCode方法 |
应用场景
| 需要Key有序场景下 | 不关心Key是否有序,需要更高的时间性能 |
3.3.4TreeMap的使用
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class Test {
public static void main(String[] args) {
Map<String, Integer> map = new TreeMap<>();
//put(K key, V value) 设置 key 对应的 value
map.put("小米手环9", 249);
map.put("红米K60", 3299);
get(Object key) 返回 key 对应的 value
System.out.println("小米手环9:" + map.get("小米手环9"));
System.out.println("红米K60:" + map.get("红米K60"));
//getOrDefault(Object key, V defaultValue) 返回 key 对应的 value,key 不存在,返回默认值
System.out.println(map.getOrDefault("小米", 9999));
//Set<K> keySet() 返回所有 key 的不重复集合
//把所有key放在了集合Set当中
Set<String> set = map.keySet();
System.out.println(set);
//containsKey(Object key) 判断是否包含 key
System.out.println(map.containsKey("红米K60"));
//containsValue(Object value) 判断是否包含 value
System.out.println(map.containsValue(249));
//remove(Object key) 删除 key 对应的映射关系
System.out.println(map.remove("小米手环9"));//删除小米手环9
//Set<Map.Entry<K, V>> entrySet() 返回所有的 key-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());
}
}
}
3.4Set 的使用
3.4.1Set 的说明
3.4.2Set的常用方法说明
方法 | 解释 |
boolean add(E e) | 添加元素,但重复元素不会被添加成功 |
void clear() | 清空集合 |
boolean contains(Object o) | 判断 o 是否在集合中 |
Iterator iterator() | 返回迭代器 |
boolean remove(Object o) | 删除集合中的 o |
int size() | 返回set中元素的个数 |
boolean isEmpty() | 检测set是否为空,空返回true,否则返回false |
Object[] toArray() | 将set中的元素转换为数组返回 |
boolean containsAll(Collection<?> c) | 集合c中的元素是否在set中全部存在,是返回true,否则返回 false |
boolean addAll(Collection<? extends E> c) | 将集合c中的元素添加到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可以。
8. TreeSet和HashSet的区别
Set底层结构
|
TreeSet
|
HashSet
|
底层结构
| 红黑树 | 哈希桶 |
插入/删除/查找时间复杂度
| O(log₂N) | O(1) |
是否有序
| 关于Key有序 | 不一定有序 |
线程安全
| 不安全 | 不安全 |
插入/删除/查找区别 | 按照红黑树的特性来进行插入和删除 | 1. 先计算key哈希地址 2. 然后进行 插入和删除 |
比较与覆写 | key必须能够比较,否则会抛出 ClassCastException异常 | 自定义类型需要覆写equals和 hashCode方法 |
应用场景
| 需要Key有序场景下 | Key是否有序不关心,需要更高的 时间性能 |
3.4.3TreeSet的使用
import java.util.*;
public class Test {
public static void main(String[] args) {
Set<String> set =new TreeSet<>();
//add(E e) 添加元素,但重复元素不会被添加成功
set.add("张三");
set.add("李四");
set.add("王五");
System.out.println(set);
System.out.println(set.add("张三"));
//contains(Object o) 判断 o 是否在集合中
System.out.println(set.contains("李四"));
//size() 返回set中元素的个数
System.out.println(set.size());
//remove(Object o) 删除集合中的 o
System.out.println(set.remove("王五"));
//iterator() 返回迭代器
Iterator<String> it = set.iterator();
while(it.hasNext()){
System.out.print(it.next() + " ");
}
System.out.println();
//addAll(Collection<? extends E> c) 将集合c中的元素添加到set中,可以达到去重的效果
String[] str ={"李四","王五","赵六"};
set.addAll(Arrays.asList(str));
System.out.println(set);
//containsAll(Collection<?> c) 集合c中的元素是否在set中全部存在,是返回true,否则返回 false
System.out.println(set.containsAll(Arrays.asList(str)));
}
}
4.总结
本次介绍了数据结构中关于Map和Set的概念、使用以及搜索树的几种操作,熟悉并掌握以上用法,为我们编写程序时提供更加方便的使用和操作。