二叉搜索树
概念
二叉搜索树又称为
二叉排序树
,也可以为空树;
性质:
- 若它的左子树不为空,则左子树上所有结点的值都小于根结点的值;
- 若它的右子树不为空,则右子树上所有结点的值都大于根结点的值;
- 左右子树也分别是一棵二叉搜索树;
或者也可以这样理解
:只要中序遍历结果是一个有序序列就是一棵二叉搜索树;
如下图所示就是一棵二叉搜索树:
为了用构造的二叉搜索树来封装 Map
,所以写成泛型的,因此在定义结点时,将结点中的值域给成K-V
键值对,并让 key
继承Comparable
,使用compareTo
就可以进行比较;
结点及构造方法的定义:
采用
内部类的方式
来定义结点并给出构造方法;
代码如下
:
public class TreeMap <K extends Comparable<K>,V> implements Map<K,V> {
public static class TreeNode<K,V> {
TreeNode left; //左孩子
TreeNode right; //右孩子
Map.Entry<K,V> kv; //结点中的值域
public TreeNode(K key,V value) {
kv=new Entry<>();
kv.key = key;
kv.value=value;
}
}
二叉搜索树的插入
- 树为空:插入根结点位置
- 树不为空时:
步骤
:
(1)确定要插入结点的位置
- 让
cur
从根结点开始,只要cur
不空,循环继续,让parent
标记cur
的双亲位置; - 将插入结点的
key
与cur
的key
进行比较,结果有三种情况:
大于:让cur
就往搜索树的右子树中查找
小于: 让cur
就往搜索树的左子树中查找
等于:返回旧值
(2)插入新结点
代码实现
:
public V put(K key, V value) {
//空树
if(root==null){
root=new TreeNode<>(key,value);
size=1;
return value;
}
//树不空
//按照二叉搜索树的特性找待插入结点在树中的位置
TreeNode<K,V> cur=root;
TreeNode<K,V> parent=null;
while(cur!=null){
parent=cur; //标记cur的双亲
int rst=key.compareTo(cur.kv.key);
if(rst>0){
cur=cur.right;
}else if(rst<0){
cur=cur.left;
}else{
V oldValue=cur.kv.value;
cur.kv.value=value;
return oldValue;
}
}
//插入新结点
int rst=key.compareTo(parent.kv.key);
TreeNode<K,V> newNode=new TreeNode<>(key,value);
if(rst>0){
parent.right=newNode;
}else{
parent.left=newNode;
}
size++;
return value;
}
查找二叉搜索树的 key
根据二叉搜索树的特性,分两种情况;
情况一:树为空
直接返回false
情况二:树不空
根据二叉搜索树的性质,(1)先判断待查找的结点的key
与cur
标记的根结点是否相同;(2)当(1)不满足时,让cur
再去根的左右子树中查找
代码实现
:
//判断是否包含key---->利用搜索树的特性
//包含返回true,否则返回false
public boolean containsKey(K key) {
TreeNode<K,V> cur=root;
int rst=key.compareTo(cur.kv.key);
if(rst==0){
return true;
}else if(rst<0){
cur=cur.left;
}else{
cur=cur.right;
}
//没找到
return false;
}
查找二叉搜索树的 value
与上面就不一样了,这次需要对每个结点的
value
进行比对,因此需要进行中序遍历来查找;
代码如下
:
/判断是否包含value--->中序遍历
//包含返回true,否则返回false
public boolean containsValue(V value) {
return containsValue(root,value);
}
private boolean containsValue(TreeNode<K,V> _root,V value){
//树为空时
if(_root==null){
return false;
}
//树不为空
//对树进行中序遍历--->先在左子树中查看是否包含value
//包含返回true
if(containsValue(_root.left,value)) {
return true;
}
//判断根结点的value与查找的value是否相同
if(value.equals(_root.kv.value)){
return true;
}
//否则就去右子树中找value
return containsValue(_root.right,value);
}
二叉搜索树的删除(难点+重点
):
分情况:
假设最后删除的结点为
cur
;
情况1:
cur
是叶子结点:可以直接删除;
方法:只需要将parent
的右指向cur
的右即可
情况2:
cur
只有左孩子:可以直接删除;
方法:让parent
的左指向cur
的左即可
情况3:
cur
只有右孩子:可以直接删除;
方法:让parent
的右指向cur
的右
情况4:
cur
左右孩子均存在:不可以直接删除;
方法:使用替换法进行删除,即在它的右子树中寻找值最小的结点,将找到的该结点与被删除结点交换,再将待删除结点进行删除;
详细情况见以下代码~
代码实现
:
public V remove(K key) {
//1.找待删除结点的位置
TreeNode<K,V> cur=root;
TreeNode<K,V> parent=null;
while(cur!=null){
int rst=key.compareTo(cur.kv.key);
if(rst<0){
cur=cur.left;
}else if(rst>0){
cur=cur.right;
}else{
//找到了
break;
}
}
if(cur==null){
return null;
}
//2. 删除结点
V value =cur.kv.value;
if(cur.left==null){
//cur可能是叶子结点,也可能只有右孩子
if(parent==null){
//cur为根结点
root=cur.right;
}else{
if(cur==parent.left){
parent.left=cur.right;
}else {
parent.right=cur.right;
}
}
}else if( cur.right==null){
//cur 只有左孩子
if(parent==null){
//cur为根结点
root=cur.left;
}else {
if(cur==parent.left){
parent.left=cur.left;
}else{
parent.right=cur.left;
}
}
}else{
//cur左右孩子均存在
//a. 在其右子树中找最小的结点---->处于右边最左侧位置的结点
TreeNode<K,V> delNode=cur.right;
parent=cur;
while(delNode.left!=null){
delNode=delNode.left;
}
//b.将替代结点的值域赋给待删除结点cur
cur.kv=delNode.kv;
//c.删除
if(parent.left==delNode){
parent.left=delNode.right;
}else{
parent.right=delNode.right;
}
}
size--;
return value;
}
由于插入和删除的操作都必须先查找,查找的效率就代表了二叉搜索树中各个操作的性能;
对有n
个结点的二叉搜索树,当结点越多时,比较的次数就越多;
最优情况下,二叉搜索树为完全二叉树,其比较次数为log2N
:
最差情况下,二叉搜索树退化为单支树,其比较次数为N
次;