2分钟,复习一遍二叉搜索树(BST树)!

357 阅读3分钟

简单介绍

什么是二叉搜索树?

二叉搜索树简称BST(Binary Search Tree),是一种经典的数据结构。如果二叉搜索树的左子树不空,则左子树上所有节点的值均小于它的根节点的值;若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;它的左右子树也分别为二叉搜索树。

为什么需要二叉搜索树?

二叉搜索树采用二分思维将数据按照规则组装在一个树形结构中,大大提高了数据检索的效率。二叉搜索树既有链表的快速插入与删除操作的特点,又有数组快速查找的优势。所以应用广泛。

二叉搜索树的特性

基本的二分特性已经在「什么是二叉搜索树?」中讲过,本章不再重复说明。

操作花费的时间复杂度?

二叉搜索树查找,删除,插入操作,所花的时间都和树的高度成正比。因此,如果共有n个元素,那么平均每次操作需要O(logn)的时间。

而情况下最差二叉搜索树会退化成线性表,导致时间复杂度变为 O(n) 。然后衍生出了AVL树来解决此类问题。

image.png

中序遍历后是有序数组?

我们对这样一棵二叉树进行中序遍历

image.png

得出序列:[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

当前树的图示如下:

image.png

测试删除:

 @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…

参考文章: