【数据结构】二叉搜索树BST -> 基础实现篇

目录

 🌟前言

 🌟基础实现

1.1 增

1.2 查

1.3 删

1.4 改

1.5 打印BST

完整代码实现 


🌟前言

        二叉搜索树(Binary Search Tree)简称BST。BST主要有三个特性:

        (1)对于BST的每一个节点node,左子树节点值<根节点值<右子树节点值;

        (2)对于BST的每一个节点node,其左侧子树和右侧子树都是BST;

        (3)BST的中序遍历结果是有序的 -> 升序!(非常重要的性质!)

                而且BST中不存在重复的值!

 🌟基础实现

        首先肯定是先定义一个类TreeNode。主要包括根节点,树中节点的数量。节点的值,该节点的左孩子,右孩子,以及构造方法。

    private TreeNode root;//根节点
    private int size;//有效节点个数
    private static class TreeNode{
        int val;
        TreeNode left;
        TreeNode right;

        public TreeNode() {
        }
        public TreeNode(int val) {
            this.val = val;
        }

1.1 增

        要添加一个新节点,一定是保存在叶子节点的位置。至于是树的哪一边,需要判断该值与根节点的大小:如果该节点值小于根节点,则在左子树插入;如果该值大于根节点值,则在右子树插入。直到碰见空节点为止。

代码实现:

    //外部调用
    public void add(int val){
        //内部维护一个addProx函数
        root = addProx(root,val);
    }
    private TreeNode addProx(TreeNode root, int val) {
        //树为空
        if(root == null){
            //将新传入的val构建为一个新的节点
            TreeNode node = new TreeNode(val);
            size++;
            return node;//返回新的树的根节点
        } else if (val < root.val) {//在左子树插入
            root.left = addProx(root.left,val);
            return root;
        }
        root.right = addProx(root.right,val);
        return root;
    }

代码的调用过程:

 1.2 查

        查找这里我们主要实现三部分:分别是查找最大值,查找最小值。

 ✍ 查找最大值

主要思路:

        因为BST的特点,左子树节点值<根节点值<右子树节点值,所以最大值一定在右子树部分。那我们不断的向BST的右侧遍历,直到遇见第一个右孩子为空的根节点,则该节点保存的就一定是最大值。

 代码实现: 也是递归的方式(也很好理解)

    public int searchMax(){
        if(root == null){
            throw new NoSuchElementException("no element");
        }
        TreeNode maxNode = findMax(root);
        return maxNode.val;
    }
    private TreeNode findMax(TreeNode root) {
        if(root.right == null){
            return root;
        }
        return findMax(root.right);
    }

        递归的过程:

 ✍ 查找最小值

        同理,如果要找到该BST的最小值,则最小值一定在树的左侧,所以不断在左子树遍历,直到遇见第一个左孩子为空的根节点,则该节点的值就是最小值。

代码实现:

    public int searchMin(){
        if(root == null){
            throw new NoSuchElementException("no element");
        }
        TreeNode minNode = findMin(root);
        return minNode.val;
    }

    private TreeNode findMin(TreeNode root) {
        if(root.left == null){
            return root;
        }
        return findMin(root.left);
    }

  递归过程:

1.3 删

        删除这里我们主要实现三部分:分别是删除最大值,删除最小值,删除任意值。

✍ 删除最大值

        主要思路: 先找到最大值节点,然后记录下来该节点的左孩子值(此时肯定不存在右孩子),然后将该左孩子拼接到原树中。

代码实现:

    public int removeMax(){
        TreeNode maxNode = findMax(root);
        root = removeMax(root);
        return maxNode.val;
    }
    //内部删除最大值函数
    private TreeNode removeMax(TreeNode root) {
        if(root.right == null){
            //当前的root节点就是最大值
            TreeNode left = root.left;
            root.left = root = null;
            size--;
            return left;
        }
        root.right = removeMax(root.right);
        return root;
    }

 递归过程:

 ✍ 删除最小值

        删除最小值也是同理:先递归的找到最小值节点,然后保存该节点的右孩子,删除最小值节点之后,将右孩子与原根节点拼接。

代码实现:

    public int removeMin(){
        TreeNode node = findMin(root);//找到最小值的节点
        root = removeMin(root);
        return node.val;

    }
    //内部删除最小值函数
    private TreeNode removeMin(TreeNode root) {
        if(root.left == null){//root就是要被删除的节点
            TreeNode right = root.right;//先保存该节点的右孩子
            root.right = root = null;//清除该节点
            return right;//返回该右孩子
        }
        root.left = removeMin(root.left);//递归调用
        return root;
    }

✍ 删除任意值

        删除任意值是这三种里面情况最复杂的。函数目标:在以当前root为根的BST中删除值为val的节点,并返回删除后的树根节点。

代码实现:

删除一个值,主要存在以下三种情况:

🌼 待删除值小于根节点的值,则要在左子树中删除;

🌼 待删除值大于根节点的值,则要在右子树中删除;

🌼 待删除值等于根节点的值,则该根节点就是要被删除的节点:而这种情况中,又存在以下三种情况:

(1)待删除节点只有左孩子,没有右孩子:则为最大值的删除

(2)待删除节点只有右孩子,没有左孩子,则为最小值的删除

(3)待删除节点既有左孩子,又有右孩子,则这种情况最为复杂。我们来重点分析一下:这种情况的关键在于删除了该节点之后,该节点位置就是空,那么我该从哪里找一个新的节点代替这个被删除节点呢?看下图:

        分析:首先我需要一个节点顶替30节点的位置,而且这个节点还要满足BST的特性:左子树节点值<根节点值<右子树值:以50为节点的这棵树为例:新节点的值一定在39和58之间,所以只有该被删除节点的左子树的最大值42和被删除节点右子树的最小值53满足条件,可以作为新的后节点。以53为例,接下来我们看一下怎么将53这个节点拼接到原来50的位置上。

 大致过程:(原谅我这个手残党😭图画的有点丑)注意步骤23的顺序不能交换,否则在拼接右子树的时候,要先删除successor,此时删除的就不是successor为53这个节点了。

 代码实现:

    public void remove(int val) {
        remove(root,val);
    }
    public TreeNode remove(TreeNode root, int val){
        //base case
        if(root == null){
            return null;
        } else if (val < root.val) {
            //在左子树的删除操作:递归调用
            root.left = remove(root.left,val);
            return root;
        } else if (val > root.val) {
            //在右子树的删除:递归调用
            root.right = remove(root.right,val);
            return root;
        } else{
            //当前节点就是要被删除的节点
            //(1)当该节点只有左孩子,没有右孩子->相当于最大值的删除
            if(root.right == null){
                TreeNode left = root.left;
                root.left = root = null;
                size --;
                return left;
            }
            //(2)当该节点只有右孩子,没有左孩子->相当于最小值的删除
            if(root.left == null){
                TreeNode right = root.right;
                root.right = root = null;
                size --;
                return right;
            }
            //(3)当前节点左右孩子都有,此时删除该节点,则需要一个新的节点顶替过来:
            //后来发现:该节点要么是该删除节点的左子树的最大值,要么是该删除节点的右子树的最小值
            TreeNode successor = findMin(root.right);//找到该顶替的节点:右子树的最小值
            successor.right = removeMin(root.right);//断开successor节点的右子树分支
            successor.left = root.left;
            root.left = root.right = root = null;//清空该根节点以及所有的子树分支
            return successor;//successor就是新的树根
        }
    }

1.4 改

        一般BST中不使用修改这个功能,因为BST中不存在重复的值。如果要修改,实际上是先删除后增加。不能直接修改(要保证BST的性质不能变)。

1.5 打印BST

        能读懂打印出来的含义:28为根节点,16和40为28的左右节点。16的左孩子为13,右孩子为19;13的左孩子为Null,右孩子为15...以此类推。

完整代码实现


public class MyBST {
    private TreeNode root;//根节点
    private int size;//有效节点个数
    private static class TreeNode{
        int val;
        TreeNode left;
        TreeNode right;

        public TreeNode() {
        }
        public TreeNode(int val) {
            this.val = val;
        }
    }
    /**
     * 增:插入新节点一定保存在叶子节点:不断和根节点比较大小,若新的值小于根节点,则放在左子树的插入;否则在右子树的插入。直到碰到空节点为止。
     */
    //外部调用
    public void add(int val){
        //内部维护一个addProx函数
        root = addProx(root,val);
    }
    private TreeNode addProx(TreeNode root, int val) {
        //树为空
        if(root == null){
            //将新传入的val构建为一个新的节点
            TreeNode node = new TreeNode(val);
            size++;
            return node;//返回新的树的根节点
        } else if (val < root.val) {//在左子树插入
            root.left = addProx(root.left,val);
            return root;
        }
        root.right = addProx(root.right,val);
        return root;
    }
    /**
     * 查:查找最大最小和任意值。 最大值:不断向BST右侧遍历,碰到的第一个root.right为空的情况,则该根节点的值为最大值;最小值:左,第一个root.left==null
     * 任意值的查询就是二分查找。若走到空树还没找到特定值,则说明该值在BST中不存在。
     * */
    public int searchMax(){
        if(root == null){
            throw new NoSuchElementException("no element");
        }
        TreeNode maxNode = findMax(root);
        return maxNode.val;
    }
    private TreeNode findMax(TreeNode root) {
        if(root.right == null){
            return root;
        }
        return findMax(root.right);
    }
    //查找最小值
    public int searchMin(){
        if(root == null){
            throw new NoSuchElementException("no element");
        }
        TreeNode minNode = findMin(root);
        return minNode.val;
    }

    private TreeNode findMin(TreeNode root) {
        if(root.left == null){
            return root;
        }
        return findMin(root.left);
    }
    //查找任意值
    public boolean searchNum(int val){
        if(root == null){
            throw new NoSuchElementException("root is null,no such element!");
        }else{
            return searchNum(root,val);
        }
    }
    //内部定义searchNum函数
    private boolean searchNum(TreeNode root, int val) {
        if(val == root.val){
            return true;
        } else if (val < root.val) {
            searchNum(root.left,val);
            return true;
        } else {
            searchNum(root.right,val);
            return true;
        }
    }

    /**
     * 判断SBT中是否包含某个值
     */
    public boolean contains(int val){
        boolean result = containsProx(root,val);
        return result;
    }
    private boolean containsProx(TreeNode root, int val) {
        if(root == null){
            return false;
        }
        if(root.val == val){
            return true;
        } else if (root.val > val) {
            return containsProx(root.left,val);
        }
        return containsProx(root.right,val);
    }
    /**
     * 删除:最大值,最小值
     */ 
    public int removeMax(){
        TreeNode maxNode = findMax(root);
        root = removeMax(root);
        return maxNode.val;
    }
    //内部删除最大值函数
    private TreeNode removeMax(TreeNode root) {
        if(root.right == null){
            //当前的root节点就是最大值
            TreeNode left = root.left;
            root.left = root = null;
            size--;
            return left;
        }
        root.right = removeMax(root.right);
        return root;
    }
    public int removeMin(){
        TreeNode node = findMin(root);//找到最小值的节点
        root = removeMin(root);
        return node.val;

    }
    //内部删除最小值函数
    private TreeNode removeMin(TreeNode root) {
        if(root.left == null){//root就是要被删除的节点
            TreeNode right = root.right;//先保存该节点的右孩子
            root.right = root = null;//清除该节点
            return right;//返回该右孩子
        }
        root.left = removeMin(root.left);//递归调用
        return root;
    }
    /**
     * 删除任意值:在当前以root为根的BST中删除值为val的节点,返回删除后的树根节点
     */
    public void remove(int val) {
        remove(root,val);
    }
    public TreeNode remove(TreeNode root, int val){
        //base case
        if(root == null){
            return null;
        } else if (val < root.val) {
            //在左子树的删除操作:递归调用
            root.left = remove(root.left,val);
            return root;
        } else if (val > root.val) {
            //在右子树的删除:递归调用
            root.right = remove(root.right,val);
            return root;
        } else{
            //当前节点就是要被删除的节点
            //(1)当该节点只有左孩子,没有右孩子->相当于最大值的删除
            if(root.right == null){
                TreeNode left = root.left;
                root.left = root = null;
                size --;
                return left;
            }
            //(2)当该节点只有右孩子,没有左孩子->相当于最小值的删除
            if(root.left == null){
                TreeNode right = root.right;
                root.right = root = null;
                size --;
                return right;
            }
            //(3)当前节点左右孩子都有,此时删除该节点,则需要一个新的节点顶替过来:
            //后来发现:该节点要么是该删除节点的左子树的最大值,要么是该删除节点的右子树的最小值
            TreeNode successor = findMin(root.right);//找到该顶替的节点:右子树的最小值
            successor.right = removeMin(root.right);//断开successor节点的右子树分支
            successor.left = root.left;
            root.left = root.right = root = null;//清空该根节点以及所有的子树分支
            return successor;//successor就是新的树根
        }
    }
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        printBST(root,0,sb);
        return sb.toString();
    }
    /**
     * 在以当前root为根的BST中,将当前节点的层次和值,拼接到sb对象中
     */
    private void printBST(TreeNode root, int level, StringBuilder sb) {
        //base case
        if(root == null){
            sb.append(printLine(level));
            sb.append("Null\n");//树为空,直接打印Null
            return;
        }
        //当前树不为空
        sb.append(printLine(level));//先根据层数拼接--的个数
        sb.append(root.val);//拼接值
        sb.append("\n");//换行
        //递归的打印左树
        printBST(root.left,level+1,sb);
        //递归的打印右树
        printBST(root.right,level+1,sb);
    }

    private String printLine(int level) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < level; i++) {
            sb.append("--");//一层打印一次--
        }
        return sb.toString();
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值