二叉搜索树问题
05_二叉搜索树
1、二叉搜索树(Binary Search Tree)
- 二叉搜索树是二叉树的一种,是应用非常广泛的一种二叉树,英文简称为BST
- 又被称为:二叉查找树、二叉排序树
- 任意一个节点的值都大于其左子树所有节点的值
- 任意一个节点的值都小于其右子树所有节点的值
- 它的左右子树也是一棵二叉搜索树
- 二叉搜索树可以大大提高搜索数据的效率
- 二叉搜索树存储的元素必须具备可比较性
- 比如int、double等
- 如果是自定义类型,需要指定比较方式
- 不允许为null
2、二叉搜索树的接口设计
- int size() // 元素的数量
- boolean isEmpty() // 是否为空
- void clear() // 清空所有元素
- void add(E element) // 添加元素
- void remove(E element) // 删除元素
- boolean contains(E element) // 是否包含某元素
2.1、添加节点
比如我们往下面这颗树中添加节点12,1
我们的添加步骤是:
- 找到父节点parent
- 创建新节点node
- parent.left = node 或者 parent.right = node
假如我们要添加的元素在树中存在,我们建议覆盖旧的值
public void add(E element){
NodeNotNUllCheck(element);
// 传入第一个节点
if(root == null){
root = new Node<>(element, null);
size++;
return;
}
Node<E> node = root;
Node<E> parent = root;
int cmp = 0;
while(node != null){
parent = node; // 父节点
cmp = compareTo(node.element, element); // 方向
if(cmp < 0){
node = node.right;
}else if(cmp > 0){
node = node.left;
}else{ // 相等,最好是覆盖掉
node.element = element;
return;
}
}
Node<E> newNode = new Node<>(element, parent);
if(cmp < 0){
parent.right = newNode;
}else{
parent.left = newNode;
}
size++;
}
2.2、删除节点
删除节点又分三种情况:
删除叶子节点、度为1的节点、度为2的节点
2.2.1、删除叶子节点
对于删除叶子节点的这种情况,我们可以直接删除
如下图所示,如果我们要删除叶子节点3,那么我们只需要让找到它的父节点2,然后执行node.parent.right = null即可直接删除,不过我们要保证的是node == node.parent.right
同样的,假如我们要删除的是叶子节点10,那么我们也只需要找到它的父节点11,然后执行node.parent.left = null即可直接删除,不过我们要保证的是node == node.parent.left
对于只含有一个根节点的树来说,也即node.parent == null,我们直接让root = null即可
2.2.2、删除度为1的节点
对于下图所示的情况来看,我们要删除度为1的节点,此时我们拿节点4和9来举例说明:
我们把要删除的节点定义为node,它的孩子节点为child,我们从图上可以很清晰的看到,度为1的节点,它的孩子节点,要么左孩子要么是右孩子,也就是child 是 node.left 或者 child 是 node.right
如果我们要删除的节点是它父亲节点的左子树的话,即我们图中的节点4,它是根节点7的左子树,那么我们删除它的时候,只要满足child.parent = node.parent 和 node.parent.left = child即可,也就是让2的父亲节点指向4的父亲节点,然后让7的左子节点为2,即可完成删除
同样的如果我们要删除的节点是它父亲节点的右子树的话,我们只需要满足child.parent = node.parent 和 node.parent.right= child即可
还有一种情况,就是如果node是根节点的话,如下图所示:
我们只需要让root = child, child.parent = null即可完成
2.2.3、删除度为2的节点
如下图所示:
假如我们要删除根节点5,那么我们只需要找到根节点的前驱结点,或者后继节点即可实现,前驱节点具体看https://blog.csdn.net/mj598_/article/details/115036091?spm=1001.2014.3001.5501
最后代码如下:
具体细节我已经写得很清楚了
/**
* 删除节点
* @param element
*/
public void remove(E element){
remove(node(element));
}
private void remove(Node<E> node){
if(root == null){
return;
}
size--;
// 如果度为2
if(node.hasTowChildren()){
// 找到它的前驱结点
Node<E> pre = predecessor(node);
node.element = pre.element;
node = pre;
}
// 度为1的节点,它只存在左子节点,或者右子节点,如果左子节点不为空,那replacement就是node.left,反之,就是node.right
Node<E> replacement = node.left != null ? node.left : node.right;
// 如果replacement不为空的话,那么node肯定是度为1的节点
if(replacement != null){ // node为度为1的节点
replacement.parent = node.parent;
if(node.parent == null){ //如果node的父节点为null,也就是node为根节点
root = replacement;
}else if (node == node.parent.left){ // 如果是左子节点的话,就让父节点的左子树指向自己的子节点
node.parent.left = replacement;
}else if(node == node.parent.right){ // 如果是右子节点的话,就让父节点的左子树指向自己的子节点
node.parent.right = replacement;
}
}else if(node.parent == null){ // 如果只有一个根节点的话,那么删除操作就是让它的根节点指向null即可
root = null;
}else { // node是叶子节点
if(node == node.parent.right){ // 如果这个叶子节点是父子节点的右子节点
node.parent.right = null; // 直接将它置为null
}else {
// 如果这个叶子节点是父子节点的左子节点
// 直接将它置为null
node.parent.left = null;
}
}
}
private Node<E> node(E element){
Node<E> node = root;
int cmp = compareTo(element, node.element);
while (node != null){
if(cmp == 0){
return node;
}else if (cmp < 0){
node = node.left;
}else {
node = node.right;
}
}
return null;
}