【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


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

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

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

相关文章

无涯教程-PHP - preg_split()函数

preg_split() - 语法 array preg_split (string pattern, string string [, int limit [, int flags]]); preg_split()函数的操作与split()完全相同&#xff0c;只不过正则表达式被接受为pattern的输入参数。 如果指定了可选的输入参数limit&#xff0c;则仅返回子字符串的限…

Oracle 主从库目录不一致(异路径)的n种处理方案及效果

最近遇到了复制数据&#xff08;DUPLICATE TARGET DATABASE TO xxx&#xff09;的时候 Oracle 源和目标库目录不一致的问题&#xff0c;比较初级但也踩到一些坑&#xff0c;整理记录一下。主从库搭建的时候注意事项其实也类似&#xff0c;而且更通用&#xff0c;所以标题写的是…

WebGL 变量uniform、gl.getUniformLocation、gl.uniform4f及其同族函数相关

目录 uniform变量命名规范 获取 uniform 变量的存储地址 gl.getUniformLocation 向uniform变量赋值 gl.uniform4f ​编辑 gl.uniform4f()的同族函数 demo&#xff1a;点击webgl坐标系的四个象限绘制各自不同颜色的点 uniform变量命名规范 var FSHADER_SOURCE uniform vec4…

小程序-基于vant的Picker组件实现省市区选择

一、原因 因vant/area-data部分的市/区数据跟后台使用的高德/腾讯省市区有所出入&#xff0c;故须保持跟后台用同一份数据&#xff0c;所以考虑以下几个组件 1、Area 2、Cascader 3、Picker 因为使用的是高德地图的省市区json文件&#xff0c;用area的话修改结构代价太大&…

AVL——平衡搜索树

✅<1>主页&#xff1a;我的代码爱吃辣&#x1f4c3;<2>知识讲解&#xff1a;数据结构——AVL树☂️<3>开发环境&#xff1a;Visual Studio 2022&#x1f4ac;<4>前言&#xff1a;AVL树是对二叉搜索树的严格高度控制&#xff0c;所以AVL树的搜索效率很高…

xargs 的用法 在1个文件夹中批量删除文件,这些删除的文件名是另一个文件夹中的文件名。

xargs 的用法 在1个文件夹中批量删除文件&#xff0c;这些删除的文件名是另一个文件夹中的文件名。 1、问题背景 应用场景 1、问题背景 应用场景 在二进制部署docker时&#xff0c;会把docker的所有可执行文件复制到/usr/bin下。 如果说复制过去后&#xff0c;想要反悔&#x…

零散笔记:《Spring实战》Thymeleaf

1、Thymeleaf模板就是增加一些额外元素属性的HTML&#xff0c;这些属性能够指导模板如何渲染request数据。 <p th:test "${message}">placeholder message</p> th我推测是中文的”替换“。 2、th:each&#xff0c;迭代元素集合。 <div th:each &qu…

VB.NET通过VB6 ActiveX DLL调用PowerBasic及FreeBasic动态库

前面说的Delphi通过Activex DLL同时调用PowerBasic和FreeBasic写的DLL&#xff0c;是在WINDOWS基础平台上完成的。 而 .NET平台是架在WINDOWS基础平台之上的&#xff0c;它的上面VB.NET或C#等开发的APP程序&#xff0c;下面写一下用VB.NET&#xff0c;通过VB6注册的Activex DLL…

一文速学-让神经网络不再神秘,一天速学神经网络基础(一)

前言 思索了很久到底要不要出深度学习内容&#xff0c;毕竟在数学建模专栏里边的机器学习内容还有一大半算法没有更新&#xff0c;很多坑都没有填满&#xff0c;而且现在深度学习的文章和学习课程都十分的多&#xff0c;我考虑了很久决定还是得出神经网络系列文章&#xff0c;…

C语言刷题(13)

第一题 第二题 第三题 第四题 第五题 第六题 第七题 注意 1.nsqrt(n)&#xff0c;sqrt本身不会将n开根 2.初始化已经令sumn了&#xff0c;故相加的个数为m-1次

DataGridView keydown事件无法在C#中工作

原因&#xff1a;单元格内编辑文本时,DataGridView keydown事件不起作用。每当单元格处于编辑模式时,其托管控件就会接收KeyDown事件而不是DataGridView包含它的父级.这就是为什么当单元格未处于编辑模式时(即使它被选中),键盘快捷键正常工作,因为DataGridView控件本身会收到Ke…

K8S用户管理体系介绍

1 K8S账户体系介绍 在k8s中&#xff0c;有两类用户&#xff0c;service account和user&#xff0c;我们可以通过创建role或clusterrole&#xff0c;再将账户和role或clusterrole进行绑定来给账号赋予权限&#xff0c;实现权限控制&#xff0c;两类账户的作用如下。 server acc…

小航助学GESP_C++一级模拟测试卷第2套(含题库答题软件账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSDN博客 需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSD…

JavaScript中的let和var区别

JavaScript中的let和var是两种变量声明的方式&#xff0c;它们有一些区别&#xff1a; 作用域&#xff1a; var声明的变量具有函数作用域&#xff0c;即在声明的函数内部有效。如果没有在函数内部声明&#xff0c;变量则为全局作用域。 let声明的变量具有块作用域&#xff0c;…

Yolov8红外弱小目标检测(7):BiFPN高效双向跨尺度连接和加权特征融合,助力小目标检测

💡💡💡本文改进:BiFPN高效双向跨尺度连接和加权特征融合 ,有效提升小目标难检测问题; BiFPN | 亲测在红外弱小目标检测涨点明显,map@0.5 从0.755提升至0.766 💡💡💡Yolo小目标检测,独家首发创新(原创),适用于Yolov5、Yolov7、Yolov8等各个Yolo系列,专栏…

【LeetCode 算法】Add Two Integers 两整数相加-位运算

文章目录 Add Two Integers 两整数相加问题描述&#xff1a;分析代码递归 Tag Add Two Integers 两整数相加 问题描述&#xff1a; 给你两个整数 num1 和 num2&#xff0c;返回这两个整数的和。 − 100 < n u m 1 , n u m 2 < 100 -100 < num1, num2 < 100 −1…

WPF入门到精通:3.MVVM简单应用及全局异常处理

MVVM简介 在WPF应用程序开发中&#xff0c;MVVM&#xff08;Model-View-ViewModel&#xff09;是一种非常流行的架构模式。它为应用程序的设计提供了良好的分层结构和可扩展性。 结构分为下列三部分 Model&#xff1a;定义了应用程序的数据模型 就是系统中的对象&#xff0c;…

Go的gin参数校验之validator库

使用 validator 以后&#xff0c;只需要在定义结构体时使用binding或validatetag标识相关校验规则&#xff0c;就可以进行参数校验了&#xff0c;而不用自己单独去写常见的校验规则。 main.go package mainimport ("fmt""github.com/go-playground/validator/…

大学生创业出路【第二弹】科创训练营

目录 &#x1f680;一、我从哪里了解到的训练营 &#x1f680;二、训练营里学习和日常 &#x1f50e;学习 &#x1f50e;环境和设备 &#x1f50e;遇到的人 &#x1f50e;团队记录视频 &#x1f680;三、感悟 ​​​​个人主页&#xff1a;一天三顿-不喝奶茶&#x1f39…

Room封装APP离线缓存框架HiStorage——jetpack技术

在移动应用开发中&#xff0c;离线缓存是提高用户体验和应用性能的关键策略之一。为了简化开发者的工作&#xff0c;Jetpack技术组件提供了一系列强大的工具&#xff0c;其中Room是一个功能强大的数据库持久化库。 HiStorage&#xff0c;它提供了简单易用的接口&#xff0c;帮助…