概述
历史
二叉搜索树最早是由Bernoulli兄弟在18世纪中提出的,但是真正推广和应用该数据结构的是1960年代的D.L. Gries。他的著作《The Science of Programming》中详细介绍了二叉搜索树的实现和应用。
在计算机科学的发展中,二叉搜索树成为了一种非常基础的数据结构,被广泛应用在各种领域,包括搜索、排序、数据库索引等。随着计算机算力的提升和对数据结构的深入研究,二叉搜索树也不断被优化和扩展,例如AVL树、红黑树等。
特性
二叉搜索树(也称二叉排序树)是符合下面特征的二叉树:
-
树节点增加 key 属性,用来比较谁大谁小,key 不可以重复
-
对于任意一个树节点,它的 key 比左子树的 key 都大,同时也比右子树的 key 都小,例如下图所示
轻易看出要查找 7 (从根开始),只需三次比较
-
与 4 比,较之大,向右找
-
与 6 比,较之大,继续向右找
-
与 7 比,找到
查找的时间复杂度与树高相关,插入、删除也是如此。
-
如果这棵树比较平衡,那么时间复杂度均是 O(log{N})
-
当然,这棵树如果不平衡(左右高度相差过大)下图,那么这时是最糟的情况,时间复杂度是 O(N)
实现
定义节点
public class BSTTree1 {
static class BSTNode {
int key; // 若希望任意类型作为 key, 则后续可以将其设计为 Comparable 接口
Object value;
BSTNode left;
BSTNode right;
public BSTNode(int key) {
this.key = key;
this.value = key;
}
public BSTNode(int key, Object value) {
this.key = key;
this.value = value;
}
public BSTNode(int key, Object value, BSTNode left, BSTNode right) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
}
}
BSTNode root;//根节点
}
这里查询以及put操作不再赘述 主要来分析删除操作的流程
删除
要删除某节点(称为 D),必须先找到被删除节点的父节点,这里称为 Parent
-
删除节点没有左孩子,将右孩子托孤给 Parent
-
删除节点没有右孩子,将左孩子托孤给 Parent
-
删除节点左右孩子都没有,已经被涵盖在情况1、情况2 当中,把 null 托孤给 Parent
-
被删除节点左右孩子双全 可以将它的后继节点(称为 S)托孤给 Parent,设 S 的父亲为 SP,又分两种情况
(1)被删除节点和后继节点相邻,直接把后继节点顶到被删除节点的位置即可
(2)不相邻,先处理后事,即处理后继节点的右孩子(为什么是右孩子,不是左孩子呢?因为后继节点会找被删除节点key值最近的节点,如果后继节点s有左孩子,那么就不可能找到的是s,而是s的左孩子 这里比较绕,可以看下图多思考几遍)
代码实现
托孤方法
/**
* 托孤方法
* @param parent 被删除节点的父亲
* @param deleted 被删除节点
* @param child 被顶上去的节点
*/
private void shift(BSTNode parent,BSTNode deleted,BSTNode child){
if(parent == null){
root = child;
}
if(parent.left == deleted){
parent.left = child;
}else{
parent.right = child;
}
}
public Object delete(int key){
BSTNode p = root;
BSTNode pParent = null;
while(p != null){
if(key < p.key){
pParent = p;
p = p.left;
}else if(key > p.key){
pParent = p;
p = p.right;
}else{
break;
}
}
if(p == null){
return null;
}
//p为被删除节点 pParent为被删除节点的父亲
if(p.left == null){
//情况一 被删除节点有右孩子没有左孩子 只需把被删除节点的右孩子托孤给父亲
//情况三 被删除节点为叶子节点 既没有左孩子也没有右孩子 直接走了情况一的逻辑
shift(pParent,p,p.right);
}else if(p.right == null){
//情况二 被删除节点有左孩子没有右孩子 只需把被删除节点的左孩子托孤给父亲
shift(pParent,p,p.left);
}else{
//情况四 被删除节点左右孩子双全
BSTNode s = p.right;
BSTNode sParent = p;
while(s.left != null){
sParent = s;
s = s.left;
}
//1.被删除节点和后继节点s相邻 直接顶上去
//2.不相邻 处理后事
if(sParent != p){
shift(sParent,s,s.right);//处理s的右孩子
s.right = p.right; //认亲被删除节点的右孩子 如果相邻无需认亲右孩子
}
//3.顶替被删除节点
shift(pParent,p,s);
s.left = p.left; //认亲被删除节点的左孩子 无论是否相邻都需认亲
}
Object value = p.value;
p = null;//help GC
return value;
}