二叉搜索数使用,底层原理及代码实现

1:二叉搜索树的定义

二叉搜索树的底层是一个二叉链表

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

举个例子,我们要把数组[50,87,64,100,24,35,1,77]插入到二叉搜索树中,对应的树应该是这个样子

2.二叉搜索树的特点

  1. 既然以搜索树为名字,显然搜索功能是很强大的(相较于数组的搜索),对于二叉树的每一个节点来说其左子树的任意值绝对小于根节点,右子树的任意值绝对大于根节点,如果我们要查找一个值,该值比根节点小的话去左子树找,比根节点大的话去右子树找,如果二叉搜索树是一颗满二叉树的话,搜索的时间复杂度将为log(N),相当于从全世界70亿人中找一个人仅用了30次操作.
  2. 因为二叉树的左子树的任意值绝对小于根节点,右子树的任意值绝对大于根节点,所以其中序遍历即为升序数组,搜索树理论上不可以同时存在多个相同的元素,因为这是没有意义的,所以严格来说,这是一个无重复元素的升序数组

3.二叉搜索树的底层原理

插入

显然原树在执行完插入操作后仍应该是二叉搜索树,为了不破坏原树的结构,对于根节点来说,如果插入值大于根节点,应该往右插入,插入值小于根节点,应该往左插入.直到最后1.找到的节点为空,代表应该向空节点处插入2.存在节点值 == 插入值,这样的插入是无意义的,插入失败!

以上图为例,如果要插入一个76

模拟实现

/*	template <class T>//二叉搜索的节点struct TreeNode {typedef TreeNode<T> Node;Node* _left;Node* _right;T  _val;//构造函数TreeNode(T val = T()):_left(nullptr),_right(nullptr),_val(val){}};
*/bool insert(T val) {if (_root == nullptr){_root = new Node(val);}Node* child = _root;Node* parent = _root;while (child != nullptr) {parent = child;if (val < child->_val)	//val比根节点小,往左走{child = child->_left;}else if (val > child->_val)	//val比根节点大,往右走{child = child->_right;}else {return false;	//val与根节点相等,二叉搜索树中不许存在相同元素,所以不可插入,返回假}}if (parent->_val < val)	parent->_right = new Node(val);else if (parent->_val > val)	parent->_left = new Node(val);return true;	//插入成功
}

查找

查找与插入很类似,大于的话去右边找,小于的话去左边找,找到了返回节点,没找到返回nullptr

以找64为例

模拟实现

/*	template <class T>//二叉搜索的节点struct TreeNode {typedef TreeNode<T> Node;Node* _left;Node* _right;T  _val;//构造函数TreeNode(T val = T()):_left(nullptr),_right(nullptr),_val(val){}};
*/Node* find(T val) {Node* tmp = _root;while (tmp != nullptr) {if (val == tmp->_val)return tmp;	//找到啦else if (val > tmp->val) {tmp = tmp->_right;		//val大于该节点的值,去右子树找}else {tmp = tmp->_left;		//val小于该节点的值,去左子树找}}return nullptr;		//树里没有val,不存在该节点
}

删除

这边我们重画一个比较大一些的搜索二叉树以便于举例,数据很简单,1到16

首先,当删去8时,其余的所有节点都仍旧符合搜索二叉树的定义,删去其他叶子节点有相同的效果,同理,在删去2节点时,理论上来说仅有2的子树受到影响,搜索树的其他部分仍旧符合定义.

再以删去九号节点为例,由于二叉树的定义,删去九号节点后新节点的值必须满足除自身外,左子树都小于其,右子树都大于其.

由上图为例,对二叉树进行分析可得:除8外的左树<8<9<10<除10外的右树,也就是说:

左子树的右边的右边的右边的......的叶节点和右子树左边的左边的.......的叶节点最适合做根节点


特殊情况1:左子树没有右节点

使用左子树自身的根节点即可.


特殊情况2:没有左子树&&只有一个孩子节点

上图为删除节点4的示意图,可以看出,4号节点没有左子树,此时4号节点仅有一个子节点,直接让子节点代替自己即可


模拟实现

		bool earse(const T& val) {Node* parent = _root;Node* child = _root;while (child!= nullptr) {if (val > child->_val){parent = child;child = child->_right;}else if (val < child->_val){parent = child;child = child->_left;}else   //相等{if (child->_left == nullptr){if (child == _root)_root = child->_right;else {if (parent->_left == child)	parent->_left = child->_right;else if (parent->_right == child)	parent->_right = child->_right;}delete child;}else if (child->_right == nullptr) {if (child == _root)_root = child->_left;else {if (parent->_left == child)	parent->_left = child->_left;else if (parent->_right == child)	parent->_right = child->_left;}delete child;}else {Node* tmp = child->_left;Node* pp = tmp;while (tmp->_right) {pp = tmp;tmp = tmp->_right;}child->_val = tmp->_val;if (pp != tmp)pp->_right = tmp->_left;delete tmp;}return true;}}return false;}

高度(不重要)

	public:size_t height() {return _height(_root);}private:size_t _height(Node* root) {if (root == nullptr)return 0;elsereturn _height(root->_left) + _height(root->_right) + 1;}

节点个数(不重要)

	public:size_t size() {return _size(_root);}private:size_t _size(Node* root) {if (root == nullptr)return 0;return _size(root->_left) + _size(root->_right) + 1;}

打印(不重要)

void print() {std::queue<Node*> q1,q2;q1.push(_root);while (!q1.empty() || !q2.empty()) {while (!q1.empty()) {if (q1.front() == nullptr)std::cout << '#' << ' ';else {q2.push(q1.front()->_left);q2.push(q1.front()->_right);std::cout << q1.front()->_val << ' ';}q1.pop();}std::swap(q1,q2);std::cout << std::endl;}}

搜索二叉树模拟实现

#include <queue>
#include <iostream>
namespace SearchTree {template <class T>//二叉搜索的节点struct TreeNode {typedef TreeNode<T> Node;Node* _left;Node* _right;T  _val;//构造函数TreeNode(T val = T()):_left(nullptr),_right(nullptr),_val(val){}};template <class T>class SearchTree {typedef TreeNode<T> Node;Node* _root;	//根节点public:SearchTree():_root(nullptr){}		//构造函数bool insert(T val) {if (_root == nullptr){_root = new Node(val);}Node* child = _root;Node* parent = _root;while (child != nullptr) {parent = child;if (val < child->_val)	//val比根节点小,往左走{child = child->_left;}else if (val > child->_val)	//val比根节点大,往右走{child = child->_right;}else {return false;	//val与根节点相等,二叉搜索树中不许存在相同元素,所以不可插入,返回假}}if (parent->_val < val)	parent->_right = new Node(val);else if (parent->_val > val)	parent->_left = new Node(val);return true;	//插入成功}bool earse(const T& val) {Node* parent = _root;Node* child = _root;while (child!= nullptr) {if (val > child->_val){parent = child;child = child->_right;}else if (val < child->_val){parent = child;child = child->_left;}else   //相等{if (child->_left == nullptr){if (child == _root)_root = child->_right;else {if (parent->_left == child)	parent->_left = child->_right;else if (parent->_right == child)	parent->_right = child->_right;}delete child;}else if (child->_right == nullptr) {if (child == _root)_root = child->_left;else {if (parent->_left == child)	parent->_left = child->_left;else if (parent->_right == child)	parent->_right = child->_left;}delete child;}else {Node* tmp = child->_left;Node* pp = tmp;while (tmp->_right) {pp = tmp;tmp = tmp->_right;}child->_val = tmp->_val;if (pp != tmp)pp->_right = tmp->_left;delete tmp;}return true;}}return false;}Node* find(T val) {Node* tmp = _root;while (tmp != nullptr) {if (val == tmp->_val)return tmp;	//找到啦else if (val > tmp->val) {tmp = tmp->_right;		//val大于该节点的值,去右子树找}else {tmp = tmp->_left;		//val小于该节点的值,去左子树找}}return nullptr;		//树里没有val,不存在该节点}void print() {std::queue<Node*> q1,q2;q1.push(_root);while (!q1.empty() || !q2.empty()) {while (!q1.empty()) {if (q1.front() == nullptr)std::cout << '#' << ' ';else {q2.push(q1.front()->_left);q2.push(q1.front()->_right);std::cout << q1.front()->_val << ' ';}q1.pop();}std::swap(q1,q2);std::cout << std::endl;}}size_t size() {return _size(_root);}size_t height() {return _height(_root);}private:size_t _size(Node* root) {if (root == nullptr)return 0;return _size(root->_left) + _size(root->_right) + 1;}size_t _height(Node* root) {if (root == nullptr)return 0;elsereturn std::max(_height(root->_left) , _height(root->_right)) + 1;}};
}

对搜索二叉树的分析及AVL树红黑树的优势

参考我在前文查找处的分析可得,查找的时间复杂度与树的高度,空间复杂度恒为o(1),当插入从a到b的值是,优先输入[a,b]中间的值,后输入接近a,b的极端值,此时树较为平衡,查找的时间复杂度接近o(logn),而当数据有序的进行插入时,树相当于一个链表,时间复杂度较高,为(o(n))

举个例子,当把数据[1,2,3,4,5,6,7]插入二叉树时

以[4,2,1,3,6,5,7]插入时,如果我们要查找7,需要进行三次判断即可,

以[1,2,3,4,5,6,7]插入时,如果我们要查找7,需要进行七次判断!!!!时间复杂度为o(n),再加上额外的空间开销,还不如直接在原数组中查找

如果有一种改进的插入方式可以在插入时,依据原树的高度差进行动态的重构树结构,便可大大加快查找速度

附:AVL树模拟实现(供参考)

#include <queue>
#include <iostream>
#include <assert.h>
namespace AVL {template<class T>struct AVLTreeNode{AVLTreeNode(const T& data = T()): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft;AVLTreeNode<T>* _pRight;AVLTreeNode<T>* _pParent;T _data;int _bf;   // 节点的平衡因子};// AVL: 二叉搜索树 + 平衡因子的限制template<class T>class AVLTree{typedef AVLTreeNode<T> Node;public:AVLTree(): _pRoot(nullptr){}void print() {std::queue<Node*>q1;std::queue<Node*>q2;q1.push(_pRoot);while (!q1.empty() || !q2.empty()) {while (!q1.empty()){if (q1.front() != nullptr) {std::cout << q1.front()->_data << ' ';q2.push(q1.front()->_pLeft);q2.push(q1.front()->_pRight);}elsestd::cout << '#' << ' ';q1.pop();}swap(q1, q2);std::cout << std::endl;}}// 在AVL树中插入值为data的节点bool Insert(const T& data) {Node* node = new Node(data);if (_pRoot == nullptr) {_pRoot = node;return true;}Node* parent = _pRoot,*child = _pRoot;while (child) {parent = child;if (child->_data > data)	child = child->_pLeft;else if (child->_data < data)	child = child->_pRight;else return false;}if (parent->_data < data){parent->_pRight = node;}else{parent->_pLeft = node;}node->_pParent = parent;while (parent) {if (node == parent->_pLeft)parent->_bf--;elseparent->_bf++;if (parent->_bf == 0)break;else if (parent->_bf == 1 || parent->_bf == -1)node = parent, parent = parent->_pParent;else {		//出问题了if (parent->_bf == -2 && node->_bf == -1) {RotateR(node);}else if (parent->_bf == -2 && node->_bf == 1){RotateLR(node);}else if (parent->_bf == 2 && node->_bf == 1) {RotateL(parent);}else if (parent->_bf == -2 && node->_bf == -1) {RotateRL(node);}else {//   树损坏}}}return true;}// AVL树的验证bool IsAVLTree(){return _IsAVLTree(_pRoot);}private:// 根据AVL树的概念验证pRoot是否为有效的AVL树bool _IsAVLTree(Node* pRoot) {std::queue<Node*> q;q.push(pRoot);while (!q.empty()) {if (q.front() == nullptr){q.pop();continue;}if (q.front()->_bf >= 2 || q.front()->_bf <= -2){return false;}else {q.push(q.front()->_pLeft);q.push(q.front()->_pRight);q.pop();}}return true;}size_t _Height(Node* pRoot) {size_t h = 0;std::queue<Node*> q1;std::queue<Node*> q2;q2.push(pRoot);while (!q1.empty()&&!q2.empty()) {while (!q1.empty()){if (q1.front() != nullptr){q2.push(q1.front()->_pLeft);q2.push(q1.front()->_pRight);}q1.pop();}std::swap(q1, q2);h++;}q1.empty();q2.empty();return h - 1;}// 左单旋void RotateL(Node* pParent) {//新的头节点Node* new_Parent = pParent->_pRight;//旧头结点的父节点Node* grand = pParent->_pParent;//修改pParent的父节点if (grand != nullptr){if (grand->_pLeft == pParent)grand->_pLeft = new_Parent;elsegrand->_pRight = new_Parent;}else {_pRoot = new_Parent;}// 修改pParent节点pParent->_pParent = new_Parent;pParent->_pRight = new_Parent->_pLeft;//修改new_Parent节点if(new_Parent->_pLeft!=nullptr)new_Parent->_pLeft->_pParent = pParent;new_Parent->_pLeft = pParent;new_Parent->_pParent = grand;pParent->_pParent = new_Parent;pParent->_bf = new_Parent->_bf = 0;}// 右单旋void RotateR(Node* pParent) {//新的头节点Node* new_Parent = pParent->_pLeft;//旧头结点的父节点Node* grand = pParent->_pParent;//修改pParent的父节点if (grand != nullptr){if (grand->_pLeft == pParent)grand->_pLeft = new_Parent;elsegrand->_pRight = new_Parent;}else {_pRoot = new_Parent;}// 修改pParent节点pParent->_pParent = new_Parent;pParent->_pLeft = new_Parent->_pRight;//修改new_Parent节点if(new_Parent->_pRight!=nullptr)new_Parent->_pRight->_pParent = pParent;new_Parent->_pRight = pParent;new_Parent->_pParent = grand;pParent->_pParent = new_Parent;pParent->_bf = new_Parent->_bf = 0;}// 右左双旋void RotateRL(Node* pParent) {RotateR(pParent);RotateL(pParent->_pParent->_pParent);}// 左右双旋void RotateLR(Node* pParent) {RotateL(pParent);RotateR(pParent->_pParent->_pParent);}protected:Node* _pRoot;};
}

结语

截止至结语,本文已有接近1万字,制作不易可以留个免费的赞吗

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

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

相关文章

Redis-配置文件详解

Redis配置文件详解 units单位 配置大小单位&#xff0c;开头定义基本度量单位&#xff0c;只支持bytes&#xff0c;大小写不敏感。 INCLUDES Redis只有一个配置文件&#xff0c;如果多个人进行开发维护&#xff0c;那么就需要多个这样的配置文件&#xff0c;这时候多个配置 文…

docker安装向量数据库milvus

Miluvs Milvus 向量数据库能够帮助用户轻松应对海量非结构化数据(图片 / 视频 / 语音 / 文本)检索。 单节点 Milvus 可以在秒内完成十亿级的向量搜索,分布式架构亦能满足用户的水平扩展需求。 Milvus 向量数据库的应用场景包括:互联网娱乐(图片搜索 / 视频搜索)、新零售…

MATLAB基础—系统环境

1.MATLAB操作界面的组成 (1)MATLAB主窗口&#xff08;红色&#xff09; MATLAB主窗口是MATLAB的程序窗口&#xff0c;他除了嵌入一功能窗口外&#xff0c;主要包括功能区(1)&#xff0c;快速访问工具栏(2)&#xff0c;和当前文件夹工具栏(3)。 在功能区提供了三个选项卡&#…

浅析vue3自定义指令

vue3中可以像下面这样使用自定义指令。 这里我们只是定义了一个vFoucs变量&#xff0c;vue怎么知道这是一个指令呢&#xff1f; 这是因为约定大于配置&#xff0c;vue3中有这样一个约定&#xff08;截图来自官方文档&#xff09;&#xff1a; 注意这里说的是驼峰命令&#x…

机器学习案例:加州房产价格(一)

参考链接&#xff1a;https://hands1ml.apachecn.org/2/ 假设你是被一家地产公司雇佣的数据科学家&#xff0c;现在需要做一些工作。 公司所给的数据集是StatLib 的加州房产价格数据集。这个数据集是基于 1990 年加州普查的数据。数据已经有点老&#xff0c;但它有许多优点&…

【三十一】springboot+easyExcel实现多文件导出压缩包

互相交流入口地址 整体目录&#xff1a; 【一】springboot整合swagger 【二】springboot整合自定义swagger 【三】springboot整合token 【四】springboot整合mybatis-plus 【五】springboot整合mybatis-plus 【六】springboot整合redis 【七】springboot整合AOP实现日志操作 【…

【数字IC设计】芯片设计中的RDC

RDC问题定义 在芯片设计中,RDC是reset domain crossing 的缩写,类似于CDC(clock domain crossing),由于现在SOC芯片是有很多ECUs组成,为了使整个系统能够快速从复位中恢复, 用户希望SOC里面每个ECU模块都可以有自己独立的异步复位信号,这样可以在出问题的时候只复位有错…

【计算机网络篇】数据链路层(8)共享式以太网的退避算法和信道利用率

文章目录 &#x1f6f8;共享式以太网的退避算法&#x1f95a;截断二进制指数算法 &#x1f354;共享式以太网的信道利用率 &#x1f6f8;共享式以太网的退避算法 在使用CSMA/CD协议的共享总线以太网中&#xff0c;正在发送帧的站点一边发送帧一边检测碰撞&#xff0c;当检测到…

【Cesium】Cesium核心类、坐标系与着色器简介

核心类&#xff1a; Viewer: Viewer 是 Cesium 中最基本的视图容器&#xff0c;用于显示地球、地图、三维场景等。它提供了创建和管理场景的功能&#xff0c;可以配置视图的各种属性和行为。 Scene: Scene 是 Cesium 中的核心类之一&#xff0c;代表了一个三维场景&#xff0c…

PopClip for Mac 激活版:让文本处理更高效

还在为繁琐的文本处理而烦恼吗&#xff1f;PopClip for Mac来帮您解决&#xff01;这款神器般的文本处理工具&#xff0c;能让您轻松应对各种文本处理任务。无论是写作、编程还是日常办公&#xff0c;PopClip for Mac都能助您一臂之力&#xff0c;让您的文本处理更高效、更便捷…

【linux-IMX6ULL-定时器-GPT-串口配置流程-思路】

目录 1. 定时器配置流程1.1 EPIT定时器简介1.2 定时器1(epit1)的配置流程1.3 配置代码(寄存器版本)1.4 定时器-配合按键消抖1.4.1 实现原理1.4.2 代码实现&#xff08;寄存器版&#xff09; 2. GPT定时器实现高精度延时2.1 延时原理分析2.2 代码实现 3. UART串口配置流程3.1 UA…

微信小程序按钮去除边框线

通常我们去掉按钮边框直接设置 border:0 但是在小程序中无效&#xff0c;设置outline:none也没用&#xff0c;当然可能你会说加权重&#xff1b;试过了无效 实际上该样式是在伪元素::after内&#xff0c;主要你检查css 还看不到有这个关系&#xff0c;鹅厂就是坑多 类样式::…

半小时搞懂STM32面经知识点——IIC

1.IIC 1.1什么是IIC&#xff1f; 同步半双工通信协议&#xff0c;适用于小数据和短距离传输。 1.2 IIC需要几条线&#xff1f; IIC总共有2条通信总线&#xff08;SDA,SCL&#xff09;&#xff0c;SCL为时钟同步线&#xff0c;用于主机和从机间数据同步操作&#xff1b;SDA为…

【密评】 | 商用密码应用安全性评估从业人员考核题库(9/58)

Hill密码是重要古典密码之一&#xff0c;其加密的核心思想的是&#xff08;&#xff09;。 A.线性变换 B.非线性变换 C.循环移位 D.移位 著名的Kerckhoff原则是指&#xff08;&#xff09;。 A.系统的保密性不但依赖于对加密体制或算法的保密&#xff0c;而且依赖于密钥 B.系统…

【计算机网络】数据链路层的功能

数据链路层的基本功能&#xff1a; 封装成帧透明传输差错检测 数据链路层使用的信道主要有两种 点对点信道——PPP协议广播信道——CSMA/CD协议(有线局域网)、CSMA/CA协议(无线局域网) 数据链路层所处的地位 从图中可以看出&#xff0c;数据从主机H1送到主机H2需要在路径中…

论文笔记模版

1. 摘要 1.1 背景 1.2 挑战 1.3 提出新方法 1.4 贡献 2. 引言 2.1 背景&#xff08;引出问题&#xff09; ①介绍大背景&#xff1a; ② 应用场景&#xff1a; ③ 介绍主题&#xff1a; 2.2 引出挑战 一般用图表来展现出我们的挑战&#xff08;直观&#xff0c;解决什…

echarts环形图 legend文字过长显示...鼠标移动上展示全称

legend: {type: scroll,orient: vertical,x: left,y: bottom,top: "42%",left: 13%,data: this.dutyNames,textStyle: { color: #fff },triggerEvent: true,tooltip: {show: true,trigger: item,//鼠标移动上去展示全称},formatter: function (params) {var val &qu…

HTML五彩缤纷的爱心

写在前面 小编准备了一个五彩缤纷的爱心&#xff0c;送给各位小美女们~ 在桌面创建一个.txt文本文件&#xff0c;把代码复制进去&#xff0c;将后缀.txt改为.html&#xff0c;然后就可以双击运行啦&#xff01; HTML简介 HTML&#xff08;超文本标记语言&#xff09;是一种…

C++——二叉树搜索树

前面写了初阶数据结构——二叉树&#xff1b;本文内容是来对它来进行结尾 目录 一概念 二实现 2.1查找 2.2插入 2.3删除 完整源代码 三二叉树的应用 四二叉搜索树的性能分析 五二叉搜索树相关的面试题 一概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树…

妙笔生花,创作无限——WonderPen妙笔 for Mac

写作&#xff0c;是灵感的流淌&#xff0c;是心灵的独白。WonderPen妙笔 for Mac&#xff0c;为您的灵感插上翅膀&#xff0c;让您的创作更加流畅自如。它拥有简洁直观的界面设计&#xff0c;让您的思绪在纯净的写作环境中自由飞翔。多种写作模式&#xff0c;满足您不同的创作需…