数据结构与算法——17.二叉搜索树

news2024/12/30 2:36:53

这篇文章我们来看一下数据结构中的二叉搜索树。

目录

1.概述

2.二叉搜索树的实现

3.总结


1.概述

我们前面学到的数据结构,比如:动态数组、链表、队列、栈、堆,这些数据结构存储完数据后,我们要去查找某个数据,它的时间复杂度是O(n),因为这些数据结构的底层实现都是数组或者链表,都是线性的。我们前面有学过二分查找,它的最优时间复杂度为O(lngn)。下面,我们来学习另外一种便于查找的数据结构——二叉搜索树

二叉搜索树:又被称为二叉查找树。其特点如下:

  • 树节点上增加key属性,用来比较谁大谁小,key不可以重复
  • 对于任意一个树节点,它的key比它的左子树的key都大,比它的右子树的key都小

下面看一张图:

二叉搜索树的理想查找时间复杂度为O(logn)

2.二叉搜索树的实现

下面来看一下二叉搜索树的实现:

二叉搜索树的根据key值找节点值,找最大,找最小,找前驱和后继都是比较简单的,思路都是很好理解的。

下面重点来讲一下删除的思路(删除的情况很多):

  1. 删除节点没有左孩子,将右孩子托孤给Parent
  2. 删除节点没有右孩子,将左孩于托孤给Parent
  3. 删除节点左右孩子都没有,已经被涵盖在情况1、情况2当中,把null 托孤给Parent
  4. 删除节点左右孩子都有,可以将它的后继节点(称为S)托孤给Parent,再称S的父亲为SP,又分两种情况:(1)SP就是被删除节点,此时D与S紧邻,只需将S托孤给Parent (2)SP不是被删除节点,此时D与S不相邻,此时需要将S的后代托孤给SP,再将S托孤给Parent

下面来看一下代码的具体实现(代码太长,就不截图展示了):

package Tree;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**二叉搜索树*/
public class L2_BSTree1<T extends Comparable<T>> {
    /**节点类*/
    static class BSTNode<T>{
        T key;
        Object value;
        BSTNode left;
        BSTNode right;

        public BSTNode(T key) {
            this.key = key;
        }

        public BSTNode(T key, Object value) {
            this.key = key;
            this.value = value;
        }

        public BSTNode(T key, Object value, BSTNode left, BSTNode right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }

    BSTNode<T> root;//根节点

    /**根据key值得到节点的值*/
    public Object get(T key){
        BSTNode<T> node = root;
        while (node!=null){
            /**
             * 该值比传入参数大,返回1
             * 该值比传入参数小,返回-1
             * 该值等于传入参数,返回0
             * */
            int result = key.compareTo(node.key);
            if (result < 0){
                node = node.left;
            }else if (result > 0){
                node = node.right;
            }else {
                return node.value;
            }
        }
        return null;
    }
    public Object get1(T key){
        return doGet(root,key);
    }
    private Object doGet(BSTNode<T> node, T key){//递归的函数
        int result = key.compareTo(node.key);
        if (node == null){
            return null;
        }
        if (result < 0){
            return doGet(node.left,key);//向左找
        }
        else if (result > 0){
            return doGet(node.right,key);//向左找
        }
        else{
            return node.value;//返回当前的值
        }
    }

    /**得到最小key值所对应的值*/
    public Object min(){//非递归版
        return max(root);
    }
    public Object min(BSTNode node){//非递归版
        if (node == null){
            return null;
        }
        BSTNode pre = node;
        while (pre.left != null){
            pre = pre.left;
        }
        return pre.value;
    }
    public Object min1(){//递归版
       return doMin(root);
    }
    private Object doMin(BSTNode node){
        if (node == null){
            return null;
        }
        if (node.left == null){
            return node.value;
        }
        return doMin(node.left);
    }

    /**得到最大key值所对应的值*/
    public Object max(){//非递归版
        return max(root);
    }
    private Object max(BSTNode<T> node){
        if (node == null){
            return null;
        }
        BSTNode pre = node;
        while (pre.right != null){
            pre = pre.right;
        }
        return pre.value;
    }
    public Object max1(){//递归版
        return doMax(root);
    }
    private Object doMax(BSTNode node){
        if (node == null){
            return null;
        }
        if (node.right == null){
            return node.value;
        }
        return doMin(node.right);
    }

    /**存储key值和节点值*/
    public void put(T key,Object value){
        //1.如果key存在,更新操作
        //1.如果key不存在,新增操作
        BSTNode<T> node = root;
        BSTNode<T> parent = null;//记录key的前一个值
        while (node != null){
            parent = node;
            int result = key.compareTo(node.key);
            if (result < 0){
                node = node.left;
            }else if (result > 0){
                node = node.right;
            }else {//找到了
                node.value = value;
                return;
            }
        }
        //没找到,新增
        if (parent == null){
            root = new BSTNode<T>(key,value);
        }
        int result = key.compareTo(parent.key);
        if (result < 0){
            parent.left = new BSTNode<T>(key,value);
        }else if(result > 0){
            parent.right = new BSTNode<T>(key,value);
        }

    }

    /**找到某一个key的前驱值*/
    public Object predecessor(T key){
        BSTNode<T> p = root;
        BSTNode<T> ancestorFromLeft = null;
        while (p != null){
            int result = key.compareTo(p.key);
            if (result < 0){
                p = p.left;
            }else if (result > 0){
                ancestorFromLeft = p;
                p = p.right;
            }else {
                break;
            }
        }
        if (p == null){//没找到节点的情况
            return null;
        }
        if (p.left != null){//找到节点,有左子树
            return max(p.left);
        }
        return ancestorFromLeft != null ?
                ancestorFromLeft.value :null;
    }

    /**找到某一个key的后继值*/
    public Object successor(T key){
        BSTNode<T> p = root;
        BSTNode<T> ancestorFromRight = null;
        while (p != null){
            int result = key.compareTo(p.key);
            if (result < 0){
                ancestorFromRight = p;
                p = p.left;
            }else if (result > 0){
                p = p.right;
            }else {
                break;
            }
        }
        if (p == null){//没找到节点的情况
            return null;
        }
        if (p.right != null){//找到节点,有左子树
            return min(p.right);
        }
        return ancestorFromRight != null ?
                ancestorFromRight.value :null;
    }

    /**根据key值删除对应的节点*/
    public Object delete(T key){
        BSTNode<T> p = root;
        BSTNode<T> parent = null;
        while (p != null){
            int result = key.compareTo(p.key);
            if (result < 0){
                parent = p;//记录当前节点的父节点
                p = p.left;
            }else if (result > 0){
                parent = p;
                p = p.right;
            }else {
                break;
            }
        }
        if (p == null){
            return null;
        }
        //删除操作
        if (p.left == null ){
            //情况1
            shift(parent,p,p.right);
        } else if(p.right == null ){
            //情况2
            shift(parent,p,p.left);
        } else {
            //情况4
            BSTNode<T> s = p.right;
            BSTNode<T> sParent = p;//后继结点的父亲
            while (s.left != null){
                sParent = s;
                s = s.left;
            }
            if (sParent != p){//不相邻
                shift(sParent,s,s.right);
                s.right = p.right;
            }

            shift(parent ,p,s);
            s.left = p.left;
        }
        return p.value;
    }
    /**
     * 托孤方法
     * parent:被删除节点的父亲
     * deleted:被删除节点
     * child:被上去的结点
     * */
    private void shift(BSTNode<T> parent,BSTNode<T> deleted,BSTNode<T> child){
        if (parent == null){
            root = child;
        } else if (deleted == parent.left){
            parent.left = child;
        }else {
            parent.right = child;
        }
    }

    public Object delete1(T key){
        ArrayList<Object> Aresult = new ArrayList<>();//保存被删除节点的值
        root = doDelete(root,key,Aresult);
        return Aresult.isEmpty()? null:Aresult.get(0);
    }
    private BSTNode<T> doDelete(BSTNode<T> node,T key,ArrayList<Object> Aresult){
        //node:递归删除的起点
        //返回值:删剩下的孩子节点
        if (node == null){
            return null;
        }
        int result = key.compareTo(node.key);
        if (result < 0){
            node.left = doDelete(node.left,key,Aresult);
            return node;
        }
        if (result > 0){
            node.right = doDelete(node.right,key,Aresult);
            return node;
        }

        Aresult.add(node.value);

        if (node.left == null){
            return node.right;
        }
        if(node.right == null){
            return node.left;
        }

        BSTNode<T> s = node.right;
        while (s.left != null){
            s = s.left;
        }
        s.right = doDelete(node.right,s.key,new ArrayList<>());
        s.left = node.left;
        return s;
    }

    /*找比指定key小的所有节点的value值*/
    public List<Object> less(T key){
        ArrayList<Object> list = new ArrayList<>();
        BSTNode<T> p = root;
        LinkedList<BSTNode<T>> stack = new LinkedList<>();

        while (p != null || !stack.isEmpty()){
            if (p != null){
                stack.push(p);
                p = p.left;
            }else {
                BSTNode<T> pop = stack.pop();
                int result = key.compareTo( pop.key);
                if (result < 0 ){
                    list.add(pop.value);
                } else {
                    break;
                }
                p = pop.right;
            }
        }
        return list;
    }

    /*找比指定key小的所有节点的value值*/
    public List<Object> greater(T key){
        ArrayList<Object> list = new ArrayList<>();
        BSTNode<T> p = root;
        LinkedList<BSTNode<T>> stack = new LinkedList<>();

        while (p != null || !stack.isEmpty()){
            if (p != null){
                stack.push(p);
                p = p.left;
            }else {
                BSTNode<T> pop = stack.pop();
                int result = key.compareTo( pop.key);
                if (result > 0 ){
                    list.add(pop.value);
                }
                p = pop.right;
            }
        }
        return list;
    }

    /*找比指定key小的所有节点的value值*/
    public List<Object> between(T key1,T key2){
        ArrayList<Object> list = new ArrayList<>();
        BSTNode<T> p = root;
        LinkedList<BSTNode<T>> stack = new LinkedList<>();

        while (p != null || !stack.isEmpty()){
            if (p != null){
                stack.push(p);
                p = p.left;
            }else {
                BSTNode<T> pop = stack.pop();
                int result1 = key1.compareTo( pop.key);
                int result2 = key2.compareTo( pop.key);
                if (result1 > 0 && result2 < 0){
                    list.add(pop.value);
                }else if (result2 > 0){
                    break;
                }
                p = pop.right;
            }
        }
        return list;
    }

}

3.总结

怎么说呢,二叉搜索树对比前面的二叉树来说,难度确实是上了一个档次。但是,越学数据结构与算法你越会有这样一种感觉:他们的套路都大差不差!二叉搜索树是用链表来实现的,只要心中有图,多画画图,然后熟悉链表的一些操作,熟悉一些循环流程的判断,那么那些操作都能实现出来。如果实现不了,那就再多结合其他的数据结构来想一想。链表的操作主要就是看一些循环流程的控制。其余的没啥难的。对于数组,数组的一些操作要比链表难,因为数组太死了。

我之前的代码的注释比较多,因为刚接触,不熟悉,但现在代码中的注释并不多,那是因为一些操作都写了很多遍了。虽然不至于能默写下来,但是可以自己推导着写出来。思路有了,也练了几遍手,那么再遇见这个问题自己就能推导了。所以数据结构与算法学到后面主要就是学思路了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1049912.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FPGA 多路视频处理:图像缩放+视频拼接显示,HDMI采集,提供2套工程源码和技术支持

目录 1、前言版本更新说明免责声明 2、相关方案推荐FPGA图像缩放方案推荐FPGA视频拼接方案推荐 3、设计思路框架视频源选择IT6802解码芯片配置及采集动态彩条缓冲FIFO图像缩放模块详解设计框图代码框图2种插值算法的整合与选择 视频拼接算法图像缓存视频输出 4、vivado工程1&am…

PIE:1979-2018年中国气温数据产品(空间分辨率为0.1º)

简介 中国气温数据产品包含1979-2018年期间中国的近地表气温数据&#xff08;单位为摄氏度&#xff09;&#xff0c;时间分辨率为每日&#xff0c;空间分辨率为0.1。本产品集成了再分析数据&#xff08;ERA5、CMFD&#xff09;、遥感数据&#xff08;MODIS&#xff09;、原位数…

RISC-V架构的函数调用规范和栈布局

1、函数调用中寄存器规范 2、函数调用规范 &#xff08;1&#xff09;函数的前8个参数使用a0-a7寄存器传递&#xff0c;如果传参多于8个&#xff0c;则除前8个参数使用寄存器来传递之外&#xff0c;后面的参数使用栈传递&#xff1b; &#xff08;2&#xff09;如果传递的参数小…

《学术小白学习之路10》论文常见方法:Doc2vec-句向量模型实现

1. 数据 用于文献的摘要的相似度的计算 ## 导包 import pandas as pd import jieba import gensim from gensim.models import Doc2Vec from gensim.models.doc2vec import TaggedDocument再定义停用词典,用于分词,还可以自己定义一个分词词典 ## 读入数据 papers = pd.&l…

TensorFlow入门(一、环境搭建)

一、下载安装Anaconda 下载地址:http://www.anaconda.comhttp://www.anaconda.com 下载完成后运行exe进行安装 二、下载cuda 下载地址:http://developer.nvidia.com/cuda-downloadshttp://developer.nvidia.com/cuda-downloads 下载完成后运行exe进行安装 安装后winR cmd进…

解密PDF密码

PDF文件有两种密码&#xff0c;一个打开密码、一个限制编辑密码&#xff0c;因为PDF文件设置了密码&#xff0c;那么打开、编辑PDF文件就会受到限制。忘记了PDF密码该如何解密&#xff1f; PDF和office一样&#xff0c;可以对文件进行加密&#xff0c;但是没有提供恢复密码的功…

解决craco启动react项目卡死在Starting the development server的问题

现象&#xff1a; 原因&#xff1a;craco.config.ts配置文件有问题 经过排查发现Dev开发模式下不能有splitChunk的配置&#xff0c; 解决办法&#xff1a; 加一个生产模式的判断&#xff0c;开发模式不加载splitChunk的配置&#xff0c;仅在生产模式才加载 判断条件代码&#…

基于微信小程的流浪动物领养小程序设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…

安防监控产品经营商城小程序的作用是什么

安防监控产品覆盖面较大&#xff0c;监控器、门禁、对讲机、烟感等都有很高用途&#xff0c;家庭、办公单位各场景往往用量不少&#xff0c;对商家来说&#xff0c;市场高需求背景下也带来了众多生意&#xff0c;但线下门店的局限性&#xff0c;导致商家想要进一步增长不容易。…

进阶指针(二)

#国庆发生的那些事儿# ✨博客主页&#xff1a;小钱编程成长记 &#x1f388;博客专栏&#xff1a;进阶C语言 &#x1f388;推荐相关博文&#xff1a;进阶指针&#xff08;一&#xff09; 进阶指针&#xff08;二&#xff09; 6.函数指针数组6.1例子 7.指向函数指针数组的指针8.…

OpenHarmony自定义组件介绍

一、创建自定义组件 在ArkUI中&#xff0c;UI显示的内容均为组件&#xff0c;由框架直接提供的称为系统组件&#xff0c;由开发者定义的称为自定义组件。在进行 UI 界面开发时&#xff0c;通常不是简单的将系统组件进行组合使用&#xff0c;而是需要考虑代码可复用性、业务逻辑…

四、YApi的安装和配置

YApi是去哪儿网的前端技术中心的一个开源可视化接口管理平台。 创建接口项目 创建接口 编写接口

中文读唇总动员:CNVSRC 2023 视觉语音识别挑战赛启动

由 NCMMSC 2023 组委会发起&#xff0c;清华大学、北京邮电大学、海天瑞声、语音之家共同主办的 CNVSRC 2023 中文连续视觉语音识别挑战赛即日启动&#xff0c;诚邀参与报名。 赛事官网&#xff1a;http://cnceleb.org/competition 视觉语音识别&#xff0c;也称唇语识别&…

为什么我的Windows 10笔记本电脑明明什么软件都没开,风扇却一直在转?

2023年9月29日&#xff0c;周五上午 这两天我的笔记本电脑一开机&#xff0c;风扇就一直在转&#xff0c;而且还没停过&#xff0c;挺吵的 即使什么软件都没开&#xff0c;还在那转&#xff0c;莫名其妙的。 后来我去任务管理器按照CPU使用情况来排序&#xff0c;发现原来是W…

如何自动转发接收的请求报头?

了解OpenTelemetry的朋友应该知道&#xff0c;为了将率属于同一个请求的多个操作&#xff08;Span&#xff09;串起来&#xff0c;上游应用会生成一个唯一的TraceId。在进行跨应用的Web调用时&#xff0c;这个TraceId和代表跟踪操作标识的SpanID一并发给目标应用&#xff0c;W3…

经过认证的工具链对安全关键型应用意味着什么?

作者&#xff1a;IAR 安全关键型应用&#xff0c;在很多人看来是个专业的词汇&#xff0c;但其实它离我们的日常生活很近&#xff0c;比如汽车驾驶系统、飞机控制系统、电梯运行系统、医疗设备等与我们息息相关的事物都可以纳入安全关键型应用的范畴。 对于这类应用&#xff…

SAP入门到放弃系列之QM检验计划-Part1

文章目录 一、概述1.1、检验计划抬头1.2、检验计划工序 二、系统操作2.1、测试数据准备&#xff1a;2.2、创建检验计划 一、概述 检验计划是用来描述如何对一种或多种物料进行质量检验操作的主数据。在检验计划中&#xff0c;可以定义检验的工序顺序以及可用于检验特征的数据规…

排序:归并(Merge)排序算法分析

1.归并操作 归并:把两个或多个已经有序的序列合并成一个。 2路归并&#xff1a;二合一k路归并&#xff1a;k合一结论:m路归并&#xff0c;每选出一个元素需要对比关键字m-1次。 2.算法思想 核心操作:把数组内的两个有序序列归并为一个。 例如&#xff1a; 3.代码实现 将…

数据集笔记: Porto

数据来源&#xff1a;Taxi Trajectory Data_数据集-阿里云天池 (aliyun.com) 1 数据介绍 葡萄牙波尔图市运行的所有442辆出租车的全年轨迹&#xff08;从2013年7月1日至2014年6月30日&#xff09; 2 读取数据 import pandas as pdtrapd.read_csv(C:/Users/16000/Download…

C语言中动态内存管理

前言&#xff1a;为什么存在动态内存分配&#xff0c;为什么要用动态内存分配&#xff0c;动态内存分配的意义。鸡汤&#xff1a;有了坚持不一定成功,但没有坚持&#xff0c;就注定失败&#xff0c;各位也要努力坚持提升自己&#xff01; 动态内存分配 动态内存函数&#xff1a…