一.搜索树
1. 概念
二叉搜索树 又称 二叉排序树 , 它是一颗空树 , 或者是具有以下性质 的二叉树 :
- 若它的左子树不为空 , 则 左子树上所有结点的值 都小于根节点的值
- 若他的右子树不为空 , 则 右子树上所有结点的值 都大于根节点的值
- 他的左右子树也分别为为二叉搜索树

代码 :
java
public class BinarySearchTree {
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int val) {
this.val = val;
}
}
public TreeNode root;
/**
* 查找
*/
public TreeNode search(int val) {
TreeNode cur = root;
while (cur != null) {
if (cur.val == val) {
return cur;
} else if (cur.val > val) {
cur = cur.left;
} else {
cur = cur.right;
}
}
return null;
}
/**
* 插入
*/
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) {
return; // 不允许插入重复元素
} else if (cur.val > val) {
parent = cur;
cur = cur.left;
} else {
parent = cur;
cur = cur.right;
}
}
if (parent.val > val) {
parent.left = node;
} else {
parent.right = node;
}
}
/**
* 删除
*/
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 {
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 {
parent.right = cur.left;
}
} else {
// 左右子树都不为空,找中序后继(右子树最左节点)
TreeNode target = cur.right; // 从右子树开始查找
TreeNode targetParent = cur; // 后继的父节点初始为当前节点
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;
}
}
}
}
2.性能分析
- 插入和删除都必须先查找 , 查找效率代表了二叉搜索树的各个操作的性能
- 对于 有 N个结点的二叉搜索树 , 若每个元素查找的概率相等 , 则二叉搜索树的平均查找长度是 在二叉搜索树的深度的函数 , 即结点越深 , 比较次数越多
- 但是 对于同一个关键码集合 , 如果各关键码的插入次序不同 , 可能得到不同的二叉搜索树

- 最优情况 : 二叉搜索树为完全二叉树 , 其平均比较次数为 log2N
- 最差情况 : 二叉搜索树退化为单分支树 , 其平均比较次数为 N/2
3.和 Java 类集的关系
TreeMap 和 TreeSet 即 Java 中利用搜索树实现的 Map 和 Set ; 实际上用的是红黑树 , 而红黑树是一颗近似平衡的二叉搜素树 , 即在二叉搜索树的基础上 加 颜色及红黑树性质验证
二.搜索
1.模型一般把搜索的数据称为关键字 (Key) , 和关键字对应的称为值 (Value) , 将其称为 Key-Value 的键值对 , 搜索模型有以下两种 :
① 纯 Key 模型 , 例如 :
- 有一个词典 , 快速查找一个单词是否在词典中
- 快速查找某个名字在不在通讯录中
②Key-Value 模型 , 例如 :
- 统计文件中 每个单词出现的次数 , 统计结果是每个单词都有 与之对应的次数 : <单词 , 单词出现的次数>
- 梁山好汉的江湖绰号 : 每个好汉都有自己的江湖绰号
Map 中存储的就是 Key-Value 的键值对 , Set 中只存储了 Key
三.Map 的使用

1.关于 Map 的说明
Map 是一个接口类 , 该类没有继承自 collection , 该类中存储的是 <K,V>结构的键值对 , 并且 K 一定 是唯一的 , 不能重复
2.关于 Map.Entry<Key , Value>的说明
Map.Entry<Key , Value>是 Map 内部实现的用来存放<Key,Value>键值对映射关系的内部类 , 该内部类中主要提供了<Key,Value>的获取 , Value 设置以及 Key 的比较方式
|---------------------|--------------------------|
| 方法 | 解释 |
| K getKey() | 返回 entry 中的 Key |
| V getValue() | 返回 entry 中的 Value |
| V setValue(V value) | 将键值对中的 Value 替换为指定 Value |
注意 : Map.Entry<K,V>并没有提供设置 Key 的方法
3.Map 的常用方法
|---------------------------------------------|------------------------------------------------|
| 方法 | 解释 |
| V get(Object key) | 返回 key 对应的 value |
| V getOrDefault(Object key , V defaultValue) | 返回 key 对应的 value , key 不存在 , 返回默认值defaultValue |
| V put(K key , V value) | 设置 key 对应的 value |
| V remove(Object key) | 删除 key 对应的映射关系 |
| Set<K> keySet() | 返回所有 key 的不重复集合 |
| Collection<V>values() | 返回所有 value 的可重复集合 |
| Set<Map.Entry<K,V>> entrySet() | 返回所有 key-value 的映射关系 |
| boolean containsKey(Object key) | 判断是否含 key |
| boolean containsValue(Object value) | 判断是否含 value |
注意 :
- Map 是一个接口 , 不能直接实例化对象 , 如果要实例化对象 只能实例化实现类 TreeMap 或者 HashMap
- Map 中存放 键值对的 **Key 是唯一的 ,**Value 是可重复的
- 在 TreeMap 中插入键值对时 , Key 不能为空 , 否则会抛出 NullPointerException 异常 , Value 可以为空
- 但是在 HashMap 中 Key 和 Value 都可以为空
- Map 中的 Key 可以全部分离出来 , 存储到 Set 中来进行访问(因为 Key 不能重复)
- Map 中的 Value 可以全部分离出来 , 存储到 Collection 的任何一个子集合中(Value 可能重复)
- Map 中的键值对的 Key 不能直接修改 , Value 可以修改 , 如果要修改 Key 只能详见该 Key 删除掉 , 然后再来进行重新插入
TreeMap 和 HashMap 的区别 :
|----------------|--------------------|--------------------------------|
| Map 的底层结构 | TreeMap | HashMap |
| 底层结构 | 红黑树 | 哈希桶 |
| 插入/删除/查找的时间复杂度 | O(log2N) | O(1) |
| 是否有序 | 关于 Key 有序 | 无序 |
| 线程安全 | 不安全 | 不安全 |
| 插入/删除/查找的区别 | 需要进行元素比较 | 通过哈希函数计算哈希地址 |
| 比较与覆写 | key 必须能比较 , 否则抛出异常 | 自定义类型需要覆写 equals 和 hashCode 方法 |
| 应用场景 | 需要 key 有序场景下 | Key 是否有序不关心 , 需要更高的时间性能 |
代码 :
java
public static void main(String[] args) {
TreeMap<String,Integer> treeMap = new TreeMap<>();
/**
* put(Key,Value) : 插入 Key-Value 的键值对
* 如果 Key不存在 ,会将 Key-Value 键值对插入到 Map中 , 返回null
*/
treeMap.put("蔡旭困",1);
treeMap.put("hello",12);
Integer a1 = treeMap.put("wsz",23);
System.out.println(a1);//返回null
treeMap.put("wsa",null);//Key不能为空 , 但是 Value可以为空
Integer a2 = treeMap.put("wsz",21);//如果Key存在 ,会使用 新的Value 来替换 原来的Value ,返回旧Value
System.out.println(a2);//返回旧Value23
/*------------------------------------------------*/
/**
* get(Key) : 返回Key所对应的Value
* 如果Key存在 , 返回 Key 所对应的 Value
* 如果Key不存在 , 返回 null
*/
System.out.println(treeMap.get("wsz"));//21
System.out.println(treeMap.get("sdf"));//null
/*------------------------------------------------*/
//getOrDefault(Key,Value): 如果Key存在 ,返回与Key对应的Value , 如果不存在 , 返回一个默认值
System.out.println(treeMap.getOrDefault("wsz", 21));//21
System.out.println(treeMap.getOrDefault("sdf",1));//不存在Value , 返回一个默认值1
System.out.println(treeMap.size());//4
/*------------------------------------------------*/
/**
* containKey(Key):用来检测Key是否包含在Map中
* 按照红黑树的性质来进行查找
* 找到返回true ,否则返回false
*/
System.out.println(treeMap.containsKey("蔡旭困"));
/**
* containValue(Value) :检测Value是否包含在 Map 中
* 找到返回 true ,否则返回 false
*/
System.out.println(treeMap.containsValue(1));//true
/*------------------------------------------------*/
/**
* 打印所有的Key
* keySet()是将Map中的Key防止在Set中返回
*/
for (String s:treeMap.keySet()) {
System.out.print(s+" ");
}
System.out.println();
/**
* 打印所有的Value
* Value()是将Map中的Value放在 collect的一个集合中返回的
*/
for (Integer x:treeMap.values()) {
System.out.print(x+" ");
}
System.out.println();
/**
* 打印所有的键值对
* entrySet() : 将Map中的键值对放在Set中返回了
*/
for (Map.Entry<String, Integer> entry:treeMap.entrySet()) {
System.out.println(entry.getKey()+"-->"+entry.getValue());
}
System.out.println();
}
四.Set 的说明

1.Set 的常用方法
|---------------------------------------------|-------------------------------------------------|
| 方法 | 解释 |
| boolean add (E e) | 添加元素 , 但重复不会被添加成功 |
| void clear() | 清空集合 |
| boolean contains(Object o) | 判断 o 是否在集合中 |
| Iterator<E>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 中 , 可以达到去重的效果 |
注意 :
- Set 是继承自 collection 的一个接口类
- Set 中只存储了 key , 并且要求 key 的值要唯一
- TreeSet 的底层是使用 Map 来实现的 , 其使用 key 与 Object 的一个默认对象作为键值对插入到 Map 中的
- Set 最大的功能就是对集合中的元素进行去重
- 实现 Set 接口的常用类有 TreeSet 和 HashSet , 还有 linkedHashSet , linkedHashSet 是在 HashSet 的基础上维护了一个双向链表来记录元素的插入次序
- Set 中的 Key 不能修改 , 如果要修改 , 先将原来的删除掉 , 然后再重新插入
- TreeSet 中不能插入 null 的 key , HashSet 可以
TreeSet 和 HashSet 的区别 :
|--------------|---------------------|-------------------------------|
| Set 底层结构 | TreeSet | hashSet |
| 底层结构 | 红黑树 | 哈希桶 |
| 插入删除查找的时间复杂度 | O(log2N) | O(1) |
| 是否有序 | 关于 Key 有序 | 不一定有序 |
| 线程安全 | 不安全 | 不安全 |
| 插入/删除/查找的区别 | 按照红黑树的特性来进行插入和删除 | 先计算 key 哈希地址 ; 然后进行插入和删除 |
| 比较与覆写 | key 必须能够比较 , 否则抛出异常 | 自定义类需要覆写 equals 和 hashCode 方法 |
| 应用场景 | 需要 Key 有序场景下 | Key 是否有序不关心 , 需要更高的时间性能 |
代码 :
java
public static void main(String[] args) {
Set<String> s = new TreeSet<>();
/**
* add(key):如果key不存在, 则插入, 返回true
* 如果key存在, 则返回false
* 如果key是空的, 则会抛出空指针异常
*/
s.add("def0");
s.add("fgh");
s.add("hpo");
s.add("jfj");
s.add("okj");
s.add("dja");
boolean a1 = s.add("fgh");//key存在
boolean a2 = s.add("abc");//key不存在
//s.add(null);//抛异常
System.out.println(a1);//false
System.out.println(a2);//true
/**
* contains(key):如果key存在, 返回true, 否则返回false
*/
System.out.println(s.contains("abc"));//true
System.out.println(s.contains("hji"));//false
/**
* remove(key):key存在, 删除成功返回true
* key不存在, 删除失败返回false
* key为空, 抛出空指针异常
*/
s.remove("abc");
System.out.println(s);
/**
* 迭代器
*/
Iterator<String> it = s.iterator();
while(it.hasNext()){
System.out.print(it.next()+" ");
}
System.out.println();
}