数据结构 - 二叉搜索树

前言

搜索树是基于二叉树实现的一课特殊结构的树,为了方便查找,根结点的左子树小于根,右子树大于根

如图中,以30为根结点,左树val 全部小于30, 右树val 全部大于30,以15 为根结点也是重复一样的结构,这种结构在搜索查找方面相对于普通二叉树来说,时间复杂度会减少很多 。

模拟实现二叉搜索树

结点

    //结点
    class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;

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

    //根结点
    public TreeNode root;

查找

    //查找
    public TreeNode find(int val) {
        if(root == null) {
            return null;
        }
        TreeNode cur = root;
        while(cur != null) {
            if(cur.val == val) {
                return cur;
            }else if(cur.val < val) {
                cur = cur.right;
            }else {
                cur = cur.left;
            }
        }
        return null;
    }

根据搜索树的结构性质,左小又大,我们定义cur 结点,每次和 val 进行比较,小于cur 向左走,大于 cur 向右走,等于就返回 cur 结点。

插入数据

    //插入数据
    public void insert(int val) {
        //如果root 为null,将插入的数据变成root
        if(root == null) {
            root = new TreeNode(val);
        }
        TreeNode cur = root;
        TreeNode parent = null;
        //找到需要插入的结点(parent)
        while(cur != null) {
            if(cur.val < val) {
                parent = cur;
                cur = cur.right;
            }else if(cur.val == val) {
                return;
            }else {
                parent = cur;
                cur = cur.left;
            }
        }
        //找到后再判断比较val和parent.val 决定插入在parent 的左左边还是右边
        TreeNode node = new TreeNode(val);
        if(parent.val > val) {
            parent.left = node;
        }else {
            parent.right = node;
        }
    }

插入数据时,首先就是比较val 值,我们定义一个 cur 结点为root, parent 结点为 cur 的父亲结点,先比较 cur.val 和 val,如果小于,那么val 肯定是在cur 的右边,我们将cur 赋值给parent,然后cur 向right 走,如果小于,cur 就往左走,等于直接return ,因为二叉搜索树是不允许有相同值的结点存在。

当cur 走到 null 时,我们的 val 值就要准备进行插入了,此时parent 为上面cur 最后一次比较的结点,我们需要判断 val 值 和 parent.val 值,根据左小右大的结构进行插入。

 删除数据

    //删除数据
    public boolean remove(int val) {
        TreeNode cur = root;
        TreeNode parent = null;
        while(cur != null) {
            if(cur.val < val) {
                parent = cur;
                cur = cur.right;
            }else if(cur.val == val) {
                removeNode(parent,cur);
            }else {
                parent = cur;
                cur = cur.left;
            }
        }
        return false;
    }

    private void removeNode(TreeNode parent, TreeNode cur) {
        if(cur.left == null) {//左为空
            if(cur == root) {
                root = root.right;
            }else if(parent.left == cur) {
                parent.left = cur.right;
            }else {
                parent.right = cur.right;
            }
        }else if(cur.right == null) {//右为空
            if(cur == root) {
                root = root.left;
            }else if(parent.left == cur) {
                parent.left = cur.left;
            }else {
                parent.right = cur.left;
            }
        }else {//都不为空
            TreeNode targetParent = cur;
            TreeNode target = cur.right;
            //找到 cur 右子树里最小的那一个
            while(target.left != null) {
                targetParent = target;
                target = target.left;
            }
            cur.val = target.val;

            if(targetParent.left == target) {
                targetParent.left = target.right;
            }else {
                targetParent.right = target.right;
            }
        }
    }

删除数据有很多种情况,首先找到我们需要删除的结点,和查找方法的逻辑一样,找到后进行删除,我们将删除另写一个方法 removeNode() ,在删除的判断下有一下这些情况。

  • cur 为需要删除的结点,parent 为 cur 的父节点

cur.left == null 时

 1.cur == root 

root = root.right

c

2.parent.left == cur

parent.left = cur.right

3.parent.right == cur

parent.right = cur.right

 

 cur.right == null 时

  1.cur == root 

root = root.left

 2.parent.left == cur

parent.left = cur.left 

 3.parent.right == cur

parent.right = cur.left 

 cur.left != null && cur.right != null

当左右都不为空时,定义变量 target ,为 cur 的右子树上找出一个最小的值(这个值还是大于cur 的) ,当找到后,将这个值覆盖给cur 的值,然后将我们的target 删除。

注意,删除target 时,如果target 为 targetParent.left 时,只需要将 targetParent.left 指向 target 的right 就可以了,因为当找到 target 时,target 的左一定时空的,如果左不为空,那么这个左子树还要比 target 小,那么target 就还会往 left 走。

还有一种情况,当 target.left 一开始就为 null 时,while 循环进不去,那么 target 就为 targetParent.right,这个时候就需要改变 right 的指向。

当cur 左右都不为 null 时,也可以在cur 的左子树上找到一个最大值去覆盖 cur的 val。

二叉搜索树的时间复杂度

在最好的情况下,搜索树为一课完全二叉树,时间复杂度最大为 O(log₂N) ,

即为树的高度,对于具有N 个结点的完全二叉树,树的高度为 log₂(n + 1),

 

 在最坏的情况下,搜索树为一课单支树,时间复杂度最大为 O(N) ,即为结点数量。

 

为了防止出现左右子树高度不平衡的情况,在二叉搜索树的上面还有AVL树 和 红黑树。 

以上就是我对二叉搜索树的概述了, 如果觉得本篇文章对你有帮助的话麻烦给个三连支持下吧!!!

 

 

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值