[学习目标]
- 掌握二叉搜索树的模拟实现
- 掌握TreeMap和TreeSet类的使用
- 小结
- OJ练习
1.搜索树
1.1概念
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
1.2操作--查找
- 当前节点不为空;
- 将要查找的数字key,与当面节点地key比较,相等那就是找到了,返回true;
- 若不相等,如果待查找的数字比当前节点大,那就往节点的右树找;
- 如果待查找的数字比当前节点小,那就往节点的左树找;
- 如此循环,直到当前节点为空为止,返回false;
1.3操作--插入
1.如果是空树,即是root==null,直接插入;
2.如果不是空树,就是按照逻辑查找该元素节点应该插入的位置
1.4操作--删除(难点)
设待删除节点为cur,它的双亲节点为parent
1.待删除节点的左树为空,右树不确定(cur.left==null)
1.1cur==root ------>root=cur.right
1.2cur==parent.left ------>parent.left=cur.right
1.3cur==parent.right ------>parent.right==cur.right
2.待删除节点的右树为空,左树不确定(cur.right==null)
2.1 cur==root ------>root=cur.left
2.2cur==parent.left ------>parent.left=cur.left
2.3cur==parent.right ------>parent.right=cur.left
3.待删除节点左右树不为空
这里用替换法作为方法,即找到cur左子树的最大值或者右子树的最小值p节点,将p值赋给 cur,删除掉p, 这里选择找左子树的最大值作为分享
那么这个最大值就有可能两种情况, 记tp为p的是双亲节点
3.1 p==tp.right,这里选择删除3,左子树最大值就是2(这里由于2是最大的,所以2不会有右子树,但 是可以有左子树)
cur.key=p.key
tp.right=p.left;
3.2 p==tp.left 比如删除7,左子树的最大值就是6
cur.key=p.key
tp.left=p.elft
1.5 实现
public class BinarySearchTree {
public static class TreeNode{
int val;
TreeNode left;
TreeNode right;
public TreeNode(int val){
this.val=val;
}
}
public TreeNode root;
public boolean search(int val){
TreeNode cur=root;
if(cur==null){
return false;
}
while(cur!=null){
if(cur.val<val) {
cur = cur.right;
}else if(cur.val>val){
cur=cur.left;
}else{
return true;
}
}
return false;
}
public void insert(int val){
TreeNode cur=root;
TreeNode pcur=null;
TreeNode newNode=new TreeNode(val);
if(root==null){
root=newNode;
return;
}
while(cur!=null){
if(cur.val>val){
pcur=cur;
cur=cur.left;
}else{
pcur=cur;
cur=cur.right;
}
}
if(pcur.val>val){
pcur.left=newNode;
}else{
pcur.right=newNode;
}
}
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(parent.left==cur){
parent.left=cur.right;
}else{
parent.right=cur.right;
}
}else if(cur.right==null){
if(cur==root){
root=cur.left;
}else if(parent.left==cur){
parent.left=cur.left;
}else{
parent.right=cur.left;
}
}else{
TreeNode tp=cur;
TreeNode t=cur.right;
while(t.right!=null){
tp=t;
t=t.right;
}
cur.val=t.val;
if(tp.left==t){
tp.left=t.left;
}else{
tp.right=t.left;
}
}
}
}
2.1模型
一般把搜索的数据称为关键字(Key),和关键字对应的称为值(Value),将其称之为Key-value的键值对,所以 模型会有两种:
1. 纯 key 模型,比如:
- 有一个英文词典,快速查找一个单词是否在词典中
- 快速查找某个名字在不在通讯录中
2. Key-Value 模型,比如:
- 统计文件中每个单词出现的次数,统计结果是每个单词都有与其对应的次数:
- 梁山好汉的江湖绰号:每个好汉都有自己的江湖绰号
而Map中存储的就是key-value的键值对,Set中只存储了Key。
2.2 Map
2.2.1 关于Map的说明
Map是一个接口类,不继承于Collections接口,存储的是Key-Value的键值对,Key的唯一的,不可重复的
2.2.2 关于Map.Entry<>的说明
Map.Entry<>是Map的内部类,就像链表的节点的一样,只不过存储的Key-Value两个值的键值对,该内部类中主要提供了 的获取,value的设置以及Key的比较方式。
方法 | 说明 |
K getKey() | 返回Key的值 |
V getValue() | 返回Value的值 |
V setValue(V value) | 将键值对中指定的Value换成value |
2.2.3 Map的常用方法
方法 | 说明 |
V get(Object key) | 返回key对应的Value 没有对应的key返回null |
V getOrDefault(Object key,V objectValue) | 返回key对应的Value 找不到key返回objectValue |
V put(Object key,V value) | 添加键值对,key重复就修改value |
V remove(Object key) | 删除key键值对 |
boolean containsKey(Object key) | 判断是否包含key |
boolean containsValue(V value) | 判断是否包含value |
Set<Map.Entry<Key,Vlaue>> entrySet | 返回所有Map的键值对关系,用于获取所有的键值对,常用于遍历 |
Set<Key> Keyset | 返回所有的Key集合 |
Collection<V> values() | 返回所有 value 的可重复集合 |
注意:
- Map是一个接口,不能直接实例化,必须实例化它的实现类HashMap和TreeMap;
- Map存放的键值对Key不可重复,Value可以重复
- key不可以修改,只能删除,重新添加,value可以修改
- Map没有继承Collections,不能遍历,所以要借助Map中的Set<Map.Entry<K, V>> entrySet(),获取 Map中所有的键值对;
2.2.4 TreeMap的使用案例
//实例化一个TreeMap,key是一个String类型,Value是Integer类型,切记要使用包装类
Map<String,Integer> treeMap=new TreeMap<>();
//增加
treeMap.put("zhangSan",23);
treeMap.put("liSi",24);
treeMap.put("wangWu",25);
treeMap.put("zhaoLiu",26);
//删除
treeMap.remove("liSi");
//修改
treeMap.put("zhangSan",24);
System.out.println("===============================================");
//查找
System.out.println(treeMap.get("zhangsan"));//null;
System.out.println(treeMap.getOrDefault("zhangsan",0));//0
System.out.println(treeMap.get("zhangSan"));//24
System.out.println("===============================================");
//判断是否包含
System.out.println(treeMap.containsKey("zhangsan"));//false
System.out.println(treeMap.containsKey("zhangSan"));//true
System.out.println(treeMap.containsValue(23));//false
System.out.println(treeMap.containsValue(24));//true
System.out.println("===============================================");
//获取key集合
for(String x:treeMap.keySet()){
System.out.println(x);
///wangWu
//zhangSan
//zhaoLiu
}
System.out.println("===============================================");
//获取value集合
for(int x:treeMap.values()){
System.out.println(x);
//25
//24
//26
}
System.out.println("===============================================");
//Key-Value集合
for(Map.Entry<String,Integer> entry:treeMap.entrySet()){
System.out.println("Key-->"+entry.getKey()+" "+"Value-->"+entry.getValue());
//Key-->wangWu Value-->25
//Key-->zhangSan Value-->24
//Key-->zhaoLiu Value-->26
}
执行结果:
2.3 Set
2.3.1 关于Set的说明
set是继承于Collections,只存储Key,Key不重复;
2.3.2 Set的常用方法说明
方法 | 说明 |
boolean add(E) | 增加key |
void clear() | 清空所有key |
boolean contains(Object) | 判断是否包含key |
boolean isEmpty() | 判断Set是否为空 |
Iterator<E> iterator() | 返回迭代器 |
boolean remove(Object) | 删除节点 |
int size() | 返回set中元素的个数 |
Object[] toArray() | 将set中的元素转换为数组返回 |
boolean containsAll(Collection c) | 集合c中的元素是否在set中全部存在,是返回true,否则返回 false |
boolean addAll(Collection c) | 将集合c中的元素添加到set中,可以达到去重的效果 |
注意
- Set底层使用Map实现的,将key和Object作为键值对插入Map中
- Set的Key不能重复,Set结构最大的用处就是去重
2.3.3 TreeSet的使用案例
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
public class demo2 {
public static void main(String[] args) {
//实例化一个TreeSet,key是一个String类型,切记要使用包装类
Set<String> treeSet=new TreeSet<>();
//增加key
System.out.println(treeSet.add("zhangSan"));//true
treeSet.add("Lisi");
treeSet.add("wangWu");
treeSet.add("zhaoLiu");
System.out.println("======================================");
//返回set中元素的个数
System.out.println(treeSet.size());//3
System.out.println("======================================");
//清空所有key
//treeSet.clear();
//判断是否包含key
System.out.println(treeSet.contains("zhangSan"));//true
System.out.println("======================================");
//判断Set是否为空
System.out.println(treeSet.isEmpty());
System.out.println("======================================");
//返回迭代器
Iterator<String> iterator=treeSet.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("======================================");
//删除节点
System.out.println(treeSet.remove("zhangSan"));//true
System.out.println(treeSet.remove("zhangsan"));//false
System.out.println("======================================");
//将set中的元素转换为数组返回
Object[] ret=treeSet.toArray();
for (Object x:ret) {
System.out.println(x);
//Lisi
//wangWu
//zhaoLiu
}
}
}
执行结果:
3.小结
相信通过上面的学习,你已经发现TreeMap和TreeSet所使用的数据结构正是二叉搜索树(二叉排列树);自己先去模拟实现一下二叉搜索树,再去使用一下TreeSet和TreeMap,熟练掌握这个数据结构是很有必要的;
4.OJ练习
1.只出现一次的数字
2.宝石与石头