二叉搜索树

71 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第16天,点击查看活动详情

二叉搜索树

二叉搜索树(Binary Search Tree)是二叉树的一种,是应用非常广泛的一种二叉树,英文简称为BST

又被称为:二叉查找树、二叉排序树

任意一个节点的值都大于其左子树所有节点的值

任意一个节点的值都小于其右子树所有节点的值

它的左右子树也是一颗二叉搜索树

二叉搜索树可以大大提高搜索数据的效率

二叉搜索树存储的元素必须具备可比较性

image-20220909165146318

二叉搜索树的接口设计

  • int size() //元素的数量
  • boolean isEmpty() //是否为空
  • void clear() //清空所有元素
  • void add(E element) //添加元素
  • boolean contains(E element) //是否包含某元素

添加节点

添加步骤

  • 找到父节点parent
  • 创建新节点node
  • parent.left = node 或者 parent.right = node
 import printer.BinaryTreeInfo;
 ​
 import java.util.Comparator;
 @SuppressWarnings("unchecked")
 public class BinarySearchTree<E> implements BinaryTreeInfo {
     private int size;
     private Node<E> root;
     private Comparator<E> comparator;
     public BinarySearchTree(){
         this(null);
     }public BinarySearchTree(Comparator<E> comparator){
         this.comparator=comparator;
     }
     public int size(){
         return size;
     }
     public boolean isEmpty(){
         return size ==0;
     }
     public void clear(){
 ​
     }
     public void add(E element){
         elementNotNullCheck(element);
 //        添加第一个节点
         if(root==null){
             root=new Node<>(element,null);
             size++;
             return;
         }
 //        添加的不是第一个节点
 //        找到父节点
         Node<E> parent = root;
         Node<E> node = root;
         int cmp =0;
         while(node!=null){
             cmp=compare(element, node.element);
             parent = node;
             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++;
     }
     public void remove(E element){
 ​
     }
     public boolean contains(E element){
         return node(element)!=null;
     }
 ​
     /**
      *
      * @return 返回值等于0,代表e1和e2相等;返回值大于0,代表e1大于e2;返回值小于0,代表e1小于e2;
      */
     private int compare(E e1,E e2){
         if(comparator!=null) {
             return comparator.compare(e1, e2);
         }
         return ((Comparable<E>)e1).compareTo(e2);
     }
     private void elementNotNullCheck(E element){
         if(element==null){
             throw new IllegalArgumentException("element must not be null");
         }
     }
 ​
     @Override
     public Object root() {
         return root;
     }
 ​
     @Override
     public Object left(Object node) {
         return ((Node<E>)node).left;
     }
 ​
     @Override
     public Object right(Object node) {
         return ((Node<E>)node).right;
     }
 ​
     @Override
     public Object string(Object node) {
         return ((Node<E>)node).element;
     }
 ​
     private static class Node<E> {
         E element;
         Node<E> left;
         Node<E> right;
         Node<E> parent;
         public Node(E element,Node<E> parent){
             this.element=element;
             this.parent=parent;
         }
     }
 }
 ​

二叉树的遍历

遍历是数据结构中的常见操作

把所有元素都访问一遍

  • 根据节点访问顺序的不同,二叉树的常见遍历方式有4种

    前序遍历(Preorder Traversal)

    中序遍历(Inorder Traversal)

    后序遍历(Postorder Traversal)

    层序遍历(Level Order Traversal)

前序遍历

访问顺序

根节点、前序遍历左子树、前序遍历右子树

image-20220911154402852

     public void preorderTraversal(){
         preorderTraversal(root);
     }
     private void preorderTraversal(Node<E> node){
         if(node==null)return;
         System.out.println(node.element);
         preorderTraversal(node.left);
         preorderTraversal(node.right);
     }

中序遍历

访问顺序

中序遍历左子树、根节点、中序遍历右子树

image-20220911161046708

二叉搜索数的中序遍历结果是升序或者降序的

 public void inorderTraversal(){
     inorderTraversal(root);
 }
 private void inorderTraversal(Node<E> node){
     if(node==null)return;
     inorderTraversal(node.left);
     System.out.println(node.element);
     inorderTraversal(node.right);
 }

后序遍历

访问顺序

后序遍历左子树、后序遍历右子树、根节点

image-20220911170601055

     public void postorderTraversal(){
         postorderTraversal(root);
     }
     private void postorderTraversal(Node<E> node){
         if(node==null)return;
         postorderTraversal(node.left);
         postorderTraversal(node.right);
         System.out.println(node.element);
     }

层序遍历

访问顺序

从上到下、从左到右依次访问每一个节点

image-20220911170956324

实现思路:使用队列

  1. 将根节点入队
  2. 循环执行以下操作,直到队列为空
  • 将队头节点A出队,进行访问
  • 将A的左子节点入队
  • 将A的右子节点入队
 public void levelOrderTraversal(){
     if(root==null)return;
     Queue<Node<E>> queue = new LinkedList<>();
     queue.offer(root);
     while (!queue.isEmpty()){
         Node<E> node = queue.poll();
         System.out.println(node.element);
         if(node.left!=null){
             queue.offer(node.left);
         }
         if(node.right!=null){
             queue.offer(node.right);
         }
     }
 }

设计遍历接口

 public static interface Visitor<E>{
     void visit(E element);
 }
 public void preorder(Visitor<E> visitor) {
         preorder(root, visitor);
     }
     
 private void preorder(Node<E> node, Visitor<E> visitor) {
         if (node == null || visitor.stop) return;
         
         visitor.visit(node.element);
         preorder(node.left, visitor);
         preorder(node.right, visitor);
         
     }
     
     public void inorder(Visitor<E> visitor) {
         
         inorder(root, visitor);
     }
     
     private void inorder(Node<E> node, Visitor<E> visitor) {
         if (node == null || visitor.stop) return;
         
         inorder(node.left, visitor);
         visitor.visit(node.element);
         inorder(node.right, visitor);
     }
     
     public void postorder(Visitor<E> visitor) {
         
         postorder(root, visitor);
     }
     
     private void postorder(Node<E> node, Visitor<E> visitor) {
         if (node == null || visitor.stop) return;
         
         postorder(node.left, visitor);
         postorder(node.right, visitor);
         visitor.visit(node.element);
     }
     
     public void levelOrder(Visitor<E> visitor) {
         if (root == null || visitor == null) return;
         
         Queue<Node<E>> queue = new LinkedList<>();
         queue.offer(root);
         
         while (!queue.isEmpty()) {
             Node<E> node = queue.poll();
             visitor.visit(node.element); 
             
             if (node.left != null) {
                 queue.offer(node.left);
             }
             
             if (node.right != null) {
                 queue.offer(node.right);
             }
         }
     }
 ​
 bst.preorder(new Visitor<Integer>() {
     public void visit(Integer element) {
         System.out.print(element + " ");
     }
 });
 System.out.println();
     bst.inorder(new Visitor<Integer>() {
     public void visit(Integer element) {
         System.out.print(element + " ");
     }
 });
 ​
 bst.postorder(new Visitor<Integer>() {
     public void visit(Integer element) {
         System.out.print(element + " ");
     }
 });
 bst.levelOrder(new Visitor<Integer>() {
     public void visit(Integer element) {
         System.out.print(element + " ");
     }
 });

增强遍历接口

 public void preorder(Visitor<E> visitor) {
     if (visitor == null) return;
     preorder(root, visitor);
 }
 ​
 private void preorder(Node<E> node, Visitor<E> visitor) {
     if (node == null || visitor.stop) return;
 ​
     visitor.stop = visitor.visit(node.element);
     preorder(node.left, visitor);
     preorder(node.right, visitor);
 }
 ​
 public void inorder(Visitor<E> visitor) {
     if (visitor == null) return;
     inorder(root, visitor);
 }
 ​
 private void inorder(Node<E> node, Visitor<E> visitor) {
     if (node == null || visitor.stop) return;
 ​
     inorder(node.left, visitor);
     if (visitor.stop) return;
     visitor.stop = visitor.visit(node.element);
     inorder(node.right, visitor);
 }
 ​
 public void postorder(Visitor<E> visitor) {
     if (visitor == null) return;
     postorder(root, visitor);
 }
 ​
 private void postorder(Node<E> node, Visitor<E> visitor) {
 if (node == null || visitor.stop) return;
 ​
 postorder(node.left, visitor);
 postorder(node.right, visitor);
 if (visitor.stop) return;
 visitor.stop = visitor.visit(node.element);
 }
 ​
 public void levelOrder(Visitor<E> visitor) {
     if (root == null || visitor == null) return;
 ​
     Queue<Node<E>> queue = new LinkedList<>();
     queue.offer(root);
 ​
     while (!queue.isEmpty()) {
         Node<E> node = queue.poll();
         if (visitor.visit(node.element)) return;
 ​
         if (node.left != null) {
         queue.offer(node.left);
         }
 ​
         if (node.right != null) {
         queue.offer(node.right);
         }
     }
 }
 ​
 static void test9() {
     Integer data[] = new Integer[] {
         7, 4, 9, 2, 1
     };
 ​
     BinarySearchTree<Integer> bst = new BinarySearchTree<>();
     for (int i = 0; i < data.length; i++) {
         bst.add(data[i]);
     }
     BinaryTrees.println(bst);
 ​
     bst.preorder(new Visitor<Integer>() {
         public boolean visit(Integer element) {
             System.out.print(element + " ");
             return element == 2 ? true : false;
         }
     });
     System.out.println();
 ​
     bst.inorder(new Visitor<Integer>() {
         public boolean visit(Integer element) {
             System.out.print(element + " ");
             return element == 4 ? true : false;
         }
     });
     System.out.println();
 ​
     bst.postorder(new Visitor<Integer>() {
         public boolean visit(Integer element) {
             System.out.print(element + " ");
             return element == 4 ? true : false;
         }
     });
     System.out.println();
 ​
     bst.levelOrder(new Visitor<Integer>() {
         public boolean visit(Integer element) {
             System.out.print(element + " ");
             return element == 9 ? true : false;
         }
     });
     System.out.println();
 }
 ​

计算二叉树的高度

 private int height(Node<E> node){
     if(node==null) return 0;
     return 1 + Math.max(height(node.left),height(node.right));
 }
 ​
 public int height() {
     if (root == null) return 0;
 ​
     // 树的高度
     int height = 0;
     // 存储着每一层的元素数量
     int levelSize = 1;
     Queue<Node<E>> queue = new LinkedList<>();
     queue.offer(root);
 ​
     while (!queue.isEmpty()) {
         Node<E> node = queue.poll();
         levelSize--;
 ​
         if (node.left != null) {
             queue.offer(node.left);
         }
 ​
         if (node.right != null) {
             queue.offer(node.right);
         }
 ​
         if (levelSize == 0) { // 意味着即将要访问下一层
             levelSize = queue.size();
             height++;
             }
     }
 ​
     return height;
 }
 ​

判断一棵树是否为完全二叉树

  • 如果树为空,返回false

  • 如果树不为空,开始层序遍历二叉树(用队列)

    如果node.left!=null&&node.right!=null,将node.left、node.right按顺序入队

    如果node.left==null&&node.right!=null,返回false

    如果node.left!=null&&node.right==null或者node.left==null&&node.right==null那么后面遍历的节点应该都为叶子节点,才是完全二叉树,否则返回false

 public boolean isComplete(){
     if(root==null) return false;
     Queue<Node <E>> queue = new LinkedList<>();
     queue.offer(root);
     boolean leaf=false;
     while(!queue.isEmpty()){
         Node<E> node = queue.poll();
         if(leaf&&!node.isLeaf()) return false;
         if(node.left!=null&&node.right!=null){
             queue.offer(node.left);
             queue.offer(node.right);
         }else if(node.left==null&&node.right!=null){
         return false;
         }else{
             leaf=true;
             if(node.left!=null){
                 queue.offer(node.left);
             }
         }
     }
     return true;
 }
 public boolean isComplete(){
     if(root==null) return false;
     Queue<Node<E>> queue = new LinkedList<>();
     queue.offer(root);
     boolean leaf = false;
     while(!queue.isEmpty()){
         Node<E> node = queue.poll();
         if(leaf&&!node.isLeaf()) return false;
         if(node.left!=null){
             queue.offer(node.left);
         }else if(node.right!=null){
             //node.left==null&&node.right!=null
             return false;
         }
         if(node.right!=null){
             queue.offer(node.right);
         }else{
             //node.right==null
             leaf=true;
         }
     }
     return true;
 }

image-20220927205940077

重构二叉树

以下结果可以保证重构出唯一的一颗二叉树

  • 前序遍历+中序遍历
  • 后序遍历+中序遍历
  • 前序遍历+后序遍历
  • 如果它是一颗真二叉树,结果是唯一的
  • 否则结果不唯一

前序遍历+中序遍历重构二叉树

  • 前序遍历:4 2 1 3 6 5
  • 中序遍历:1 2 3 4 5 6

根 左 右

左 根 右

  1. 前序:先得出4为根节点
  2. 中序:得出 1 2 3 在左边 5 6 在右边
  3. 前序:得出 2 6 为第二层的节点
  4. 综合得出 最后结果

image-20220928120337059

后序遍历+中序遍历重构二叉树

  • 后序遍历 1 3 2 5 6 4
  • 中序遍历 1 2 3 4 5 6

左 右 根

左 根 右

  1. 后序:得出 4为根节点
  2. 中序:得出 1 2 3 为左子树 5 6 为右子树元素
  3. 后序:得出2 6为根节点
  4. 综合得出 最后结果

前驱节点

  • 中序遍历时的前一个节点
  • 如果是二叉搜索树,前驱节点就是前一个比它小的节点
  • node.left !=null
  • predecessor = node.left.right.right.right...
  • 终止条件:right为null
  • node.left == null && node.parent != null
  • predecessor = node.parent.parent.parent...
  • 终止条件:node在parent的右子树中
  • node.left == null && node.parent == null
  • 那就没有前驱节点
 private Node<E> predecessor(Node<E> node){
     if(node==null) return null;
     //前驱节点在左子树当中
     Node<E> p = node.left;
     if(p!=null){
         while(p.right!=null){
             p=p.right;
         }
         return p;
     }
     while(node.parent!=null&&node==node.parent.left){
         node=node.parent;
     }
     return node.parent;
 }

后继节点

  • 中序遍历时的后一个节点
  • 如果是二叉搜索树,后继节点就是后一个比它大的节点
  • node.right!=null
  • successor=node.right.left.left.left...
  • 终止条件:left为null
  • node.right == null && node.parent !=null
  • successor=node.parent.parent.parent...
  • 终止条件:node在parent的左子树中
  • node.right == null && node.parent == null
  • 那就没有后继节点
 private Node<E> successor(Node<E> node){
     if(node==null) return null;
     //前驱节点在左子树当中
     Node<E> p = node.right;
     if(p!=null){
         while(p.left!=null){
             p=p.left;
         }
         return p;
     }
     while(node.parent!=null&&node==node.parent.right){
         node=node.parent;
     }
     return node.parent;
 }

remove

删除节点-叶子节点

  • 直接删除

  • node == node.parent.left

    • node.parent.left=null
  • node == node.parent.right

    • node.parent.right=null
  • node.parent == null

    • root = null

删除节点-度为1的节点

  • 用子节点替代原节点的位置
  • child 是 node.left 或者 child 是 node.right
  • 用child替代node的位置
  • 如果node是左子节点
  • child.parent=node.parent
  • node.parent.left=child
  • 如果node是右子节点
  • child.parent = node.parent
  • node.parent.right = child
  • 如果node是根节点
  • root=child
  • child.parent=null

删除节点-度为2的节点

  • 先用前驱或者后继节点的值覆盖原节点的值
  • 然后删除相应的前驱或者后继节点
  • 如果一个节点的度为2,那么它的前驱、后继节点的度只可能是1和0

代码实现

 public void remove(E element){
     remove(node(element));
 }
 public boolean contains(E element){
     return node(element)!=null;
 }
 private void remove(Node<E> node){
     if(node==null) return;
     size--;
     if(node.hasTwoChildren()){//度为2的节点
         //找到后继节点
         Node<E> s = successor(node);
         //用后继节点的值覆盖度为2的节点的值
         node.element=s.element;
         //删除后继节点
         node=s;
     }
     //删除node节点(node的度必然是1或者0)
     Node<E> replacement = node.left!=null? node.left : node.right;
     
     if(replacement!=null){//node是度为1的节点
         //更改parent
         replacement.parent = node.parent;
         //更改parent的left、right的指向
         if(node.parent==null){//node是度为1的节点并且是根节点
             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){//node是叶子节点并且是根节点
         root=null;
     }else{//node是叶子节点,但不是根节点
         if(node==node.parent.right){
             node.parent.right=null;
         }else{//node==node.parent.left
             node.parent.left=null;
         }
     }
 }
 private Node<E> node(E element){
     Node<E> node = root;
     while(node!=null){
         int cmp = compare(element,node.element);
         if(cmp==0) return node;
         if(cmp>0){
             node=node.right;
         }else{
             node=node.left;
         }
     }
     return null;
 }