【C++进阶】二叉树搜索树

⭐博客主页:️CS semi主页
⭐欢迎关注:点赞收藏+留言
⭐系列专栏:C++进阶
⭐代码仓库:C++进阶
家人们更新不易,你们的点赞和关注对我而言十分重要,友友们麻烦多多点赞+关注,你们的支持是我创作最大的动力,欢迎友友们私信提问,家人们不要忘记点赞收藏+关注哦!!!

二叉树搜索树

  • 一、二叉搜索树
    • 1、概念
    • 2、搜索二叉树的实现(非递归版本和递归版本)
      • (1)搭个框架
      • (2)查找
        • i、非递归版本
        • ii、递归版本
      • (3)插入
        • i、非递归版本
        • ii、递归版本
      • (4)删除
        • i、非递归版本
        • ii、递归版本
      • (5)中序遍历
      • (7)拷贝
      • (8)赋值
      • (9)销毁
  • 二、二叉树的性能分析
  • 三、二叉搜索树的应用
    • Key模型
    • Key-Value模型


一、二叉搜索树

1、概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树

以下均是搜索二叉树,简而言之就是左小右大。
在这里插入图片描述

2、搜索二叉树的实现(非递归版本和递归版本)

(1)搭个框架

要实现二叉搜索树,我们需要搭建一个结点类的框架:
##1、结点类当中包含三个成员变量:节点值,左指针,右指针。
##2、结点类只需要一个构造函数即可,用于构造指定节点值的结点。

// 先来个结点的定义
template<class K>
// struct为了默认是开放的
struct BSTreeNode
{// 左树右树和值BSTreeNode<K>* _right;BSTreeNode<K>* _left;K _key;// 构造一下左右结点和值(构造函数)BSTreeNode(const K& key):_right(nullptr), _left(nullptr), _key(key){}
};
// 二叉搜索树的定义
template<class K>
class BSTree
{
public:// 结点重命名为Nodetypedef BSTreeNode<K> Node;// 构造函数BSTree():_root(nullptr){}
private:Node* _root;
};

(2)查找

i、非递归版本

从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。最多查找高度次,走到到空,还没找到,这个值不存在。
在这里插入图片描述

// 查找bool Find(const K& key){// 定义一下当前节点为根节点Node* cur = _root;while (cur){// 值小则往右找if (cur->_key < key){cur = cur->_right;}// 值大则往左找else if (cur->_key > key){cur = cur->_left;}// 找到则返回trueelse{return true;}}// 找不到返回falsereturn false;}

ii、递归版本

代码:

// 查找 -- 递归版本bool FindR(const K& key){return _FindR(_root, key);}// 查找的子函数bool _FindR(Node* root, const K& key){if (root == nullptr)return false;if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return true;}}

解析:
在这里插入图片描述

(3)插入

i、非递归版本

  1. 树为空,则直接新增节点,赋值给root指针
  2. 树不空,按二叉搜索树性质(左小右大)查找插入位置,插入新节点
    (1)若待插入结点的值小于根节点的值,则需要将结点插入到左子树当中。
    (2)若待插入结点的值大于根节点的值,则需要将结点插入到右子树当中。
    (3)若待插入结点的值等于根结点的值,则插入结点失败。
    往后如此进行下去,直到找到与待插入结点的值相同的结点判定为插入失败,或者是最终插入到某叶子结点的左右子树中(即空树当中)。

在这里插入图片描述

// 插入bool Insert(const K& key){// _root为空if (_root == nullptr){_root = new Node(key);return true;}// _root不为空Node* cur = _root; // 记录一下当前节点从根节点开始Node* parent = nullptr; // 父亲节点用来记录要插入的父节点while (cur){// 插入值比当前节点大则往右树走if (cur->_key < key){parent = cur;cur = cur->_right;}// 插入值比当前节点小则往左树走else if (cur->_key > key){parent = cur;cur = cur->_left;}// 值相等则返回falseelse{return false;}}// 创建一个cur的带值的信结点cur = new Node(key);// 判断一层parent的值与key的值的大小if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}

ii、递归版本

神之一手:& – const K& key – 我下个递归版本是你的引用,我就是你,就不需要传参了。

在这里插入图片描述

// 查找
bool FindR(const K& key)
{return _FindR(_root, key);
}
// 插入的子函数
bool _InsertR(Node*& root, const K& 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;}
}

(4)删除

相对来讲会比较难一些,首先查找元素是否在二叉搜索树中,如果不存在,则返回false删除失败即可, 否则要删除的结点可能分下面三种情况:

&&&无儿无女型:也只有一种情况就是叶子结点,删除叶子结点,直接将其父亲节点指向空,把这个结点置空即可。
&&&独生家庭型:只有一个子节点,删除自己本身,并链接子节点和父节点。
&&&俩孩子型:可以让待删除的结点找其左子树中最大的结点保存值,然后将待删结点的值用左子树最大节点的值替代,最后将左子树最大结点删掉。或者是找其待删结点右子树当中最小的值的结点保存值,然后将待删结点的值用右子树最小结点的值代替,最后将右子树最小结点删掉。
俩孩子替换结点还有两种情况:倘若替换结点刚好是叶子结点没有孩子,直接删除置空即可。
倘若替换节点有一个孩子(不管左右孩子)就跟独生家庭的模式一模一样了!

解释为什么替换结点要么没孩子要么只有一个孩子:因为左小右大,倘若俩孩子都有,那这个替换节点一定不是左子树最大/右子树最小的结点!

在这里插入图片描述

i、非递归版本

代码层面解析:
实际上有四种:
1、左子树为空
2、右子树为空
3、左右子树都为空
4、要删的刚好是根节点

在这里插入图片描述

// 删除左子树的最右结点
bool Erase(const K& key)
{Node* parent = nullptr; // 刚开始定义parent为NULLNode* cur = _root; // 当前节点就是根节点while (cur) // 先确保找到位置{// 先找到要删除的位置if (cur->_key < key) // 往右找{parent = cur;cur = cur->_right;}else if (cur->_key > key) // 往左找{parent = cur;cur = cur->_left;}else // 找到了{// 左为空if (cur->_left == nullptr){// 要删除的结点刚好是根节点if (cur == _root){_root = cur->_right; // 左为空的情况下直接让右边第一个结点为根节点即可}// 要删除的不是根节点else{if (parent->_right == cur) // 前面确保parent已经往后找过结点了{parent->_right = cur->_right; // 直接连接parent的右和cur的右}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; // 精妙部分就是parent定义为当前节点,因为保证parent节点不为空Node* LeftMax = cur->_left;  // 法一:用的是左树的最右节点while (LeftMax->_right){parent = LeftMax;// parent永远比cur节点往前走一步LeftMax = LeftMax->_right;}swap(cur->_key, LeftMax->_key); // 交换待删结点的值和// 判断LeftMax在哪里的问题if (parent->_left == LeftMax){parent->_left = LeftMax->_left;}else{parent->_right = LeftMax->_left;}cur = LeftMax; // 当前节点就是最右节点}delete cur;return true;}}return false;
}
// 删除右子树的最左节点
// 删除
bool Erase(const K& key)
{Node* parent = nullptr; // 刚开始定义parent为NULLNode* cur = _root; // 当前节点就是根节点while (cur) // 先确保找到位置{// 先找到要删除的位置if (cur->_key < key) // 往右找{parent = cur;cur = cur->_right;}else if (cur->_key > key) // 往左找{parent = cur;cur = cur->_left;}else // 找到了{// 左为空if (cur->_left == nullptr){// 要删除的结点刚好是根节点if (cur == _root){_root = cur->_right; // 左为空的情况下直接让右边第一个结点为根节点即可}// 要删除的不是根节点else{if (parent->_right == cur) // 前面确保parent已经往后找过结点了{parent->_right = cur->_right; // 直接连接parent的右和cur的右}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; // 精妙部分就是parent定义为当前节点,因为保证parent节点不为空//	Node* LeftMax = cur->_left;  // 法一:用的是左树的最右节点//	while (LeftMax->_right)//	{//		parent = LeftMax;// parent永远比cur节点往前走一步//		LeftMax = LeftMax->_right;//	}//	swap(cur->_key, LeftMax->_key); // 交换待删结点的值和//	// 判断LeftMax在哪里的问题//	if (parent->_left == LeftMax)//	{//		parent->_left = LeftMax->_left;//	}//	else//	{//		parent->_right = LeftMax->_left;//	}//	cur = LeftMax; // 当前节点就是最右节点//}else{Node* parent = cur;Node* RightMin = cur->_right;while (RightMin->_left){parent = RightMin;RightMin = RightMin->_left;}swap(cur->_key, RightMin->_key);if (parent->_right = RightMin){parent->_right = RightMin->_right;}else{parent->_right = cur->_left;}cur = RightMin;}delete cur;return true;}}return false;
}

ii、递归版本

递归版本好理解,
1、若树为空树。则结点删除失败,返回false。
2、若所给的key值小于树的根节点的值,则问题变为删除左子树中值为key的结点。
3、若所给的key值大于树的根节点的值,则问题变为删除右子树中值为key的结点。
4、若所给的key值为根节点的值,则还是找左子树的最小结点或者右子树的最右结点按照下面的步骤进行即可。

根的左子树为空,新结点为根的右
根的右子树为空,新节点为根的左
根的左右子树都不为空,则可以找左子树的最大或者是右子树的最小值,交换然后删除当前节点即可。

// 删除
bool EraseR(const K& key)
{return _EraseR(_root, key);
}// 删除的子函数
bool _EraseR(Node*& root, const K& key)
{if (root == nullptr)return false;// 先找到要删除的数值if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else{Node* del = root;// 1、左为空// 2、右为空// 3、左右都不为空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(leftmax->_key, root->_key);return _EraseR(root->_left, key);}delete del;return true;}
}

(5)中序遍历

思路就是左子树-根-右子树,递归左子树打印再递归右子树。

// 中序遍历
void InOrder()
{_InOrder(_root);cout << endl;
}// 中序遍历的子函数
void _InOrder(Node* root)
{if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);
}

(7)拷贝

拷贝是深拷贝,创建一个copyroot的树然后递归拷贝左子树拷贝右子树,最后返回copyroot的树即可。

// 拷贝构造 -- 深拷贝
BSTree(const BSTree<K>& t)
{_root = Copy(t._root);
}
// 拷贝构造子函数
Node* Copy(Node* root)
{if (root == nullptr)return nullptr;Node* copyroot = new Node(root->_key);root->_left = Copy(root->_left); // 拷贝左子树root->_right = Copy(root->_right); // 拷贝右子树return copyroot;
}

(8)赋值

利用swap库函数直接赋值。

// 赋值
BSTree<K>& operator=(BSTree<K> t)
{swap(_root, t._root);return *this;
}

(9)销毁

// 销毁
void Destroy(Node*& root)
{if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;
}

二、二叉树的性能分析

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:log2N。

最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:N/2。

在这里插入图片描述


三、二叉搜索树的应用

Key模型

key的搜索模型,判断关键字在不在

比如我们刷卡进宿舍,链接终端找到这个人的信息即可。
检查一篇英文文章有没有拼写出错。

Key-Value模型

KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。

比如:中英文的转换,通过英文单词的输入能够找到相对应的中文翻译< word, chinese > 就构成键对值。

统计单词个数,统计这个单词的出现频次,< word, count >。

// 改造二叉搜索树为KV结构
template<class K, class V>
struct BSTNode
{BSTNode(const K& key = K(), const V& value = V()): _PLeft(nullptr), _PRight(nullptr), _key(key), _Value(value){}BSTNode<T>* _PLeft;BSTNode<T>* _PRight;K _key;V _value
};
template<class K, class V>
class BSTree
{typedef BSTNode<K, V> Node;typedef Node* PNode;
public:BSTree() : _PRoot(nullptr) {}PNode Find(const K& key);bool Insert(const K& key, const V& value);bool Erase(const K& key);
private:PNode _PRoot;
}void TestBSTree()
{// 输入单词,查找单词对应的中文翻译BSTree<string, string> dict;dict.Insert("string", "字符串");dict.Insert("tree", "树");dict.Insert("left", "左边、剩余");dict.Insert("right", "右边");dict.Insert("sort", "排序");// 插入词库中所有单词string str;while (cin >> str){BSTreeNode<string, string>* ret = dict.Find(str);if (ret == nullptr){cout << "单词拼写错误,词库中没有这个单词:" << str << endl;}else{cout << str << "中文翻译:" << ret->_value << endl;}}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一文读懂java变量类型

前言 在学习和使用Java编程语言时&#xff0c;理解变量类型是至关重要的基础知识。Java是一种静态类型语言&#xff0c;强调变量必须先声明其类型&#xff0c;才能进行后续操作。因此&#xff0c;对于初学者来说&#xff0c;了解Java中不同的变量类型及其特性是迈向编程成功的…

基于Alexnet深度学习网络的人员口罩识别算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 file_path1 test\mask\;% 图像文件夹路径 %获取测试图像文件夹下所有jpg格式的图像文件…

2023年9月NPDP产品经理国际认证报名来这里就对了

产品经理国际资格认证NPDP是新产品开发方面的认证&#xff0c;集理论、方法与实践为一体的全方位的知识体系&#xff0c;为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 【认证机构】 产品开发与管理协会&#xff08;PDMA&#xff09;成立于1979年&#xff0c;是…

Python网络爬虫库:轻松提取网页数据的利器

网络爬虫是一种自动化程序&#xff0c;它可以通过访问网页并提取所需的数据。Python是一种流行的编程语言&#xff0c;拥有许多强大的网络爬虫库。在本文中&#xff0c;我们将介绍几个常用的Python网络爬虫库以及它们的使用。 Requests库 Requests是一个简单而优雅的HTTP库&…

三维模型3DTile格式轻量化压缩处理工具常用几款软件介绍

三维模型3DTile格式轻量化压缩处理工具常用几款软件介绍 三维模型3DTile格式的轻量化处理旨在减少模型的存储空间和提高渲染性能。以下是一些推荐的工具软件&#xff0c;可以用于实现这个目的&#xff1a; MeshLab&#xff1a;MeshLab是一个开源的三维模型处理软件&#xff0c…

TensorFlow详解

TensorFlow详解 TensorFlow是一个开源的机器学习框架&#xff0c;由Google开发。它是一个强大、高度可扩展的计算框架&#xff0c;可以用于各种机器学习任务&#xff0c;包括图像和语音识别、自然语言处理、推荐系统等。 TensorFlow 是一种由 Google 开发的开源机器学习框架&am…

护航数字政府建设,美创科技成为“数字政府建设赋能计划”成员单位

近日&#xff0c;“2023软博会-软件驱动数字政府创新发展论坛”顺利召开&#xff0c;本次论坛由中国信息通信研究院、中国通信标准化协会承办&#xff0c;中国通信标准化协会云计算标准和开源推进委员会、数字政府建设赋能计划支持。 天津市工业和信息化局总经济师杨冬梅、中国…

Leetcode125. 验证回文串

力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s&…

Cpolar+Tipas:在Ubuntu上搭建私人问答网站,为您提供专业的问题解答

文章目录 前言2.Tipask网站搭建2.1 Tipask网站下载和安装2.2 Tipask网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar临时数据隧道3.2 Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3 Cpolar稳定隧道&#xff08;本地设置&#xff09; 4. 公网访问测试5. 结语 前…

Threejs汽车展厅

2023-09-06-16-29-40 预览&#xff1a;https://9kt8fy-1234.csb.app/ 源码链接

微信自动打招呼自动回复

点击蓝字 关注我们 微信无疑是我们日常生活中最常用的社交工具之一。但是&#xff0c;你有没有感觉到&#xff0c;每天都要花费大量时间去添加好友、回复简单咨询消息和打招呼&#xff0c;是一件很烦琐的事情呢&#xff1f;如果你也有这样的困扰&#xff0c;那么今天就给大家介…

如何注册喀麦隆商标?

想象一下&#xff0c;你正在喀麦隆的雨林中寻找宝藏&#xff0c;突然你发现了一个从未被人发现的部落。这个部落的人们用一种独特的图案作为他们的标记&#xff0c;来展示他们的身份和与众不同。这个图案就是喀麦隆的商标&#xff01; 在商业世界中&#xff0c;商标就像这个独特…

数据结构 每日一练:选择 + 编程

目录 选择 编程 选择 1、 设对n&#xff08;n>1&#xff09;个元素的线性表的运算只有4种&#xff1a;删除第一个元素&#xff0c;删除最后一个元素&#xff0c;在第一个元素之前插入新元素&#xff0c;在最后一个元素之后插入新元素&#xff0c;则最好使用&#xff08;&a…

IT运维:使用数据分析平台监控H3C交换机

概述 在企业日常运维中&#xff0c;设备种类繁多&#xff0c;日志格式各异&#xff0c;日志量巨大&#xff0c;大量的告警&#xff0c;我们面临着如何统一的存放这些日志&#xff1f;如何对海量的日志进行查看&#xff0c;分析&#xff1f;传统的日志设备无法满足日志格式各异的…

SpringBoot-Learning系列之Kafka整合

SpringBoot-Learning系列之Kafka整合 本系列是一个独立的SpringBoot学习系列&#xff0c;本着 What Why How 的思想去整合Java开发领域各种组件。 消息系统 主要应用场景 流量消峰(秒杀 抢购)、应用解耦&#xff08;核心业务与非核心业务之间的解耦&#xff09;异步处理、顺序…

在Creo 6.0中画图模板问题

在Creo 6.0中&#xff0c;文件的默认模板是英制模板“inlbs_part_solid”,此文件模板中尺寸的单位是inch。我们建模中需要的单位是mm&#xff0c;改变Creo文件默认的单位有两种方法。 1 【新建】对话框取消勾选【使用默认模板】对话框 &#xff08;1&#xff09;单击主页选项…

基于SSM的房屋租售网站

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

RabbitMQ学习笔记

1、什么是MQ&#xff1f; MQ全称message queue&#xff08;消息队列&#xff09;&#xff0c;本质是一个队列&#xff0c;FIFO先进先出&#xff0c;是消息传送过程中保存消息的容器&#xff0c;多 用于分布式系统之间进行通信。 在互联网架构中&#xff0c;MQ是一种非常常见的…

sql注入基本概念

死在山野的风里&#xff0c;活在自由的梦里 sql注入基本概念 MYSQL基本语法union合并查询2个特性&#xff1a;order by 排序三个重要的信息 Sql Server MYSQL 基本语法 登录 mysql -h ip -u user -p pass基本操作 show databases; 查看数据库crea…

2023Web前端开发面试手册

​​​​​​​​ HTML基础 1. HTML 文件中的 DOCTYPE 是什么作用&#xff1f; HTML超文本标记语言: 是一个标记语言, 就有对应的语法标准 DOCTYPE 即 Document Type&#xff0c;网页文件的文档类型标准。 主要作用是告诉浏览器的解析器要使用哪种 HTML规范 或 XHTML规范…