【C++数据结构】二叉搜索树

news2024/11/19 9:34:34

【C++数据结构】二叉搜索树

目录

  • 【C++数据结构】二叉搜索树
      • 二叉搜索树概念
      • 二叉搜索树操作
          • 二叉搜索树的查找
          • 二叉搜索树的插入
          • 二叉搜索树的删除
          • 二叉搜索树的实现
          • 二叉搜索树的应用
          • 二叉搜索树的性能分析

作者:爱写代码的刚子
时间:2023.8.22
前言:二叉搜索树是为后面的map和set做铺垫,在有些题目中难度较大,需要我们打好二叉树的基础。

二叉搜索树概念

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

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

二叉搜索树操作

二叉搜索树的查找

在这里插入图片描述

二叉搜索树的插入
  1. a树为空,则直接插入
  2. 树不空,按二叉搜索树性质查找插入位置,插入新节点
  • 如果插入的节点大于当前节点,就走右子树;如果插入的节点小于当前节点,就走左子树。
二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点

看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:
情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点
情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点
情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中, 再来处理该结点的删除问题

二叉搜索树的实现

以下是我模拟实现二叉搜索树的代码

template<class T>
class BSTreeNode
{
public:
    BSTreeNode<T>* _left;
    BSTreeNode<T>* _right;
    T _key;
    BSTreeNode(const T&key)
    :_left(nullptr)
    ,_right(nullptr)
    ,_key(key)
    {}
};

template<class T>
class BSTree
{
    typedef BSTreeNode<T> Node;
public:
    BSTree()
    :_root(nullptr)
    {}
    BSTree(const BSTree<T>& tree)
    {
        _root=_Copy(tree._root);
    }
    ~BSTree()
    {
        _Destroy(_root);
    }
    BSTree<T>& operator=(BSTree<T> tree)
    {
        swap(_root,tree._root);
        return *this;
    }
    bool InSert(const T&key)
    {
        if(_root==nullptr)
        {
            _root=new Node(key);
            return true;
        }
        Node*parent=nullptr;
        Node*cur=_root;
        while(cur)
        {
            if(cur->_key>key)
            {
                parent=cur;
                cur=cur->_left;
            }
            else if(cur->_key<key)
            {
                parent=cur;
                cur=cur->_right;
            }
            else
            {
                return false;
            }
        }
        cur = new Node(key);
        if(parent->_key<key)
        {
            parent->_right=cur;
        }
        else{
            parent->_left=cur;
        }
        return true;
    }
    bool Find(const T&key)
    {
        Node* cur=_root;
        while(cur)
        {
            if(cur->_key>key)
            {
                cur=cur->_left;
            }
            else if(cur->_key<key)
            {
                cur=cur->_right;
            }
            else
            {
                return true;
            }          
        }
        return false;
    }
    bool Erase(const T&key)
    {
        Node*parent=nullptr;
        Node*cur=_root;
        while(cur)
        {
            if(cur->_key>key)
            {
                parent = cur;
                cur=cur->_left;
            }
            else if(cur->_key<key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else{
                if(cur->_left==nullptr)
                {
                    if(cur==_root)
                    {
                        _root=cur->_right;
                    }
                    else if(parent->_right==cur)//判断在哪棵树,左树就连左树,右树就连右树
                    {
                        parent->_right=cur->_right;
                    }
                    else{
                        parent->_left=cur->_right;
                    }
                }
                else if(cur->_right==nullptr)
                {
                    if(cur==_root)
                    {
                        _root=cur->_left;
                    }
                    else if(parent->_right==cur)
                    {
                        parent->_right=cur->_left;
                    }
                    else{
                        parent->_left=cur->_left;
                    }
                }
                else
                {
                    Node*parent=cur;//这里父节点不能置空,考虑cur为头节点的情况
                    Node*leftMax=cur->_left;//下面的步骤是找左子树的最大节点
                    while(leftMax->_right)
                    {
                        parent=leftMax;
                        leftMax=leftMax->_right;
                    }
                    swap(cur->_key,leftMax->_key);
                    if(parent->_left==leftMax)//特殊情况
                    {
                        parent->_left=leftMax->_left;
                    }
                    else if(parent->_right==leftMax)
                    {
                        parent->_right=leftMax->_left;
                    }
                    cur=leftMax;

                }

                delete cur;
                cur=nullptr;
                return true;
            }
        }
        return false;
    } 
    bool FindR(const T&key)//递归实现
    {
        return _FindR(_root,key);
    }
    void InOrder()
    {
        _InOrder(_root);
        cout<<endl;
    }
    bool InSertR(const T&key)
    {
        return _InSertR(_root,key);
    }
    bool EraseR(const T&key)
    {
        return _EraseR(_root,key);
    }
private:
    Node* _Copy(const Node* root)
    {
        if(root==nullptr)
        {
            return nullptr;
        }
        Node* copynode=new Node(root->_key);
        copynode->_left=_Copy(root->left);
        copynode->_right=_Copy(root->_right);
        return copynode;
    }
    bool _FindR(const Node*root,const T&key)
    {
        if(root==nullptr)
        {
            return false;
        }
        if(root->_key>key)
        {
            return _FindR(root->_left,key);
        }
        else if(root->_key<key)
        {
            return _FindR(root->_right,key);
        }
        else{
            return true;
        }
    }
    void _Destroy(Node*&root)
    {
        if(root==nullptr)
        {
            return;
        }
        _Destroy(root->_left);
        _Destroy(root->_right);
        delete root;
        root=nullptr;
    }
    void _InOrder(const Node*root)
    {
        if(root==nullptr)
        {
            return;
        }
        _InOrder(root->_left);
        cout<<root->_key<<" ";
        _InOrder(root->_right);
    }
    bool _InSertR(Node*&root,const T&key)
    {
        if(root==nullptr)
        {
            root=new Node(key);
            return true;
        }
        if(root->_key<key)
        {
            return _InSertR(root->_right,key);
        }
        else if(root->_key>key)
        {
            return _InSertR(root->_left,key);
        }
        else
        {
            return false;
        }
        //return false;
    }
    bool _EraseR(Node*&root,const T&key)
    {
        if(root==nullptr)
        {
            return false;
        }

        if(root->_key>key)
        {
            return _EraseR(root->_left,key);
        }
        else if(root->_key<key)
        {
            return _EraseR(root->_right,key);
        }
        else{
            Node*save=root;
            if(root->_left==nullptr)
            {
                root=root->_right;//使用引用的妙处,不需要判断左右子树
            }
            else if(root->_right==nullptr)
            {
                root=root->_left;
            }
            else{
                Node*leftMax=root->_left;
                while(leftMax->_right)
                {
                    leftMax=leftMax->_right;
                }
                swap(root->_key,leftMax->_key);
                return _EraseR(root->_left,key);//注意这里不是传root
            }
            delete save;
            return true;
        }
    }
private:
    Node* _root;
};

几个需要注意的地方:

  • bool _EraseR(Node*&root,const T&key)这个函数中return _EraseR(root->_left,key);不能写为return _EraseR(root,key);否则会出现头节点删除不干净的情况:
    举例:
    在这里插入图片描述
  • bool Erase(const T&key)函数中
if(parent->_left==leftMax)//特殊情况
{
          parent->_left=leftMax->_left;
}

是为了解决如下情况:
在这里插入图片描述

二叉搜索树的应用
  1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。 比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
  • 以单词集合中的每个单词作为key,构建一棵二叉搜索树
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
  1. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生 活中非常常见:比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英 文单词与其对应的中文<word, chinese>就构成一种键值对;再比如统计单词次数,统计成功后,给定 单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。
    比如:实现一个简单的英汉词典dict,可以通过英文找到与其对应的中文,具体实现方式如下:
  • <单词,中文含义>为键值对构造二叉搜索树,注意:二叉搜索树需要比较,键值对比较时只比较 Key
  • 查询英文单词时,只需给出英文单词,就可快速找到与其对应的key

(举例代码略过)

二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的 深度的函数,即结点越深,则比较次数越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

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

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


要学好搜索二叉树的应用还是要多刷题,多看看原理。

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

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

相关文章

MySQL数据库管理操作

MySQL常用的数据类型 int&#xff1a;整型 用于定义整数类型的数据float&#xff1a;单精度浮点4字节32位准确表示到小数点后六位double&#xff1a;双精度浮点8字节64位char&#xff1a;固定长度的字符类型用于定义字符类型数据。Char如果存入数据的实际长度比指定长度要…

前端工程化概述

软件工程定义&#xff1a;将工程方法系统化地应用到软件开发中 前端发展历史 前端工程化的发展历史可以追溯到互联网的早期阶段&#xff0c;随着前端技术的不断演进和互联网应用的复杂化&#xff0c;前端工程化也逐渐成为了前端开发的重要领域。以下是前端工程化的主要发展里程…

诚迈科技子公司智达诚远与Unity中国达成合作,打造智能座舱新时代

2023 年 8 月 23 日&#xff0c;全球领先的实时 3D 引擎 Unity 在华合资公司 Unity 中国举办发布会&#xff0c;正式对外发布 Unity 引擎中国版——团结引擎&#xff0c;并带来专为次世代汽车智能座舱打造的团结引擎车机版。发布会上&#xff0c;诚迈科技副总裁、诚迈科技子公司…

C 实现Window/DOS 键盘监听事件

今天是重新复习C语言实现的第一天&#xff0c;今天想编写C 对Windwos/Dos 键盘事件的学习。但是我在安装Visual Studio 2022 没有安装MFC 框架&#xff0c;今天记录下VS追加 MFC框架。 Visual Studio 2022 追加MFC 1、打开vs&#xff0c;点击创建新项目&#xff0c;右侧滑动框…

【ubuntu】 20.04 网络连接器图标不显示、有线未托管、设置界面中没有“网络”选项等问题解决方案

问题 在工作中 Ubuntu 20.04 桌面版因挂机或不当操作&#xff0c;意外导致如下问题 1、 Ubuntu 网络连接图标消失 2、 有线未托管 上图中展示的是 有线 已连接 &#xff0c;故障的显示 有限 未托管 或其他字符 3、 ”设置“ 中缺少”网络“选项 上图是设置界面&#xff0c…

Excel 分组排名

分组排名 公式&#xff1a;SUMPRODUCT((A:AA2)*(C:C>C2)) 1 降序&#xff1a;> 改为 < ⚠️注意1&#xff1a;此处空值参与排名&#xff1b;不参与排名则公式改为&#xff1a;IF(C2“”,“”,SUMPRODUCT((A:AA2)*(C:C>C2)) 1) ⚠️注意2&#xff1a;相同值的项…

多维时序 | MATLAB实现BiTCN-BiGRU-Attention多变量时间序列预测

多维时序 | MATLAB实现SABO-CNN-GRU-Attention多变量时间序列预测 目录 多维时序 | MATLAB实现SABO-CNN-GRU-Attention多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 多维时序 | MATLAB实现BiTCN-BiGRU-Attention多变量时间序列预测。 模型描…

大数据数据仓库

一.在线教育 1.数据采集 1.数仓概念 数据仓库是为企业制定决策&#xff0c;提供数据支持的。数据采集和存储、对数据进行计算和分析 2.项目架构 2.数据分类 业务数据 用户行为数据 爬虫数据 2.离线数仓 3.实时数仓

.NET 8 Preview 7 中的 ASP.NET Core 更新

作者&#xff1a;Daniel Roth 排版&#xff1a;Alan Wang .NET 8 Preview 7 现在已经发布&#xff0c;其中包括了对 ASP.NET Core 的许多重要更新。 以下是预览版本中新增功能的摘要&#xff1a; 服务器和中间件 防伪中间件 API 编写 最小 API 的防伪集成 Native AOT 请求委托…

【Modbus通信实验三】数据切片问题

在做两个串口相互通信的实验中&#xff0c;当发送频率快一点时偶尔会遇到以下情景&#xff0c;即一次send中把原数据拆成两份发送&#xff0c;就会导致CRC校验错误。下图中6字节数据拆成42是把SetRThreshold()阈值设为2&#xff0c;当设为1的情况下则会拆成51。 一开始以为是缓…

一款轻量级开发者工具,提高开发效率

Devkits Devkits 是一款轻量级桌面端应用&#xff0c;提供了一系列开发者工具&#xff0c;提高开发效率。 离线。类似的在线工具已经不少了&#xff0c;但是大多数都是在线的&#xff0c;网络不好的时候就很难用了。Devkits 提供了离线使用的功能&#xff0c;可以在没有网络的…

16、Flink 的table api与sql之连接外部系统: 读写外部系统的连接器和格式以及JDBC示例(4)

Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…

使用NXP GUI GUIDER生成的GUI移植到雅特力MCU平台过程详解(ST/GD/国民/极海通用)

好记性不如烂笔头&#xff0c;既然不够聪明&#xff0c;就乖乖的做笔记&#xff0c;温故而知新。 本文档用于本人对知识点的梳理和记录 一、前言 上一篇我们有介绍NXP GUI Guider工具如何制作和调试GUI&#xff0c;GUI神器 NXP GUI GUIDER开发工具入门教程https://blog.csdn.n…

【云计算】Docker特别版——前端一篇学会

docker学习 文章目录 一、下载安装docker&#xff08;一&#xff09;Windows桌面应用安装&#xff08;二&#xff09;Linux命令安装 二、windows注册登录docker三、Docker的常规操作(一)、基本的 Docker 命令(二)、镜像操作(三)、容器的配置(四)、登录远程仓库 四、镜像管理(一…

视频转音频mp3怎么弄?

视频转音频mp3怎么弄&#xff1f;在很多人看来&#xff0c;音频就是视频中的一部分&#xff0c;其实这时是一定道理的&#xff0c;视频是一种包含图像和有声音的多媒体文件&#xff0c;没有声音的视频是不完美的。时代发展到现在&#xff0c;短视频已经融入了我们生活的方方面面…

ElementUI中的日历组件加载无效的问题

在ElementUI中提供了一个日历组件。在某些场景下还是比较有用的。只是在使用的时候会有些下坑&#xff0c;大家要注意下。   官网提供的信息比较简介。我们在引入到项目中使用的时候可以能会出现下面的错误提示。 Unknown custom element: <el-calendar> - did you …

【Spring】Spring循环依赖

目录 什么是循环依赖问题 循环依赖具体是怎么解决的 具体的解决步骤&#xff1a; 通俗实例&#xff1a; 严谨的循环依赖解决图例 为什么使用的是三级缓存&#xff0c;二级缓存不够用吗&#xff1f; 什么是循环依赖问题 Spring的循环依赖是指在Bean之间存在相互依赖关…

【教程】手把手教你Termius去除登录并解除限制,非常简单!

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 如果不想关注我&#xff0c;但还想看本文&#xff0c;就去这里吧&#xff0c;无限制&#xff1a;【教程】手把手教你Termius去除登录并解除限制&#xff0c;非常简单&#xff01; - 小锋学长生活大爆炸 这里以Mac…

C语言:整型提升

一、什么是整型提升 C语言的整型算术运算至少是以缺省整型类型的精度来进行的。 为了达到这个精度&#xff0c;算术运算表达式中的 字符型char 和 短整型short 需要被转换为普通整型&#xff0c;这种转换成为整型提升。 二、整型提升的意义 表达式的整型运算需要在CPU相应的运算…

基于Jenkins自动打包并部署docker环境

目录 1、安装docker-ce 2、阿里云镜像加速器 3、构建tomcat 基础镜像 4、构建一个Maven项目 实验环境 操作系统 IP地址 主机名 角色 CentOS7.5 192.168.200.111 git git服务器 CentOS7.5 192.168.200.112 Jenkins git客户端 jenkins服务器 CentOS7.5 192.168…