二叉搜索树、二叉排序树(查找、插入和删除)——Java版本

1. 概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树
  • 不允许存在相同的结点

如:一个int a [] = {5,3,4,1,7,8,2,6,0,9}; 数组组成二叉排序树

定义二叉搜索树的结构:

    static class TreeNode {
        public int val;
        public TreeNode left;
        public TreeNode right;

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

    public TreeNode root;

2. 操作 - 查找 

    /*
    搜索
    搜索的时间复杂度最好:O(logN)
    搜索的时间复杂度最坏:O(N)
     */
    public boolean search(int key){
        TreeNode cur = root;
        while(cur != null){
            //找到key,返回true
            if(cur.val == key) {
                return true;
            }
            //到右树寻找
            else if(cur.val < key){
                cur = cur.right;
            }
            //到左树寻找
            else{
                cur = cur.left;
            }
        }
        //没有找到值为key的结点
        return false;
    }

3. 操作 - 插入

(1) 如果树为空树,即根 == null,直接插入

(2)如果树不是空树,按照查找逻辑确定插入位置,插入新结点

    /*
    插入:都是插入到叶子结点
    插入的时间复杂度最好:O(logN)
    插入的时间复杂度最坏:O(N)
     */
    public void insert(int val) throws Exception {
        TreeNode newNode = new TreeNode(val);
        if (root == null) {
            root = newNode;
            return;
        }
        TreeNode cur = root;
        TreeNode parent = null;
        while (cur != null) {
            parent = cur;
            //判断是否有重复元素进入
            if (cur.val == val) {
                throw new Exception("有重复元素进入!");
            }
            //如果要插入的结点的值,大于当前结点,就应该在cur的右子树
            if (cur.val < val) {
                cur = cur.right;
            }
            //如果要插入的结点的值,小于当前结点,就应该在cur的左子树
            else if (cur.val > val) {
                cur = cur.left;
            }
        }
        //如果要插入的结点小于父节点,就应该接在父节点的左子树
        if (parent.val > val) {
            parent.left = newNode;
        }
        //如果要插入的结点大于父节点,就应该接在父节点的右子树
        else {
            parent.right = newNode;
        }
    }

搜索和插入的运行结果: 

4. 操作 - 删除 ⭐⭐⭐⭐⭐

分情况讨论,如下所示: 

设待删除结点为 cur, 待删除结点的双亲结点为 parent  

(1)cur.left == null

① cur 是 root,则 root = cur.right

② cur 不是 root,cur 是 parent.left,则 parent.left = cur.right

 ③ cur 不是 root,cur 是 parent.right,则 parent.right = cur.right

 (2)cur.right == null

① cur 是 root,则 root = cur.left

② cur 不是 root,cur 是 parent.left,则 parent.left = cur.left 

③ cur 不是 root,cur 是 parent.right,则 parent.right = cur.left  

 (3)cur.left != null && cur.right != null

需要使用替换法进行删除,即在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被 删除节点中,再来处理该结点的删除问题。

 这部分的代码为(有bug):

        //3.cur两边的子树都不为空
        else{
            TreeNode t = cur.left;
            TreeNode tp = null;
            while(t.right != null){
                tp = t;
                t = t.right;
            }
            cur.val = t.val;

            //这个就变成了删除t指向的结点,且t结点的右子树为空
            tp.right = t.left;
        }

原来的图: 

第一步进行分析和改进:

第二步进行分析和改进:

树类的方法: 

    //删除结点的操作
    public void remove(int val) {
        TreeNode parent = null;
        TreeNode cur = root;
        while (cur != null) {
            //去右子树寻找
            if (cur.val < val) {
                parent = cur;
                cur = cur.right;
            } else if (cur.val > val) {
                parent = cur;
                cur = cur.left;
            }
            //找到了
            else {
                removeNode(cur, parent);
                return;
            }
        }
    }

    private void removeNode(TreeNode cur, TreeNode parent) {
        //1.cur的左子树为空
        if (cur.left == null) {
            //(1)cur 是 root,则 root = cur.right
            if (cur == root) {
                root = cur.right;
            }
            //(2)cur 不是 root,cur 是 parent.left,则 parent.left = cur.right
            else if (cur == parent.left) {
                parent.left = cur.right;
            }
            //(3)cur 不是 root,cur 是 parent.right,则 parent.right = cur.right
            else {
                parent.right = cur.right;
            }
        }
        //2.cur的右子树为空
        else if (cur.right == null) {
            //(1)cur 是 root,则 root = cur.left
            if (cur == root) {
                root = cur.left;
            }
            //(2)cur 不是 root,cur 是 parent.left,则 parent.left = cur.left
            else if (cur == parent.left) {
                parent.left = cur.right;
            }
            //(3)cur 不是 root,cur 是 parent.right,则 parent.right = cur.left
            else {
                parent.right = cur.right;
            }
        }
        //3.cur两边的子树都不为空
        else {
            TreeNode t = cur.left;
            TreeNode tp = cur;
            while (t.right != null) {
                tp = t;
                t = t.right;
            }
            cur.val = t.val;
            if (cur == tp) {
                tp.left = t.left;
            } else {
                //这个就变成了删除t指向的结点,且t结点的右子树为空
                tp.right = t.left;
            }

        }
    }

 测试类:

public class Test {
    public static void main(String[] args) throws Exception {
        BinarySearchTree binarySearchTree = new BinarySearchTree();
        binarySearchTree.insert(5);
        binarySearchTree.insert(3);
        binarySearchTree.insert(7);
        binarySearchTree.insert(1);
        binarySearchTree.insert(4);
        binarySearchTree.insert(6);
        binarySearchTree.insert(8);
        binarySearchTree.insert(0);
        binarySearchTree.insert(9);
        binarySearchTree.remove(3);
    }
}

5. 二叉搜索树的完整代码

public class BinarySearchTree {
    static class TreeNode {
        public int val;
        public TreeNode left;
        public TreeNode right;

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

    public TreeNode root;

    /*
    搜索
    搜索的时间复杂度最好:O(logN)
    搜索的时间复杂度最坏:O(N)
     */
    public boolean search(int key) {
        TreeNode cur = root;
        while (cur != null) {
            //找到key,返回true
            if (cur.val == key) {
                return true;
            }
            //到右树寻找
            else if (cur.val < key) {
                cur = cur.right;
            }
            //到左树寻找
            else {
                cur = cur.left;
            }
        }
        //没有找到值为key的结点
        return false;
    }

    /*
    插入:都是插入到叶子结点
    插入的时间复杂度最好:O(logN)
    插入的时间复杂度最坏:O(N)
     */
    public void insert(int val) throws Exception {
        TreeNode newNode = new TreeNode(val);
        if (root == null) {
            root = newNode;
            return;
        }
        TreeNode cur = root;
        TreeNode parent = null;
        while (cur != null) {
            parent = cur;
            //判断是否有重复元素进入
            if (cur.val == val) {
                throw new Exception("有重复元素进入!");
            }
            //如果要插入的结点的值,大于当前结点,就应该在cur的右子树
            if (cur.val < val) {
                cur = cur.right;
            }
            //如果要插入的结点的值,小于当前结点,就应该在cur的左子树
            else if (cur.val > val) {
                cur = cur.left;
            }
        }
        //如果要插入的结点小于父节点,就应该接在父节点的左子树
        if (parent.val > val) {
            parent.left = newNode;
        }
        //如果要插入的结点大于父节点,就应该接在父节点的右子树
        else {
            parent.right = newNode;
        }
    }

    //删除结点的操作
    public void remove(int val) {
        TreeNode parent = null;
        TreeNode cur = root;
        while (cur != null) {
            //去右子树寻找
            if (cur.val < val) {
                parent = cur;
                cur = cur.right;
            } else if (cur.val > val) {
                parent = cur;
                cur = cur.left;
            }
            //找到了
            else {
                removeNode(cur, parent);
                return;
            }
        }
    }

    private void removeNode(TreeNode cur, TreeNode parent) {
        //1.cur的左子树为空
        if (cur.left == null) {
            //(1)cur 是 root,则 root = cur.right
            if (cur == root) {
                root = cur.right;
            }
            //(2)cur 不是 root,cur 是 parent.left,则 parent.left = cur.right
            else if (cur == parent.left) {
                parent.left = cur.right;
            }
            //(3)cur 不是 root,cur 是 parent.right,则 parent.right = cur.right
            else {
                parent.right = cur.right;
            }
        }
        //2.cur的右子树为空
        else if (cur.right == null) {
            //(1)cur 是 root,则 root = cur.left
            if (cur == root) {
                root = cur.left;
            }
            //(2)cur 不是 root,cur 是 parent.left,则 parent.left = cur.left
            else if (cur == parent.left) {
                parent.left = cur.right;
            }
            //(3)cur 不是 root,cur 是 parent.right,则 parent.right = cur.left
            else {
                parent.right = cur.right;
            }
        }
        //3.cur两边的子树都不为空
        else {
            TreeNode t = cur.left;
            TreeNode tp = cur;
            while (t.right != null) {
                tp = t;
                t = t.right;
            }
            cur.val = t.val;
            if (cur == tp) {
                tp.left = t.left;
            } else {
                //这个就变成了删除t指向的结点,且t结点的右子树为空
                tp.right = t.left;
            }

        }
    }

}

6. 性能分析

插入和删除操作都必须先查找查找效率代表了二叉搜索树中各个操作的性能。 对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度 的函数,即结点越深,则比较次数越多。 但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

  • 最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:\log N
  • 最差情况下,二叉搜索树退化为单支树,其平均比较次数为:N

问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,都可以,是二叉搜索树的性能最佳? 

答:可以,这就涉及到后面的AVL树和红黑树了,后期的文章会继续讨论AVL树和红黑树。

7. 和Java类集的关系

TreeMap 和 TreeSet 即 Java 中利用搜索树实现的 Map 和 Set;实际上用的是红黑树,而红黑树是一棵近似平衡的二叉搜索树,即在二叉搜索树的基础之上 + 颜色以及红黑树性质验证,关于红黑树的内容后序再进行讲解。

  • 33
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
搜索和二排序结构中的两种形式,它们在实现和使用上有一些区别。下面是它们的区别: 1. 定义: - 二搜索:二搜索是一种特殊的二,其中每个节点的值大于其左子中的所有节点的值,小于其右子中的所有节点的值。 - 二排序:二排序也是一种特殊的二,其中每个节点的值大于其左子中的所有节点的值,小于其右子中的所有节点的值,并且左子和右子也都是二排序。 2. 插入操作: - 二搜索:在二搜索插入一个新节点时,需要按照节点值的大小关系找到合适的位置插入,保持二搜索的性质。 - 二排序:在二排序插入一个新节点时,同样需要按照节点值的大小关系找到合适的位置插入,保持二排序的性质。 3. 删除操作: - 二搜索:在二搜索删除一个节点时,需要考虑节点的子节点情况,并进行相应的调整,以保持二搜索的性质。 - 二排序:在二排序删除一个节点时,同样需要考虑节点的子节点情况,并进行相应的调整,以保持二排序的性质。 4. 平衡性: - 二搜索:二搜索的平衡性取决于节点的插入删除操作,如果操作不当,可能导致二搜索不平衡,进而影响搜索的效率。 - 二排序:二排序通常是平衡的,即左子和右子的高度差不超过1,这样可以保证搜索的效率。 综上所述,二搜索和二排序在定义、插入操作、删除操作和平衡性上存在一些区别。二搜索是一种特殊的二,而二排序是一种特殊的二搜索,它们都具有按照节点值的大小关系进行插入删除操作的特点,但二排序通常是平衡的,而二搜索可能不平衡。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木子斤欠木同

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值