C++ 搜索二叉树

news2024/10/6 5:56:06

目录

  • C++ 搜索二叉树
    • 一. 介绍
    • 二.简单实现搜索二叉树
      • 1. 基本框架
      • 2. 插入节点
        • a. 图示:
        • b. 递归实现:
        • c. 非递归:
      • 3. 删除节点
        • a. 图示:
        • b. 递归实现:
        • c. 非递归:
    • 三. 小结

C++ 搜索二叉树

又名:二叉搜索树、二叉排序树、二叉查找树等

一. 介绍

搜索二叉树又称二叉搜索树(Binary search tree),其具有以下性质:

  • 若左子树不为空,根节点的值大于其左子树所有节点的值
  • 若右子树不为空,根节点的值小于其右子树所有节点的值
  • 对于左右子树也符合上述两条规则
    在这里插入图片描述

示例三图,都符合搜索二叉树的条件,左孩子 < 根 < 右孩子。

三个树的形状不同,与其构建顺序有关。

但是可以发现它们中序遍历的结果都是升序的0 1 2 3 4

二.简单实现搜索二叉树

1. 基本框架

template<class K>
struct BSTreeNode
{
	BSTreeNode(const K& key = K())
		:_key(key)
		, _left(nullptr)
		, _right(nullptr)
	{}

	K _key;
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
};

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;

private:
	Node* _root = nullptr;
};

在这里插入图片描述

_root:指向一个搜索二叉树的根节点

2. 插入节点

a. 图示:

在这里插入图片描述

b. 递归实现:

注意:root为引用

  • root为空,让root指向新建节点
  • root不为空,如果新节点的值大于root的值,那么新节点肯定在root的右数中…依次类推,直到找到空位置处,即新节点位置
  • 若新建的值存在,则不用再插入了,插入失败。
bool InsertR(const K& key)
{
    return _InsertR(_root, key);
}
bool _InsertR(Node*& root, const K& key)
{
    //根为空时
    if (nullptr == root)
    {
        //*root为引用,修改会改变其父节点的左/右指针
        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;
    }
}

c. 非递归:

  • 树为空,新节点为根节点

  • 树不为空,按搜索二叉树的性质找到新节点链接的对应位置。

    遍历搜索二叉树,如果新节点大于当前节点,那么新节点应该在当前节点的右子树中…依次类推,直到当前节点为空时,即应该插入到该位置。可以记录当前节点的前父节点,因为父节点可能左右子树都为空,因此需要再做一次判断,插入到该父节点的正确位置

  • 若插入的值已经存在,不用再插入了,插入失败。

bool Insert(const K& key)
{
    Node* newNode = new Node(key);

    //如果是第一个插入的根节点
    if (_root == nullptr)
    {
        _root = newNode;//让_root指向根节点
    }
    else
    {
        Node* prev = nullptr;
        Node* cur = _root;
        //cur为nullptr即找到合适的位置
        while (cur)
        {
            prev = cur;
            //如果当前节点的值大于要插入的值
            if (cur->_key > newNode->_key)
            {
                //当前节点向左移动
                cur = cur->_left;
            }
            //如果当前节点的值小于要插入的值
            else if (cur->_key < newNode->_key)
            {
                //当前节点向右移动
                cur = cur->_right;
            }
            //如果当前节点的值等于要插入的值
            else
            {
                return false; //不进行插入
            }
        }
        //判断新节点是插入在prev的左还是右
        if (prev->_key < newNode->_key)
        {
            prev->_right = newNode;
        }
        else
        {
            prev->_left = newNode;
        }
    }
    return true;
}

3. 删除节点

a. 图示:

  • 所删除节点的右子树为空节点
    在这里插入图片描述

  • 所删除节点的左子树为空节点
    在这里插入图片描述

  • 所删除节点的左右子树都为空,会在前两种情况中处理

  • 删除节点的左右子树都不为空

方法1:同其右子树最小值的节点交换,然后再删除(右子树最小值的节点,其左子树必为空)

在这里插入图片描述


方法2:同其左子树最大值的节点交换,然后再删除(左子树最大值的节点,其右子树必为空)

在这里插入图片描述


b. 递归实现:

注意:root为引用

  • 如果root为空,则无要删除的节点,返回false
  • 如果root的值大于要删除节点的值,这要删除的节点一定在root的右子树中…依次寻找
  • 如果root的值等于要删除节点的值,则需要删除该root
    • root左子树为nullptr,则将root的右子树链接给root,
    • root右子树为nullptr,则将root的左子树链接给root,
    • root左右子树都不为nullptr,则可以找到左子树的最大值(或右子树的最小值),与root内容交互,然后删除左子树的最大值
bool EraseR(const K& key)
{
    return _EraseR(_root, key);
}
bool _EraseR(Node*& root, const K& key)
{
    //如果root为空
    if (nullptr == root)
    {
        return false;
    }

    //root节点值小于删除节点的值
    if (root->_key < key)
    {
        return _EraseR(root->_right, key);
    }
    //root节点值大于删除节点的值
    else if (root->_key > key)
    {
        return _EraseR(root->_left, key);
    }
    else//root节点值等于删除节点的值
    {
        Node* del = root;
        //如果删除的节点左子树为空
        if (root->_left == nullptr)
        {
            //*root为引用,修改会改变其父节点的左/右指针
            root = root->_right;
            delete del;
        }
        //如果删除的节点右子树为空
        else if (root->_right == nullptr)
        {
            //*root为引用,修改会改变其父节点的左/右指针
            root = root->_left;
            delete del;
        }
        //如果删除的节点左右子树都不为空
        else
        {
            //找到左子树中最大值节点,即左子树的最右节点
            Node* LMax = root->_left;
            while (LMax->_right)
            {
                LMax = LMax->_right;
            }
            
            //将左子树中最大值节点和root值进行交换
            std::swap(root->_key, LMax->_key);
            //删除该左子树中最大值节点
            return _EraseR(root->_left, key);
        }
        return true;
    }
}

c. 非递归:

  • 按搜索二叉树的规律,找到需要删除的节点cur
  • 如果cur为nullptr,无该要删除的那个节点
  • 如果cur不为空,对cur进行删除
    • cur左子树为nullptr,则将root的右子树链接给root,
    • cur右子树为nullptr,则将root的左子树链接给root,
    • cur左右子树都不为nullptr,则可以找到左子树的最大值(或右子树的最小值),与cur内容交互,然后删除左子树的最大值
bool Erase(const K& key)
{
    Node* prev = nullptr;
    Node* cur = _root;

    //找到要删除的节点
    while (cur)
    {
        if (cur->_key == key)
        {
            break;
        }

        prev = cur;
        if (cur->_key < key)
        {
            cur = cur->_right;
        }
        else
        {
            cur = cur->_left;
        }
    }
    if (cur == nullptr)
    {
        return false;
    }

    //所删除节点的左子树为空
    if (cur->_left == nullptr)
    {
        //将cur的右子树链接到父节点

        if (_root == cur)//如果是删除根节点
            //if(prev == nullptr)也可以写这个
        {
            _root = cur->_right;
        }
        else if (prev->_left == cur)//如果cur是prev的左孩子
        {
            prev->_left = cur->_right;
        }
        else//如果cur是prev的右孩子
        {
            prev->_right = cur->_right;
        }

        delete cur;//删除节点
    }
    //右子树为空
    else if (cur->_right == nullptr)
    {
        //将cur的左子树链接到父节点

        if (_root == cur)//如果是删除根节点
        {
            _root = cur->_left;
        }
        else if (prev->_left == cur)//链接到父节点的左边
        {
            prev->_left = cur->_left;
        }
        else链接到父节点的右边
        {
            prev->_right = cur->_left;
        }
        delete cur;
    }          
    else //左右都不为空
    {                         
        Node* curLTM = cur->_left; //cur的left子tree的max结点
        Node* prevLTM = cur;//记录curLTM的父节点

        //找到左子树中最大值节点,即左子树的最右节点
        while (curLTM->_right)
        {
            prevLTM = curLTM;
            curLTM = curLTM->_right;
        }

        cur->_key = curLTM->_key;//交换值
        //交换后,则需要删除curLTM

        //将curLTM的右子树给其父节点
        if (prevLTM->_left == curLTM)
        {
            prevLTM->_left = curLTM->_left;
        }
        else
        {
            prevLTM->_right = curLTM->_left;
        }

        delete curLTM;//删除curLTM
    }

    return true;
}

三. 小结

搜索二叉树的插入、删除,不同于普通二叉树,主要是要保持特殊规则(左孩子 < 根 < 右孩子)。在实现过程中,如删除一个左右子树都不为空的节点时,实际上是通过操作来转化为删除一个左子树或右子树为空的节点。当然还有许多细节,建议边画图,边实现。

对于搜索二叉树,顾名思义,其特长在搜索(查找)功能上。一般情况下,其查找的时间复杂度为树的高度, O ( H ) O(H) O(H)。在此基础上,一棵搜索二叉树最优的情况应该类似于完全二叉树( H ≈ l o g 2 N H\approx log_2N Hlog2N),最坏的情况则是单支树( H = N H = N H=N)。

对于这种情况,有大佬对二叉搜索树进行优化,产生出AVL树,红黑树等结构。


🦀🦀观看~

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

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

相关文章

bean的三种实例化方式

实例化bean的三种方式&#xff0c;构造方法,静态工厂和实例工厂 构造方法实例化&#xff08;常用&#xff09; 步骤1&#xff1a;准备一个BookDao和BookDaoImpl类 public interface BookDao {public void save(); } ​ public class BookDaoImpl implements BookDao {public…

Vue中如何进行表单图片裁剪与预览

Vue中如何进行表单图片裁剪与预览 在前端开发中&#xff0c;表单提交是一个常见的操作。有时候&#xff0c;我们需要上传图片&#xff0c;但是上传的图片可能会非常大&#xff0c;这会增加服务器的负担&#xff0c;同时也会降低用户的体验。因此&#xff0c;我们通常需要对上传…

Python:关于flask框架的flask_scrip._compat

关于flask框架的flask_scrip._compat compat是什么源码Flask版本书写不同 compat是什么 compat 英文单词同胞的意思 compat的功能是在py的不同版本之间做兼容处理 一些py2/py3兼容性支持基于精简版的six&#xff0c;因此我们不必依赖于它的特定版本。 源码 # -*- coding: u…

使用芯片和贴片天线解决多频带射频问题

智能手机和可穿戴电子设备等手持和便携式无线产品依赖可置入设备的微型芯片、贴片和印制线天线。尽管这些小型器件解决了在小尺寸系统中携带多频带天线阵列的问题&#xff0c;但它们也引入了辐射效率下降、阻抗匹配以及与附近物体和人体的交互等相关问题。 为解决这些问题&…

ASO优化之如何降低应用的卸载率

不管是苹果应用商店&#xff0c;还是国内的安卓市场和国外的Google Play&#xff0c;拥有超过200万个应用&#xff0c;每个应用都面临着众多的竞争对手&#xff0c;当应用在承诺之后没有及时兑现可以提供的功能&#xff0c;就会面临被卸载的风险。 对应用在不同平台的应用商店…

chatgpt赋能python:Python数据类型的确定方法

Python数据类型的确定方法 在Python中&#xff0c;一个变量可以保存任何类型的数据。数据类型是指数据的种类和形式。在使用Python时&#xff0c;数据类型通常是自动推断的&#xff0c;但有时我们需要手动确定数据类型。本文介绍了Python中确定变量数据类型的几种方法。 使用…

如何部署免交互脚本

目录 一、免交互 什么是免交互 Here Document免交互 二、Expect概述 expect sed命令 三、如何用ssh实现免交互 四、监控硬盘实现免交互 五、创建硬盘分区如何实现免交互 一、免交互 什么是免交互 交互&#xff1a;需要人工发出指令&#xff0c;来控制程序的运行&…

走向实用的AI编解码

基于AI的端到端数据压缩方法受到越来越多的关注&#xff0c;研究对象已经包括图像、视频、点云、文本、语音和基因组等&#xff0c;其中AI图像压缩的研究最为活跃。图像编解码的研究和应用历史悠久&#xff0c;AI方法要达到实用&#xff0c;需要解决诸多问题才能取得相比于传统…

金融大数据平台是怎么构建的?

大数据对银行业的价值不言而喻。 在业务上,如何去挖掘客户的内在需求,为客户提供更有价值的服务是目前金融机构的战略转型和业务创新的关键。大数据技术正是金融机构深挖数据资产、实现差异化竞争、推动业务创新的重要工具。 在运营上,通过大数据应用和分析,金融机构能够定位…

利用事务消息实现分布式事务

什么是事务 什么是事务呢&#xff1f;事务是并发控制的单位&#xff0c;是用户定义的一个操作序列。有四个特性(ACID)&#xff1a; 原子性(Atomicity)&#xff1a; 事务是数据库的逻辑工作单位&#xff0c;事务中包括的诸操作要么全做&#xff0c;要么全不做。一致性(Consist…

深入探究kubernetes resources - Part 2

你以为CPU请求只是用来调度的吗&#xff1f; 再想一想。 引入 CPU 份额&#xff0c;并为消除限制奠定基础&#xff01; 了解 CPU 请求 在上一篇文章中&#xff0c;我谈到了 Kubernetes 资源管理的基础。 在这篇文章中&#xff0c;我们将深入探讨当我们将 CPU 请求配置到 pod 的…

3D建模Cocos Creator3D:发射器模块(ShapeModule)

推荐&#xff1a;将 NSDT场景编辑器 加入你的3D工具链 3D工具集&#xff1a; NSDT简石数字孪生 发射器模块(ShapeModule) 公有属性&#xff1a; 属性作用position相对于挂载节点的位置rotation相对于挂载节点的旋转scale相对于挂载节点的缩放sphericalDirectionAmount表示当前…

ESP32(Micro Python)LVGL 两个动画程序

本次发布两个程序&#xff0c;仪表盘动画程序对刻度数量等参数进行调整&#xff0c;方便布置多个小尺寸仪表盘&#xff1b;进度条动画程序展示了多个进度条的排列方式。 仪表盘程序 import lvgl as lv import time from espidf import VSPI_HOST from ili9XXX import ili93…

人机交互学习-6 交互式系统的设计

交互式系统的设计 设计框架定义外形因素和输入方法定义功能和数据元素决定功能组合层次勾画大致的设计框架构建关键情景场景剧本通过验证性的场景剧本来检查设计 设计策略删除组织隐藏转移简化设计策略的组合 设计中的折中个性化和配置本地化和国际化审美学与实用性 软件设计的…

Golang context 实现原理与源码分析

0 context入门介绍 context是Golang应用开发常用的并发控制技术&#xff0c;主要在异步场景中用于实现并发协调以及对 goroutine 的生命周期控制&#xff0c;它与WaitGroup最大的不同点是context对于派生goroutine有更强的控制力&#xff0c;它可以控制多级的goroutine。 con…

DataGrip使用技巧

DataGrip介绍 DataGrip是JetBrains提供的面向开发人员的数据库管理产品。提供智能查询控制台、高效的架构导航、智能SQL补全等功能。 同类的产品有navicat、dbeaver。本文中使用的DataGrip版本为2023.1 显示数据库其他类型的数据库结构 DataGrip中如果某类型数据库结构数量为…

GaussDB单SQL性能慢分析

文章目录 问题描述问题现象告警单SQL性能慢分析步骤一&#xff1a;确定目标SQL步骤二&#xff1a;收集统计信息、提前排除影响步骤三&#xff1a;分析SQL性能瓶颈 单SQL性能慢-视图分析流控导致慢SQL并发锁冲突导致慢SQL表膨胀导致大量的死元组业务语句不优、计划不优 问题描述…

8自由度并联腿机器狗实现姿态平衡

1. 功能说明 本文示例将实现8自由度并联腿机器狗保持姿态平衡的功能&#xff0c;当机器狗在一个平台上原地站立&#xff0c;平台发生倾斜时&#xff0c;机器狗能够自动调整姿态&#xff0c;保证背部水平。 2. 机器狗的稳定性分析 稳定性是机器狗运动中很重要的一部分&#xff0…

Leetcode | 35 搜索插入位置

35 搜索插入位置 文章目录 35 搜索插入位置题目我的思路[官方题解](https://leetcode.cn/problems/search-insert-position/solutions/333632/sou-suo-cha-ru-wei-zhi-by-leetcode-solution/ "官方题解")欢迎关注公众号【三戒纪元】 题目 给定一个排序数组和一个目标…

Rocky linux 9.0系统安装MySQL5.7

前言 本文将带你在Rocky linux 9.0上折腾一个MySQL5.7&#xff0c; 说干就干。 文章目录 前言安装环境下载mysql 包开始安装启动测试总结 安装环境 删除系统中可能存在的包&#xff1a; [rootmufeng ~]# rpm -qa |grep mysql [rootmufeng ~]# rpm -qa |grep mariadb [rootmu…