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

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 向量数据库的应用场景包括:互联网娱乐(图片搜索 / 视频搜索)、新零售…

【K8s】Kubectl 常用命令梳理

Kubectl常用命令梳理 下面包含大致涵盖命令只需要替换对应的Pod \ NameSpace 查看 命名空间 是 ’worktest2‘ 下 名字包括 ’todo‘的所有 Pod kubectl -n worktest2 get pod|grep todo查看 所有命名空间下 名字包括 ’todo‘的所有 Pod kubectl get pods --all-namespace…

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;但它有许多优点&…

查看ubuntu当前路径的剩余存储空间

要查看Ubuntu当前路径所在磁盘分区的剩余存储空间&#xff0c;应该使用df命令&#xff0c;而不是du命令&#xff0c;因为df命令能显示磁盘分区的使用情况&#xff0c;包括总容量、已用空间和可用空间。为了使输出更易于阅读&#xff0c;可以加上-h选项。如果你还想知道特定挂载…

Mysql中的DML

insert语法 指定字段添加数据&#xff1a;insert into 表名&#xff08;字段名1&#xff0c;字段名 2&#xff09;values &#xff08;值1&#xff0c;值2&#xff09; 全部字段添加数据&#xff1a;insert into 表名 values &#xff08;值1&#xff0c;值2&#xff0c;...&…

doc 链接

阿豪 Android Framework 坂田民工framework Android Framework_坂田民工的博客-CSDN博客 Harmony HarmonyOS第一课|应用开发视频教程学习|HarmonyOS应用开发官网 zh-cn/release-notes/OpenHarmony-v4.0-release.md OpenHarmony/docs - Gitee.com yi诺千金 yi诺千金_An…

【三十一】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;当检测到…

1080:余数相同问题

1080&#xff1a;余数相同问题 时间限制: 1000 ms 内存限制: 65536 KB 提交数:74558 通过数: 49412 【题目描述】 已知三个正整数a&#xff0c;b&#xff0c;c。现有一个大于1的整数x&#xff0c;将其作为除数分别除a&#xff0c;b&#xff0c;c&#xff0c;得到的…

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

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

react如何拿输入框的值

在React中获取输入框的值可以通过以下几个步骤实现&#xff1a; 首先&#xff0c;在React组件的状态中定义一个变量来存储输入框的值。可以使用useState钩子函数来创建一个状态变量。 在输入框的onChange事件中&#xff0c;通过事件对象获取输入框的值&#xff0c;并将其更新到…

道可云元宇宙每日资讯|苹果:通过自研芯片在云端推出AI功能

道可云元宇宙每日简报&#xff08;2024年5月11日&#xff09;讯&#xff0c;今日元宇宙新鲜事有&#xff1a; 苹果&#xff1a;通过自研芯片在云端推出AI功能 5月10日&#xff0c;有消息称苹果公司今年将通过配备自有处理器的数据中心提供一些即将推出的人工智能功能&#xff…

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

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

代码绘梦:Processing艺术编程入门

&#x1f469;‍&#x1f4bb; 欢迎来到Processing的世界&#xff0c;一个用代码绘制梦想的地方&#xff01;在这个入门篇中&#xff0c;我们将带你走进Processing的大门&#xff0c;让你轻松掌握基础&#xff0c;开始你的艺术编程之旅。 第一步&#xff1a;安装Processing &…

leetcode 1191.k次串联后最大子数组之和

首先上一下暴力dp解法&#xff0c;也就是直接延展dp数组得出来的结果&#xff1a; class Solution { public:int kConcatenationMaxSum(vector<int>& arr, int k) {int narr.size();vector<int>dp(n*k,0);int res0;dp[0]arr[0];resmax(dp[0],res);for(int i1;…

Linux处理用户输入

目录 一、传递参数 1.1 读取参数 1.2 读取脚本名 二、跟踪参数 三、移动参数 四、处理选项 4.1 查找选项 4.1.1 处理简单选项 4.1.2 分离参数和选项 4.1.3 处理含值的选项 五、选项标准化 5.1 使用 getopt 命令 5.1.1 命令格式 5.1.2 在脚本中使用getopt 5.2 使用…