Java数据结构与算法第十课-----二叉搜索树

一:二叉搜索树的定义

二叉搜索树(Binary Search Tree) , (又:二叉查找树,二叉排序树) , 它或者是一棵空树,或者是具有下列性质的二叉树:

  1. 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  2. 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  3. 它的左、右子树也分别为二叉排序树。

二叉搜索树作为一种经典的数据结构,它既有链表的快速插入与删除操作的特点,又有数组快速查找的优势;所以应用十分广泛,例如在文件系统和数据库系统一般会采用这种数据结构进行高效率的排序与检索操作。

在这里插入图片描述

二:插入

关于搜索树的插入 , 我们要清楚几个问题 :

  1. 插入的节点一定是在叶子上 ;
  2. 当根节点为空时 , 说明此时为空树 , 直接插入到root即可 ;
  3. 根节点不为空时 , 依次判断 , 当前节点应该插入到左边还是右边 .
  4. 插入过程中 , 如果有相同的数字 , 不能进行插入 .


public class BinarySearchTree {

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

        TreeNode(int key){
            this.key = key;
        }
    }

    public TreeNode root;


    public boolean insert(int val){
        //1.先判断是否为空树
        if(root == null){
            root = new TreeNode(val);
            return true;
        }
        //2.说明不是空树
        TreeNode cur = root;
        TreeNode parent = null;

        while (cur != null){
            if(val >cur.key) {
                parent = cur;
                cur = cur.right;
            } else if(val < cur.key){
                parent = cur;
                cur = cur.left;
            } else {
                return false;
            }
        }

         TreeNode node = new TreeNode(val);
        if(parent.key < val){
            parent.right = node;
        } else {
            parent.left = node;
        }
        return true;
    }
}

进行测试 .

public class Test {
    public static void main(String[] args) {
        BinarySearchTree binarySearchTree = new BinarySearchTree();
        int[] array = {5,3,4,1,7,8,2,6,0,9};
        for (int i = 0; i < array.length; i++) {
            binarySearchTree.insert(array[i]);
        }
        System.out.println("测试");

    }
}

通过Debug调试 , 可以验证我们的代码是正确的 .最终二叉搜素树如下 :

在这里插入图片描述

三:查找

    /**
     * 查找
     * @param val
     * @return
     */
    public TreeNode search(int val){
        TreeNode cur = root;
        while(cur != null){
            if(cur.key > val){
                cur = cur.left;
            } else if(cur.key < val) {
                cur = cur.right;
            } else {
                return cur;
            }
        }

        return null;
    }

测试 :

public class Test {
    public static void main(String[] args) {
        BinarySearchTree binarySearchTree = new BinarySearchTree();
        int[] array = {5,3,4,1,7,8,2,6,0,9};
        for (int i = 0; i < array.length; i++) {
            binarySearchTree.insert(array[i]);
        }
        System.out.println("查找");
        BinarySearchTree.TreeNode ret1 = binarySearchTree.search(6);
        BinarySearchTree.TreeNode ret2 = binarySearchTree.search(26);
        try{
            System.out.println(ret1.key);
            System.out.println(ret2.key);
        } catch (NullPointerException e) {
            System.out.println("未查找到该节点");
            e.printStackTrace();
        }

    }
}

在这里插入图片描述

四:删除

删除节点比较复杂,需要分多种情况讨论.

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

具体情况如下 :

在这里插入图片描述
在这里插入图片描述

关于替换法的思考 :

在待删除节点的右边找最小值 , 搜索树的最小值一定在这棵树的最左边 , 说明你找到的这个节点 , 左树为空 ; 同样滴 , 如果在待删除节点的左边找最大值 , 搜索树的最大值一定在这棵树的最右边 , 说明你找到的这个节点 , 右树为空 .

所以此时再删除这个"替罪羊" , 必隶属于前面两种情况 , 我们容易处理 .

代码如下 :

    /**
     * 删除节点
     * @param key
     * @return
     */
    public boolean remove(int key){
        //先找到待删除节点
        TreeNode cur = root;
        TreeNode parent = null;
        while(cur != null){
            if(cur.key < key){
                parent = cur;
                cur = cur.right;
            } else if(cur.key > key) {
                parent = cur;
                cur = cur.left;
            } else {
                removeNode(parent,cur);
                return true;
            }
        }
        return false;
    }

    private void removeNode(TreeNode parent, TreeNode cur) {
        if(cur.left == null){
            if(cur == root){
                root = cur.right;
            } else if(cur == parent.left){
                parent.left = cur.right;
            } else {
                parent.right = cur.right;
            }
        } else if(cur.right == null) {
            if(cur == root){
                root = cur.left;
            } else if(cur == parent.left){
                parent.left = cur.left;
            } else {
                parent.right = cur.left;
            }
        } else {
            TreeNode targetParent = cur;
            TreeNode target = cur.right;
            while (target.left != null){
                targetParent = target;
                target = target.left; //往左走,找最小值
            }
            //找到了最小值,开始替换
            cur.key = target.key;
            //把target删除了
            if(target == targetParent.left){
                targetParent.left = target.right;
            } else {
                targetParent.right = target.right;
            }
        }
    }

进行测试 :

public class Test {
    public static void main(String[] args) {
        BinarySearchTree binarySearchTree = new BinarySearchTree();
        int[] array = {5,3,4,1,7,8,2,6,0,9};
        for (int i = 0; i < array.length; i++) {
            binarySearchTree.insert(array[i]);
        }
        System.out.println("查找");
        BinarySearchTree.TreeNode ret1 = binarySearchTree.search(6);
        try{
            System.out.println(ret1.key);
        } catch (NullPointerException e) {
            System.out.println("未查找到该节点");
            e.printStackTrace();
        }
        
        binarySearchTree.remove(4);
        System.out.println("测试");

    }
}

通过Debug调试 , 可以验证我们的代码是正确的 .

五:性能分析

查找效率代表了二叉搜索树中各个操作的性能 .

对有n个结点的二叉搜索树 , 若每个元素查找的概率相等 , 则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多 .

  • 最优情况 : 完全二叉树 , 比较次数为logN .
  • 最坏情况 : 单分支树 , 比较次数为 N/2.

六:和java类集的关系

TreeMap 和 TreeSet 在 java 中 , 就是利用搜索树实现的 Map 和 Set .

所以为了提高二叉搜索树操作的效率,在构建二叉搜索树时尽量避免出现单支树的情况出现 , 就出现了红黑树和AVL树 . Map和Set的底层 , 其实利用了红黑树 . 关于红黑树 , 会在后面的文章中进行阐述 .

本课内容到此结束!!!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值