简单介绍
什么是二叉搜索树?
二叉搜索树简称BST(Binary Search Tree),是一种经典的数据结构。如果二叉搜索树的左子树不空,则左子树上所有节点的值均小于它的根节点的值;若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;它的左右子树也分别为二叉搜索树。
为什么需要二叉搜索树?
二叉搜索树采用二分思维将数据按照规则组装在一个树形结构中,大大提高了数据检索的效率。二叉搜索树既有链表的快速插入与删除操作的特点,又有数组快速查找的优势。所以应用广泛。
二叉搜索树的特性
基本的二分特性已经在「什么是二叉搜索树?」中讲过,本章不再重复说明。
操作花费的时间复杂度?
二叉搜索树查找,删除,插入操作,所花的时间都和树的高度成正比。因此,如果共有n个元素,那么平均每次操作需要O(logn)的时间。
而情况下最差二叉搜索树会退化成线性表,导致时间复杂度变为 O(n) 。然后衍生出了AVL树来解决此类问题。
中序遍历后是有序数组?
我们对这样一棵二叉树进行中序遍历
得出序列:[21, 26, 30, 50, 60, 66, 68, 70]
对二叉搜搜树进行中序遍历后结果是升序排序的序列。
二叉搜索树的编码
二叉搜索树节点的定义
public class Node<T extends Comparable<T>> {
/**
* 数据域
*/
public T data;
/**
* 节点子树的高度
*/
public int height;
/**
* 左孩子
*/
public Node<T> leftChild;
/**
* 右孩子
*/
public Node<T> rightChild;
public Node(T data) {
this(data, null, null);
}
public Node(T data, Node<T> leftChild, Node<T> rightChild) {
this.data = data;
this.leftChild = leftChild;
this.rightChild = rightChild;
}
}
插入节点操作
插入流程:
-
递归找到插入节点的位置
- 如果值比当前节点值大,则到右子树去寻找插入位置
- 如果值比当前节点值小,则到左子树去寻找插入位置
-
通过返回值将插入位置传递到上一层,组织好树的结构
private Node<T> insert(Node<T> root, T data) {
// 根节点为空,说明树为空,则创建一颗树
if (root == null) {
return new Node<>(data);
} else {
if (compare(root, data) > 0) {
root.leftChild = insert(root.leftChild, data);
} else if (compare(root, data) < 0) {
root.rightChild = insert(root.rightChild, data);
}
return root;
}
}
删除节点操作
删除节点是二叉搜索树中比较麻烦的工作了,主要问题在于被删除节点存在子树如何重新组织,使操作后的二叉搜索树仍然满足性质。
节点的类型有三种:
- 叶子节点;
- 只有左子树或只有右子树;
- 既有左子树又有右子树。
删除流程:
- 删除的节点是叶子节点: 直接删除
- 删除的节点只有左子树或只有右子树: 将父节点指向待删除的节点的唯一子节点
- 删除的节点既有左子树又有右子树: 寻找右子树最小值或者左子树最大值替换该节点
public void deleteNode(T data) {
if (data == null) {
return;
}
root = delete(root, data);
}
private Node<T> delete(Node<T> root, T data) {
if (compare(root, data) < 0) {
root.rightChild = delete(root.rightChild, data);
} else if (compare(root, data) > 0) {
root.leftChild = delete(root.leftChild, data);
} else {
// 该节点拥有左右子树
if (root.leftChild != null && root.rightChild != null) {
T temp = getMin(root.rightChild);
root.data = temp;
root.rightChild = delete(root.rightChild, temp);
} else {
if (root.leftChild != null) {
root = root.leftChild;
} else {
root = root.rightChild;
}
}
}
return root;
}
getMin方法和getMax方法
我们一般在上层调用getMin(root.rightChild)或者getMax(root.leftChild)来分别寻找右子树的最小值或者左子树的最大值。
private T getMin(Node<T> root) {
Node<T> temp = root;
while (temp.leftChild != null) {
temp = temp.leftChild;
}
return temp.data;
}
private T getMax(Node<T> root) {
Node<T> temp = root;
while (temp.rightChild != null) {
temp = temp.rightChild;
}
return temp.data;
}
建树操作
建树流程:
- 通过遍历数组的方式以
createTree方法为入口,实际调用insert方法,最后创建二叉搜索树。
public void createTree(T data) {
if (data == null) {
return;
}
root = insert(root, data);
}
功能测试
测试建BST树:
public class TestBsTree {
private static final Integer[] arrays = new Integer[]{50, 66, 60, 26, 21, 30, 70, 68};
@Test
public void testCreateBsTree(){
BsTree<Integer> bsTree = new BsTree<>();
for (Integer data : arrays) {
bsTree.createTree(data);
}
bsTree.printTree();
}
}
测试结果:
前序遍历: 50 26 21 30 66 60 70 68
中序遍历: 21 26 30 50 60 66 68 70
后序遍历: 21 30 26 60 68 70 66 50
当前树的图示如下:
测试删除:
@Test
public void testDeleteNode() {
BsTree<Integer> bsTree = new BsTree<>();
for (Integer data : arrays) {
bsTree.createTree(data);
}
bsTree.deleteNode(50);
bsTree.printTree();
}
测试结果:
前序遍历: 60 26 21 30 66 70 68
中序遍历: 21 26 30 60 66 68 70
后序遍历: 21 30 26 68 70 66 60
小结
本文算是一篇补充文章,是为了后面的AVL树,红黑树等由二叉搜索树衍生出来的数据结构服务的。
关于AVL树和红黑树可以查看我的博客:
本文完整源码可以在我的仓库中找到:github.com/pixel-revol…
参考文章: