二叉搜索树 的基本操作

154 阅读4分钟

1.二叉搜索树

1.1 什么是二叉搜索树?

  二叉搜索树又叫二叉排序树,二叉搜索树是一种二叉树的数据结构,其中每个节点的左子节点的值都小于该节点的值,右子节点的值都大于该节点的值,它的左右子树也分别为二叉搜索树,同时节点之间不能重复。

演示文稿1.png

  二叉搜索树是用来干嘛的呢?

  1. 快速查找:由于二叉搜索树的特性,可以通过比较查找值与当前节点的大小关系,快速地定位到目标节点,从而实现快速查找。
  2. 排序:二叉搜索树的中序遍历结果是有序的,可以通过中序遍历的方式对二叉搜索树进行排序操作。
  3. 快速插入和删除:在二叉搜索树中插入或删除一个节点时,只需要沿着树的路径找到对应的位置即可,操作时间复杂度为 O(log2n)O(log_2n)
  4. 作为其他数据结构的基础:许多高级的数据结构,如平衡二叉搜索树、B树等都是基于二叉搜索树的思想设计的,因此二叉搜索树是许多数据结构的基础。

1.2 二叉搜索树的查找操作

  二叉搜索树的查找操作通常包含以下步骤:

  1. 从根节点开始查找。
  2. 如果当前节点为空,则目标值不存在于树中,返回 null 或者抛出异常。
  3. 如果当前节点的值等于目标值,则返回该节点。
  4. 如果目标值小于当前节点的值,则在左子树中继续查找,否则在右子树中查找。
  5. 重复步骤 2-4,直到找到目标节点或者当前节点为空。
public class MySearchTree {
    //节点
    private static class Node{
        int val;
        Node left;
        Node right;
        public Node(int val){
            this.val = val;
        }
    }

    //根节点
    private Node root = null;

    //查找指定的元素的节点
    public Node search(int val){
        Node cur = root;
        while(cur != null){
            if(cur.val < val){
                cur = cur.right;
            }else if(cur.val > val){
                cur = cur.left;
            }else{
                return cur;
            }
        }
        //没有该节点
        return null;
    }
    
}

  它的时间复杂度为O(log2n)O(log_2n),如果是如下形式的搜索树,那么时间复杂度就是O(n)O(n)

演示文稿1.1.png

1.3 二叉搜索树的插入操作

  二叉搜索树的插入步骤如下:

  1. 从根节点开始,比较目标值和当前节点的值大小关系,如果目标值小于当前节点的值,则向左子树查找;如果目标值大于当前节点的值,则向右子树查找;如果目标值等于当前节点的值,则说明目标节点已经存在于树中,无需再次插入。
  2. 重复步骤 1 直到找到最终的空节点。
  3. 在空节点处创建一个新节点,将目标值存储在该节点中。
  4. 将新节点插入到二叉搜索树中。
public boolean insert(int val){
    //特判,根节点为空直接插入
    if(root == null){
        root = new Node(val);
        return true;
    }

    Node cur = root;
    Node parent = null; // 记录cur的父节点
    while(cur != null){
        parent = cur;
        if(cur.val < val){
            cur = cur.right;
        } else if(cur.val > val){
            cur = cur.left;
        } else {
            //当树中有 val 的时候
            return false;
        }
    }
    //插入
    Node newNode = new Node(val);
    //判断插入到左子树还是右子树
    if(val < parent.val){
        parent.left = newNode;
    }else {
        parent.right = newNode;
    }
    return true;
}

1.4 二叉搜索树的删除操作

  二叉搜索树的删除操作的步骤可以分为以下几步:

  1. 查找目标节点:从根节点开始,比较目标值和当前节点的值大小关系,如果目标值小于当前节点的值,则向左子树查找;如果目标值大于当前节点的值,则向右子树查找;如果目标值等于当前节点的值,则说明找到了目标节点。如果目标节点不存在于树中,则删除操作结束,不需要进行任何操作。

  2. 分情况讨论:设待删除结点为 cur,待删除结点的双亲结点为parent

    • cur.left = null 的时候

      1. cur 是 root,则 root = cur.right
      2. cur 不是 root,cur 是 parent.left,则 parent.left = cur.right
      3. cur 不是 root,cur 是 parent.right,则 parent.right = cur.right
    • cur.right = null 的时候

      1. cur 是 root,则 root = cur.left
      2. cur 不是 root,cur 是 parent.left,则 parent.left = cur.left
      3. cur 不是 root,cur 是 parent.right,则 parent.right = cur.left
    • cur.left != null && cur.right != null 的时候

      1. 需要使用替换法进行删除,即在它的右子树中寻找关键码最小的节点(最左边的节点),用它的值填补到被删除节点中,再来处理该节点的删除问题

演示文稿1.gif

  1. 返回删除后的根节点。
//删除操作
public Node remove(int val){
    //先找到待删除的节点
    Node parent = null; //记录 cur 的父节点
    Node cur = root;
    while (cur != null){
        if(cur.val == val) {
            //进行删除操作
            removeNode(parent,cur);
            return root;
        } else if(val < cur.val){
            parent = cur;
            cur = cur.left;
        } else {
            parent = cur;
            cur = cur.right;
        }
    }
    return null;
}
private void removeNode(Node parent,Node cur){
    //三种情况
    if(cur.left == null){ //cur 没有左树
        if(cur == root){
            root = cur.right;
        }else if(cur == parent.left){
            parent.left = cur.right;
        }else if(cur == parent.right){
            parent.right = cur.right;
        }
    } else if(cur.right == null){ //cur 没有右树
        if(cur == root){
            root = cur.left;
        }else if(cur == parent.left){
            parent.left = cur.left;
        }else if(cur == parent.right){
            parent.right = cur.left;
        }
    } else { //cur 有左右子树的情况下删除【重要】
        Node target = cur.right; //代表右子树最小的节点
        Node targetParent = cur; //代表最小的节点的父节点
        while (target.left != null){
            //根据搜索树的性质,最小的节点在最左边
            targetParent = target;
            target = target.left;
        }
        cur.val = target.val;//复制
        if(target == targetParent.left){
            targetParent.left = target.right; 
        }else{ //当 cur 的右树只有一个节点的时候,也就是 target == cur.right
            targetParent.right = target.right;
        }
    }
}