二叉搜索树
BST
二叉搜索树简称BST树
二叉搜素树是二叉树的一种,数组这种数据结构也能够实现对元素的快速查找,但是对于元素的添加、删除和更新操作,使用数组来实现的话,就会存在较高的时间复杂度(O(n))级别。对于添加、删除、查找操作若是要保持较低的时间复杂度可以采用二叉搜索树来实现。
二叉搜索树又称二叉排序树,其节点之间按照一定的顺序进行排放可以极大地提高查找的效率,由于是树形结构,其在添加和删除操作上面的操作不像数组一样需要进行元素的复制。
建立一个BST树的过程,首先添加的是根部节点,然后新添加的元素就会按照BST树的特性进行添加,但相对每个添加动作来讲,每次添加都是在树的最底部(叶子节点的层)添加节点。
具有以下特性
- 所有节点都大于其左节点,都小于其右节点
- 所有节点的左、右子树也都是一个BST树
- 节点之间能够进行比较
- 不存在空节点
添加元素
添加的元素要符合BST树的特性,即添加新元素之后依旧是一个BST树。
添加步骤
步骤
- 找到要添加位置的根节点
- 判断添加的方向(左、右)
- 要添加位置已经存在元素了
- 直接覆盖
- 根据要添加的方向为根节点添加新子节点
- 要添加位置已经存在元素了
逐个添加以下元素
- 添加元素的时候,最重要的步骤是找到要添加的位置
- 找位置的时候,需要使用循环来遍历BST树
举例
7, 4, 9, 2, 5, 8
- 7 做为根节点 会直接添加到BST树中
- 然后添加 4
- 会先寻找要添加位置的根节点(使用待添加元素、BST树中节点进行比较,按照比较大小确定)
- 第一次会用 4 和根节点(7)比较
- 此时 res = -1 证明 4 应该添加到左边
- 会先寻找要添加位置的根节点(使用待添加元素、BST树中节点进行比较,按照比较大小确定)
- 添加 9
- 第一次会用 9 和根节点(7)比较
- 此时 res = 1 证明 9 应该添加到右边
- 添加 2
- 第一次使用 2 和根节点(7)比较
- 此时 res = -1 但是BST还没遍历完
- 第二次遍历的时候 可以直接和 根节点的左子树的节点比较了
- 第二次是用 2 和 4 比较
- 此时 res = -1 并且BST全部遍历,应该在4 的左边添加元素
- 添加 5
- 第一次是用 5 和根节点(7)比较
- 此时 res = -1 但是BST还没遍历完
- 继续遍历, 使用 5 和 4 进行比较
- 此时 res = 1并且BST树遍历完毕,在 4 的右边添加元素
- 第一次是用 5 和根节点(7)比较
- 添加 8
- 第一次是用 8 和 根节点(7)比较
- 此时 res = 1 BST未遍历结束
- 第二次是用 8 和 9 比较
- 此时 res = -1 BST 遍历结束
- 在 9 的右边添加元素
- 此时 res = -1 BST 遍历结束
- 第一次是用 8 和 根节点(7)比较
BST树如下
代码实现
代码实现
这样结构的二叉树在查找元素的时候不需要遍历整个树便可以找到元素的位置时间复杂度会降低很多。
@Override
public void add(E element) {
checkElement(element);
/**
* 添加根节点
*/
if (rootNode == null) {
rootNode = new Node<>(element, null);
size++;
return;
}
/**
* 添加 非根节点
* 1、找到根节点
* 2、对比左右子树,找到要插入的位置
*
* res 当前要插入位置的方向
* parent 当前要插入节点的父节点
* */
Node<E> node = rootNode;
Node<E> parent = null;
int res = 0;
while (node != null) {
// 要添加元素 和 当前元素进行比较
res = compare(element, node.value);
parent = node;
if (res > 0) {
node = node.right;
} else if (res < 0) {
node = node.left;
} else {
/**
* 相等直接就返回了
*/
node.value = element;
return;
}
}
Node<E> newNode = new Node<>(element, parent);
/**
* 找到父节点之后, 开始插入元素
*/
if (res > 0) {
parent.right = newNode;
} else if (res < 0) {
parent.left = newNode;
}
size++;
}
删除元素
对一个BST树进行删除操作的时候要确保删除节点之后的BST树依旧是一个BST树,即满足BST树的特性。此时按照二叉树的特性可以区分为删除叶子节点、度为一的节点、度为2的节点三种。
度:当前节点的子树个数
二叉树的度: 所有节点中度的最大值
删除 叶子节点
删除叶子节点的时候,可以直接删除,删除叶子节点不会破坏BST树的特性。对于上图的示例来说,删除 1 、3、5、8、12 这几个叶子节点不会对BST树的特性造成损坏。
- 删除叶子节点,找到这个节点,直接删除
删除度为一的节点
删除度为一的节点,此时这个节点只有一个子树,也就是说在当前节点的下面只有一个直接相连的节点,那么按照BST的特性来讲,如果是右子树,那么这个子树的右子树上面所有的节点都比要删除这个节点要大。那么可以直接把这个子树的根节点直接去替换要删除的节点,替换之后的二叉树依旧还是满足BST特性的。
删除的思路
- 先找到这个要删除的节点
- 然后改变要删除节点的子树、要删除节点的父节点的指针指向即可
- 简而言之, 改变两条线即可
- 线 一
- 改变要删除节点的父亲节点的子树指向,改变为相应的子树
- 把 9 的右子节点设置为 14
- 改变要删除节点的父亲节点的子树指向,改变为相应的子树
- 线 二
- 改变要删除节点的子树根节点的父节点指向,指向为要删除节点的父节点
- 把 14 的父节点设置为 9
- 改变要删除节点的子树根节点的父节点指向,指向为要删除节点的父节点
删除度为二的节点
BST树的前驱节点和后继节点
-
前驱结点:节点val值小于该节点val值并且值最大的节点
-
后继节点:节点val值大于该节点val值并且值最小的节点
一个节点的度为2 则是证明该节点拥有左右子节点,要想删除此节点之后以及保持BST树的特性,那需要找到一个合适的节点来替代该节点。而这个替代节点需要满足:1、该节点的值大于 左子树所有节点的值, 2、该节点的值小于右子树所有节点的值。
所以替代节点有两个:1、左子树中最大的那个节点,2、右子树中最小的那个节点。找到替代节点之后,直接替代要删除的节点,然后把替代节点删除。
删除步骤
- 找到前驱或者后继节点
- 使用前驱或者后继节点替换要删除的节点
- 删除前驱或者后继节点
前驱或者后继节点的度是多少呢?
- 度为 1 或者 0
- 前驱
- 此时前驱节点是 左子树中最大的节点,最大的节点一定位于右子树中
- 它可能直接是一个叶子节点,此时度为 0
- 也可能是一个拥有左子树的节点,此时度为 1
- 后继
- 此时后继节点是 右子树中最小的节点,最小的节点一定位于左子树中
- 它可能直接是一个叶子节点,此时度为0
- 也可能是一个拥有右节点的节点,此时度为 1
删除元素思路总结
分为 删除叶子节点、删除度为一的节点、删除度为2节点三种情况,而删除度为二节点的步骤中要删除替换节点,这个替换节点的度可能为一,也可能为零,所以整个删除步骤为: 1、先处理度为二的节点、然后处理度为一的节点、最后处理度为零的节点。
代码实现
/**
* 删除节点
* 此时需要分析节点度的大小
*
* @param node
*/
public void remove(Node node) {
if (node == null) {
return;
}
size--;
/**
* 先删除度为 2 的节点
* 然后在处理度为0 、 1 的节点
*/
if (node.hasTwoChildren()) {
/**
* 1、找到后继节点
* 2、后继节点替换 度为2 的节点值
* 3、删除后继节点
*/
Node s = successor(node);
node.value = s.value;
node = s;
}
// 开始处理 度为 0 、 1 的节点
// replacement 为null的时候 是 度为 0
Node replacement = node.left != null ? node.right : node.right;
if (replacement != null) {
// 度为1 的节点,设置替换节点的父亲节点
replacement.parent = node.parent;
// 度为1 的节点 并且还是根节点
if (node.parent == null) {
rootNode = 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) {
/**
* 叶子节点, 并且还是 根节点
*/
rootNode = null;
} else {
/**
* 叶子节点
*/
if (node == node.parent.left) {
node.parent.left = null;
} else {
node.parent.right = null;
}
}
}