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

【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


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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringMVC入门笔记

一、SpringMVC简介 1. 什么是MVC MVC是一种软件架构的思想&#xff0c;将软件按照模型、视图、控制器来划分 M&#xff1a;Model&#xff0c;模型层&#xff0c;指工程中的JavaBean&#xff0c;作用是处理数据 JavaBean分为两类&#xff1a; 一类称为实体类Bean&#xff1…

怎么维护自己的电脑

文章目录 我的电脑日常维护措施维护技巧键盘&屏幕清洁清理磁盘空间控制温度 电脑换电池 无论是学习还是工作&#xff0c;电脑都是IT人必不可少的重要武器&#xff0c;一台好电脑除了自身配置要经得起考验&#xff0c;后期主人对它的维护也是决定它寿命的重要因素&#xff0…

如何使用NLP库解析Python中的文本

Python是一种强大的面向对象的编程&#xff08;object-oriented programming&#xff0c;OOP&#xff09;语言&#xff0c;在人工智能领域有着广泛的用途。正是鉴于其实用性&#xff0c;以Google为首的大型科技公司&#xff0c;已经对其开发了Tensorflow等代码库&#xff0c;帮…

Flask狼书笔记 | 03_模板

文章目录 3 模板3.1 模板基本使用3.2 模板结构组织3.3 模板进阶 3 模板 模板&#xff08;template&#xff09;&#xff1a;包含固定内容和动态部分的可重用文件。Jinja2模板引擎可用于任何纯文本文件。 3.1 模板基本使用 HTML实体&#xff1a;https://dev.w3.org/html5/htm…

Ubuntu系统安装之后首需要做的事情

Ubuntu系统的初步环境搭建 1、换源2、显卡3、浏览器4、输入法5、终端6、ROS7、VSCode8、设置时间与win一致9、 TimeShift10、 Anaconda&#xff08;考虑装不装&#xff09; 1、换源 点开Software&&Update&#xff0c;找到Ubuntu Software中的Download from&#xff0c…

数据通信——传输层(UDP)

引言 我们上网观看比赛的时候&#xff0c;一旦网络信号出现问题&#xff0c;那可就太难受了&#xff0c;这意味着卡顿的时间内&#xff0c;你会错过这段时间内的内容。这种特性要归功于UDP&#xff08;User Datagram Protocol&#xff09;用户数据报协议。 无连接性 一般的&am…

IntelliJ IDEA maven配置,设置pom.xml的配置文件

IntelliJ IDEA项目&#xff0c;选择 文件 设置&#xff0c;弹窗 构建、执行、部署 构建工具 Maven就可以 maven配置好以后&#xff0c;在pom.xml的配置文件中就可以设置对应的jar包了&#xff0c;这样构建的时候自动需要的jar&#xff0c;在项目中导入即 需要的jar包设置在po…

解锁ChatGLM-6B的潜力:优化大语言模型训练,突破任务困难与答案解析难题

解锁ChatGLM-6B的潜力&#xff1a;优化大语言模型训练&#xff0c;突破任务困难与答案解析难题 LLM&#xff08;Large Language Model&#xff09;通常拥有大量的先验知识&#xff0c;使得其在许多自然语言处理任务上都有着不错的性能。 但&#xff0c;想要直接利用 LLM 完成…

UML 类图

1、概述 目录 1、概述 1.1、UML概念 1.2、类图的概念 2、类的表示方式 2.1、普通类 2.2、抽象类 2.3、接口 3、类与类关系的表示 3.1、关联关系&#xff08;Association&#xff09; 3.1.1、单向关联 3.1.2、双向关联 3.1.3、自关联 3.2、聚合关系&#xff08;aggre…

无涯教程-TensorFlow - 单词嵌入

Word embedding是从离散对象(如单词)映射到向量和实数的概念&#xff0c;可将离散的输入对象有效地转换为有用的向量。 Word embedding的输入如下所示: blue: (0.01359, 0.00075997, 0.24608, ..., -0.2524, 1.0048, 0.06259) blues: (0.01396, 0.11887, -0.48963, ..., 0.03…

【Unity】Text文本组件的一些操作

Unity的Text组件的几种常见的操作方法 Text组件是Unity中用于在UI界面上显示文本的组件。它包含了一些常见的属性和方法&#xff0c;可以用来控制文本的内容、外观和交互。以下是一些常见的Text组件的操作&#xff1a; 设置文本内容&#xff1a;通过直接在Unity编辑器中的Text…

SpringCloud学习笔记(三)_服务提供者集群与服务发现Discovery

服务提供者集群 既然SpringCloud的是微服务结构&#xff0c;那么对于同一种服务&#xff0c;当然不可能只有一个节点&#xff0c;需要部署多个节点 架构图如下&#xff1a; 由上可以看出存在多个同一种服务提供者&#xff08;Service Provider&#xff09; 搭建服务提供者集…

Mybatis-动态sql和分页

目录 一.什么是Mybatis动态分页 二.mybatis中的动态SQL 在BookMaaper.xml中写sql BookMapper BookBiz接口类 BookBizImpl实现接口类 demo测试类 ​编辑 测试结果 三.mybatis中的模糊查询 mybatis中的#与$有是什么区别 在BookMapper.xml里面建立三个模糊查询 ​编辑 …

腾讯云-对象存储服务(COS)的使用总结

简介 对象存储&#xff08;Cloud Object Storage&#xff0c;COS&#xff09;是腾讯云提供的一种存储海量文件的分布式存储服务&#xff0c;具有高扩展性、低成本、可靠安全等优点。通过控制台、API、SDK 和工具等多样化方式&#xff0c;用户可简单、快速地接入 COS&#xff0…

73 # 发布自己的 http-server 到 npm

1、添加 .npmignore 文件&#xff0c;忽略不需要的文件 public2、去官网https://www.npmjs.com/检查自己的包名是否被占用 3、切换到官方源&#xff0c;然后检查确认 nrm use npm nrm ls4、登录 npm 账号 npm login5、发布 npm publish6、查看发布情况&#xff0c;发布成功…

基于卷积神经网络的种子等级识别

目录 背影 卷积神经网络CNN的原理 卷积神经网络CNN的定义 卷积神经网络CNN的神经元 卷积神经网络CNN的激活函数 卷积神经网络CNN的传递函数 基于GUI的卷积神经网络和长短期神经网络的语音识别系统 代码下载链接:基于MATLABGUI编程的卷积神经网络和长短期神经网络语音识别系统…

住宅IP代理与数据中心IP代理的区别,最详解

跨境业务中常见到浏览器指纹防关联&#xff0c;但说到底&#xff0c;最重要的指纹是您的IP地址。在多个账号使用相同的IP地址简直触犯了大忌&#xff0c;这样做往往会导致账号惨遭暂停。 现在越来越多的跨境业务场景需要用到IP代理&#xff0c;那么我们常见的数据中心代理与住…

Linux 虚拟机安装 hadoop

目录 1 hadoop下载 2 解压hadoop 3 为 hadoop 文件夹改名 4 给 hadoop 文件夹赋权 5 修改环境变量 6 刷新环境变量 7 在hadoop313目录下创建文件夹data 8 检查文件 9 编辑 ./core-site.xml文件 10 编辑./hadoop-env.sh文件 11 编辑./hdfs-site.xml文件 12 编辑./mapr…

【业务功能篇73】web系统架构演变-单体-集群-垂直化-服务化-微服务化

1.服务架构的演 1.1 单体架构 单体架构应该是我们最先接触到的架构实现了&#xff0c;在单体架构中使用经典的三层模型&#xff0c;即表现层&#xff0c;业务逻辑层和数据访问层。 单体架构只适合在应用初期&#xff0c;且访问量比较下的情况下使用&#xff0c;优点是性价比很…

C++笔记之设计模式:setter函数、依赖注入

C笔记之设计模式&#xff1a;setter函数、依赖注入 code review! 文章目录 C笔记之设计模式&#xff1a;setter函数、依赖注入1.概念2.基本示例3.setter函数4.基本示例setter函数构成依赖注入5.概念——ChatGpt6.构造函数注入示例7.接口注入示例8. 构造函数注入的使用场景和用…