二叉搜索树

本文详细介绍了二叉搜索树的概念、性质及其在Java中的实现,包括插入、查找和删除操作。通过中序遍历,可以确保二叉搜索树的有序性。在删除操作中,针对不同情况(如叶子节点、只有一个孩子节点或两个孩子节点的情况)进行了处理,确保了树的平衡。二叉搜索树的性能取决于树的形状,最佳情况是完全二叉树,最差情况是退化为单支树。
摘要由CSDN通过智能技术生成

二叉搜索树


概念

二叉搜索树又称为二叉排序树,也可以为空树;

性质

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

或者也可以这样理解只要中序遍历结果是一个有序序列就是一棵二叉搜索树

如下图所示就是一棵二叉搜索树
在这里插入图片描述

为了用构造的二叉搜索树来封装 Map ,所以写成泛型的,因此在定义结点时,将结点中的值域给成K-V键值对,并让 key 继承Comparable,使用compareTo就可以进行比较;

结点及构造方法的定义

采用内部类的方式来定义结点并给出构造方法;

代码如下

public class TreeMap <K extends Comparable<K>,V> implements Map<K,V> {
    public static class TreeNode<K,V> {
        TreeNode left;  //左孩子
        TreeNode right;  //右孩子
        Map.Entry<K,V> kv;  //结点中的值域


        public TreeNode(K key,V value) {
            kv=new Entry<>();
            kv.key = key;
            kv.value=value;
        }
    }

二叉搜索树的插入

  • 树为空:插入根结点位置
  • 树不为空时:
    在这里插入图片描述
    步骤

(1)确定要插入结点的位置

  • cur从根结点开始,只要cur不空,循环继续,让parent标记cur的双亲位置;
  • 将插入结点的keycurkey进行比较,结果有三种情况:
    大于:让cur就往搜索树的右子树中查找
    小于: 让cur就往搜索树的左子树中查找
    等于:返回旧值

(2)插入新结点

代码实现

public V put(K key, V value) {
        //空树
        if(root==null){
            root=new TreeNode<>(key,value);
            size=1;
            return value;
        }
        //树不空
        //按照二叉搜索树的特性找待插入结点在树中的位置
        TreeNode<K,V> cur=root;
        TreeNode<K,V> parent=null;
        while(cur!=null){
            parent=cur; //标记cur的双亲
            int rst=key.compareTo(cur.kv.key);
            if(rst>0){
                cur=cur.right;
            }else if(rst<0){
                cur=cur.left;
            }else{
                V oldValue=cur.kv.value;
                cur.kv.value=value;
                return oldValue;
            }
        }
          //插入新结点
        int rst=key.compareTo(parent.kv.key);
        TreeNode<K,V> newNode=new TreeNode<>(key,value);
        if(rst>0){
            parent.right=newNode;
        }else{
            parent.left=newNode;
        }
        size++;
        return value;
    }

查找二叉搜索树的 key

根据二叉搜索树的特性,分两种情况;
情况一:树为空
直接返回false
情况二:树不空
根据二叉搜索树的性质,(1)先判断待查找的结点的keycur标记的根结点是否相同;(2)当(1)不满足时,让cur 再去根的左右子树中查找

代码实现

//判断是否包含key---->利用搜索树的特性
      //包含返回true,否则返回false
    public boolean containsKey(K key) {
        TreeNode<K,V> cur=root;
        int rst=key.compareTo(cur.kv.key);
        if(rst==0){
            return true;
        }else if(rst<0){
            cur=cur.left;
        }else{
            cur=cur.right;
        }
        //没找到
        return false;
    }

查找二叉搜索树的 value

与上面就不一样了,这次需要对每个结点的value进行比对,因此需要进行中序遍历来查找;

代码如下

/判断是否包含value--->中序遍历
    //包含返回true,否则返回false
    public boolean containsValue(V value) {
        return containsValue(root,value);
    }

    private boolean containsValue(TreeNode<K,V> _root,V value){
        //树为空时
        if(_root==null){
            return false;
        }
        //树不为空
        //对树进行中序遍历--->先在左子树中查看是否包含value
        //包含返回true
        if(containsValue(_root.left,value)) {
            return true;
        }
        //判断根结点的value与查找的value是否相同
        if(value.equals(_root.kv.value)){
            return true;
        }
        //否则就去右子树中找value
        return containsValue(_root.right,value);
    }

二叉搜索树的删除(难点+重点

分情况

假设最后删除的结点为 cur;

情况1:

  • cur是叶子结点:可以直接删除;

方法:只需要将parent的右指向cur的右即可
在这里插入图片描述

情况2:

  • cur只有左孩子:可以直接删除;

方法:让parent的左指向cur的左即可

在这里插入图片描述

情况3:

  • cur只有右孩子:可以直接删除;

方法:让parent的右指向cur的右

在这里插入图片描述

情况4:

  • cur左右孩子均存在:不可以直接删除;
    在这里插入图片描述

方法:使用替换法进行删除,即在它的右子树中寻找值最小的结点,将找到的该结点与被删除结点交换,再将待删除结点进行删除;

详细情况见以下代码~

代码实现

public V remove(K key) {
        //1.找待删除结点的位置
        TreeNode<K,V> cur=root;
        TreeNode<K,V> parent=null;
        while(cur!=null){
            int rst=key.compareTo(cur.kv.key);
            if(rst<0){
                cur=cur.left;
            }else if(rst>0){
                cur=cur.right;
            }else{
                //找到了
                break;
            }
        }
        if(cur==null){
            return null;
        }

        //2. 删除结点
        V value =cur.kv.value;
        if(cur.left==null){
            //cur可能是叶子结点,也可能只有右孩子
            if(parent==null){
                //cur为根结点
                root=cur.right;
            }else{
                if(cur==parent.left){
                    parent.left=cur.right;
                }else {
                    parent.right=cur.right;
                }
            }
           }else if( cur.right==null){
            //cur 只有左孩子
            if(parent==null){
                //cur为根结点
                root=cur.left;
            }else {
                if(cur==parent.left){
                    parent.left=cur.left;
                }else{
                    parent.right=cur.left;
                }
            }
           }else{
            //cur左右孩子均存在
            //a. 在其右子树中找最小的结点---->处于右边最左侧位置的结点
            TreeNode<K,V> delNode=cur.right;
            parent=cur;
            while(delNode.left!=null){
                delNode=delNode.left;
            }
            //b.将替代结点的值域赋给待删除结点cur
            cur.kv=delNode.kv;

            //c.删除
            if(parent.left==delNode){
                parent.left=delNode.right;
            }else{
                parent.right=delNode.right;
            }
        }
        size--;
        return value;
    }

由于插入和删除的操作都必须先查找,查找的效率就代表了二叉搜索树中各个操作的性能;
对有n个结点的二叉搜索树,当结点越多时,比较的次数就越多;
最优情况下,二叉搜索树为完全二叉树,其比较次数为log2N
最差情况下,二叉搜索树退化为单支树,其比较次数为N次;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值