Java—数据结构之二叉搜索树

目录

前言

一、二叉搜索树的概念

1.定义

2.特点

二、二叉搜索树的操作

1.查找操作

2.插入操作

3.删除操作

三、二叉搜索树的时间复杂度

完整代码

总结


前言

在Java数据结构之二叉搜索树的学习中,我通过总结以及查阅资料,对以下问题有了更深一层的理解。

  • 什么是二叉搜索树,二叉搜索树有什么样的性质以及特点?
  • 二叉搜索树该如何进行查找,插入及删除?
  • 二叉搜索树的时间复杂度该如何计算?

如果你在Java数据结构之二叉搜索树的学习中,也想对这些问题有一个更深的了解,请阅读这篇文章,或许会对你有所启发。

一、二叉搜索树的概念

1.定义

二叉搜索树又叫二叉排序树,它或是一颗空树,或是具有以下性质的二叉树

  •  若它的左子树不为空,则左子树上的所有结点都小于根结点上的值
  • 若它的右子树不为空,则右子树上的所有结点都大于根结点上的值
  • 它的左右子树也分别是搜索树

2.特点

二叉搜索树中序遍历后会得到一个升序排列的数组,若是一棵树的中序遍历是一个升序排列的数组,也能说明这棵树是二叉搜索树,在二叉搜索树中,右子树的值永远大于左子树的值。

二、二叉搜索树的基本结构

public class TreeNode {
    public long key;//要去寻找的结点
    public TreeNode left;//左子树引用
    public TreeNode right;//右子树引用

    public TreeNode(long key){
        this.key=key;
    }
    public String toString(){
    return String.format("TreeNode(%d)",key);
    }
}

二、二叉搜索树的操作

1.查找操作

根据二叉搜索树的定义,我们可知左子树的值要小于根节点的值,右子树的值要大于根节点的值,则我们

  • 如果要找的数大于当前节点值,就去右子树找
  • 如果要找的数小于当前节点值,就去左子树找
  • 如果要找的数等于当前节点的值,则该结点就是我们要找的
  • 如果整棵树遍历结束都没找到,则返回false
  //查找操作
    public boolean contains(long key) {
        TreeNode cur = root;
        while (cur != null) {//当前节点不为空
            if (key == cur.key) {
                return true;
            } else if (key < cur.key) {
                //如果要找的数小于当前节点值,就去左子树找
                cur = cur.left;
            } else {
                //key>cur.key
                //如果要找的数大于当前节点值,就去右子树找
                cur = cur.right;
            }
        }
        //如果整棵树遍历结束都找不到,则返回false
        return false;
    }

2.插入操作

在二叉搜索树中插入一个元素,首先要找到一个合适的插入位置,插入的新结点必须出现在原来空的地方

  • 如果根节点的值与待插入节点的值相等,则返回false(因为待插入的值不能与搜索树中节点的值相等)
  • 如果待插入的值比根节点的值小,则去左子树找
  • 如果待插入的值比根节点的值大,则去右子树找
  • 找到待插入节点的父节点后,创建新的节点进行插入
 //插入操作
    //如果key重复了,那么插入操作可能会失败
    public boolean add(long key) {
        //定义结点
        TreeNode node = new TreeNode(key);

        if (this.root == null) {
            //如果根节点为空,则当前节点node作为根节点
            this.root = node;
            this.size = 1;
            return true;
        }

        TreeNode parent = null;
        TreeNode cur = this.root;
        //parent始终指向cur的父节点,和链表插入类似,也需要记录指定位置的前一个结点
        while (cur != null) {
            if (key == cur.key) {
                return false;//key重复了
            } else if (key < cur.key) {
                //如果要找到的结点小于当前节点,就去左子树寻找
                parent = cur;
                cur = cur.left;
            } else {
                //key>cur.key
                //如果要找的数大于当前节点,就去右子树找
                parent = cur;
                cur = cur.right;
            }
        }
        //cur==null
        //保证parent一定是cur的双亲
        //断言parent!=null
        //要做的工作,把node插入到parent的孩子中
        if(key<parent.key){
            //左插
            parent.left=node;
        }else{
            //右插
            parent.right=node;
        }
        this.size++;
        return true;
    }

3.删除操作

二叉搜索树的删除操作需要考虑多种情况,首先要找到删除的数和父节点,考虑:

  1. 要删除的数是其父节点,要删除节点的节点是叶子节点,这时只需要将parent.right或者是parent.right设置为null,然后java自动回收机制会自动删除cur结点;
  2. 如果要删除的结点有parent的左子树或者右子树其中之一,则只需要将parent.left或者parent.right设置为cur.right或者cur.left即可;
  3. 要删除的结点有既有左子树又有右子树,在这种情况下,我们只需要找到待删结点的右子树中值最小的结点,将其删除并且获取其值,用这个值替换待删结点的值即可
 //删除操作
    public boolean remove(long key){
        TreeNode parent=null;
        TreeNode cur=root;
        //如果根节点为空,当前节点作为根

        while(cur!=null){
            //要删除的节点不为空,则去判断该节点是父节点还是叶子结点
            if(key==cur.key){
                deleteNode(parent,cur);
                this.size--;
                return true;
            }else if(key<cur.key){
                parent=cur;
                cur=cur.left;
            }else{
                parent=cur;
                cur=cur.right;
            }
        }
        return false;
    }
    private void deleteNode(TreeNode parent,TreeNode node){
        //如果要删除的点左子树为空,就将它的右子树作为根
        if(node.left==null){
            if(node==this.root){
                this.root=node.right;
            }else if(parent.left==node){
                parent.left= null;
            }else{
                parent.right=node.right;
            }
            //如果要删除的点右子树为空,就将它的左子树作为根
        }else if(node.right==null){
            if(node==this.root){
                this.root=node.left;
            }else if(parent.left==node){
                parent.left=node.left;
            }else{
                parent.right=node.left;
            }
        }else{
            //node.left!=null&&node.right!=null,左右子树均不为空
            //采用替换删除的方式
            //此处选择右子树中最小的一个(右子树中最左的一个)
            TreeNode toDeleteParent=node;
            TreeNode toDelete=node.right;
            // 断言:toDelete != null
            // 一路朝左走(toDelete.left == null 停下来)
            while (toDelete.left != null) {
                toDeleteParent = toDelete;
                toDelete = toDelete.left;
            }

            // toDelete 是我们要删除的结点
            node.key = toDelete.key;

            // 删除 toDelete
            // 断言:toDelete.left == null
            // 断言:toDelete != this.root
            // toDeleteParent.left | right = toDelete.right
            // toDeleteParent == node => 断言 toDeleteParent.right == toDelete
            //                  else  => 断言 toDeleteParent.left == toDelete
            if (toDeleteParent.left == toDelete) {
                toDeleteParent.left = toDelete.right;
            } else {
                // toDeleteParent.right == toDelete
                toDeleteParent.right = toDelete.right;
            }
        }
    }

三、二叉搜索树的时间复杂度

分析二叉搜索树的时间复杂度,要知道它的最优情况和最坏情况分别是什么?

  •  最优情况下,二叉搜索树为完全二叉树,其时间复杂度为二叉树的高度,O(
  • 最差情况下,二叉搜索树退化为单分支树,其时间复杂度为O(n)

完整代码

/*
若它的左子树不为空,则左子树上的所有结点都小于根结点上的值
若它的右子树不为空,则右子树上的所有结点都大于根结点上的值
它的左右子树也分别时搜索树
 */

/*
关于搜索树的类
验证该对象的合法性
1.size>=0
size和root之间的关系
2.size==通过遍历root得到的结点个数
3.root维护的树是搜索树,任取结点
中序遍历是有序的,key不重复——搜索树的规则
搜索树——>中序是有序的
中序是有序的——>搜索树
 */
public class BSTree {
    private TreeNode root;
    private int size;

    public BSTree() {
        this.root = null;
        this.size = 0;
    }

    //查找操作
    public boolean contains(long key) {
        TreeNode cur = root;
        while (cur != null) {//当前节点不为空
            if (key == cur.key) {
                return true;
            } else if (key < cur.key) {
                //如果要找的数小于当前节点值,就去左子树找
                cur = cur.left;
            } else {
                //key>cur.key
                //如果要找的数大于当前节点值,就去右子树找
                cur = cur.right;
            }
        }
        //如果整棵树遍历结束都找不到,则返回false
        return false;
    }

    //插入操作
    //如果key重复了,那么插入操作可能会失败
    public boolean add(long key) {
        //定义结点
        TreeNode node = new TreeNode(key);

        if (this.root == null) {
            //如果根节点为空,则当前节点node作为根节点
            this.root = node;
            this.size = 1;
            return true;
        }

        TreeNode parent = null;
        TreeNode cur = this.root;
        //parent始终指向cur的父节点,和链表插入类似,也需要记录指定位置的前一个结点
        while (cur != null) {
            if (key == cur.key) {
                return false;//key重复了
            } else if (key < cur.key) {
                //如果要找到的结点小于当前节点,就去左子树寻找
                parent = cur;
                cur = cur.left;
            } else {
                //key>cur.key
                //如果要找的数大于当前节点,就去右子树找
                parent = cur;
                cur = cur.right;
            }
        }
        //cur==null
        //保证parent一定是cur的双亲
        //断言parent!=null
        //要做的工作,把node插入到parent的孩子中
        if(key<parent.key){
            //左插
            parent.left=node;
        }else{
            //右插
            parent.right=node;
        }
        this.size++;
        return true;
    }

    //删除操作
    public boolean remove(long key){
        TreeNode parent=null;
        TreeNode cur=root;
        //如果根节点为空,当前节点作为根

        while(cur!=null){
            //要删除的节点不为空,则去判断该节点是父节点还是叶子结点
            if(key==cur.key){
                deleteNode(parent,cur);
                this.size--;
                return true;
            }else if(key<cur.key){
                parent=cur;
                cur=cur.left;
            }else{
                parent=cur;
                cur=cur.right;
            }
        }
        return false;
    }
    private void deleteNode(TreeNode parent,TreeNode node){
        //如果要删除的点左子树为空,就将它的右子树作为根
        if(node.left==null){
            if(node==this.root){
                this.root=node.right;
            }else if(parent.left==node){
                parent.left= null;
            }else{
                parent.right=node.right;
            }
            //如果要删除的点右子树为空,就将它的左子树作为根
        }else if(node.right==null){
            if(node==this.root){
                this.root=node.left;
            }else if(parent.left==node){
                parent.left=node.left;
            }else{
                parent.right=node.left;
            }
        }else{
            //node.left!=null&&node.right!=null,左右子树均不为空
            //采用替换删除的方式
            //此处选择右子树中最小的一个(右子树中最左的一个)
            TreeNode toDeleteParent=node;
            TreeNode toDelete=node.right;
            // 断言:toDelete != null
            // 一路朝左走(toDelete.left == null 停下来)
            while (toDelete.left != null) {
                toDeleteParent = toDelete;
                toDelete = toDelete.left;
            }

            // toDelete 是我们要删除的结点
            node.key = toDelete.key;

            // 删除 toDelete
            // 断言:toDelete.left == null
            // 断言:toDelete != this.root
            // toDeleteParent.left | right = toDelete.right
            // toDeleteParent == node => 断言 toDeleteParent.right == toDelete
            //                  else  => 断言 toDeleteParent.left == toDelete
            if (toDeleteParent.left == toDelete) {
                toDeleteParent.left = toDelete.right;
            } else {
                // toDeleteParent.right == toDelete
                toDeleteParent.right = toDelete.right;
            }
        }
    }

    public static void main(String[] args) {
        BSTree tree = new BSTree();
        long[] keys = { 5, 1, 6, 8, 3, 9, 0, 2, 4, 7 };
        for (long key : keys) {
            tree.add(key);
        }

        System.out.println(tree.remove(11));
    }
}

总结

以上就是今天二叉搜索树的相关内容,希望我们都能在二叉搜索树的代码练习中掌握有关知识!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值