【C++指南】一文总结C++二叉搜索树

news2025/4/3 6:25:35

🌟 各位看官好,我是egoist2023

🌍 种一棵树最好是十年前,其次是现在!

🚀 今天来学习C++二叉搜索树的实现。

👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦

目录

何为二叉搜索树

性能分析 

搜索树的结构

插入和查找(非冗余版本)

中序遍历

删除

key/value

使用场景

代码实现


何为二叉搜索树

二叉搜索(排序)树具有如下特征:

  • 若它的左子树不为空,则左子树上所有结点的值都小于等于根结点的值;
  • 若它的右⼦树不为空,则右子树上所有结点的值都大于等于根结点的值;
  • 它的左右⼦树也分别为二叉搜索树;
  • 二叉搜索树的中序遍历是有序的;
  • 了解二叉搜索树是为了之后为map/set/multimap/multiset的关联式容器打下基础。特别地,二叉搜索树分为冗余和不冗余版本。(冗余即有相等值也可插入)

在冗余版本的二叉搜索树中,会出现如下两种情况,那这两都是正确的吗?

是的,并不影响搜索二叉树的性质。

性能分析 

可以发现在该二叉搜索树较为平衡的情况下

即在最优情况下,找到指定结点的情况下,只需要访问高度k次,即高度为: log2 N;

但在最差情况下,高度会达到N。(在红黑树章节会有详解如何平衡高度差)

综合来讲,二叉搜索树增删查改时间复杂度为: O(N)。

二分查找也能达到 log2 N 级别的查找效率,为何不使用二分查找呢?二分查找有以下缺陷:

  1. 需要存储在⽀持下标随机访问的结构中,并且有序。
  2. 插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除数据一般需要挪动数据。
二叉搜索树在最差情况下能达到O(N)级别,那它能起到什么作用呢?
二叉搜索树的引入是为了后续为平衡二叉树AVL树和红⿊树作铺垫,之后引入了旋转的概念,能够好维持二叉树的结构。

搜索树的结构

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

插入和查找(非冗余版本)

 插入时分为以下情况:

  1. 树为空时,申请一个新结点赋值给根结点_root;
  2. 树不为空时,插⼊值⽐当前结点⼤往右⾛,插⼊值⽐当前结点⼩往左⾛,找到空位置,插⼊新结点。(如若支持冗余,则可往左走,也可往右走,确保逻辑一致性)

bool Insert(const K& 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->_right;
		}
		else if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	cur = new Node(key);
	if (parent->_key > key)
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}

	return true;
}
  1. 查找指定结点时,和插入结点的逻辑是类似的,新插入结点的值比当前结点大则往右走,反之往左走,找到后返回即可 。
  2. 那如果是冗余版本的二叉搜索树呢?意味着存在多个相等的值,而我们一般要查找中序遍历后的第一个相等的值(疑惑1),如何解决呢?(疑惑2)

疑惑1:找到第一个相等的值,可以方便我们知道有多少个相等的值,也能从头到尾打印。迭代器也可以确认其相等的值的范围等等。

疑惑2:

非冗余版本 

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;
		}
		else
		{
			return true;
		}
	}
	return false;
}

中序遍历

这样设计的目的是让我们调用InOrder的时候不需要传_root根节点(根本原因是在类外不能访问private私有的_root,只能在类里访问)

void InOrder()
{
	_InOrder(_root);
	cout << endl;
}

void _InOrder(Node* root)
{
	if (root == nullptr)
	{
		return;
	}

	_InOrder(root->_left);
	cout << root->_key << " ";
	_InOrder(root->_right);
}

删除

在二叉搜索树中删除的逻辑最为致命,它分为多种情况,需要考虑很多细节问题。

  1. 删除结点无左右孩子
  2. 删除结点有右孩子
  3. 删除结点有左孩子
  4. 删除结点有左右孩子

在情况1当中,把该结点的⽗亲对应孩⼦指针指向空,直接删除该结点,并不需要考虑删除结点是在左在右的情况。(情况1其实可以归类为情况2或情况3之中,本章节归类为情况2)

在情况2中:把N结点的⽗亲对应孩⼦指针指向N的右孩⼦,直接删除N结点

需要额外判断parent是左指针还是右指针指向cur的右孩子。

 

在情况3中: 把N结点的⽗亲对应孩⼦指针指向N的左孩⼦,直接删除N结点,依旧处理parent指向的问题。

在情况4中: 不能直接删除该结点且parent会不道指向cur的左还是右,这种情况需要找另一个结点来替代。为了保持二叉搜索树的性质,因此替代的结点必须是当前结点左子树的最大结点或者右子树的最小结点,交换值后,释放替代的结点资源。(本章节以找右子树最小结点为例)

这里埋下了一个坑,如果删除的是根结点,不能让parentreplace开始置为nullptr,否则会出现nullptr解引用的问题。

bool Erase(const K& key)
{
	Node* parent = nullptr;
	Node* 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->_left == cur)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
				}
				delete cur;
			}
			//右节点为空
			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;
					}
				}
				delete cur;
			}
			//有双亲节点
			else
			{
				Node* parentreplace = cur;
				Node* replace = cur->_right;

				while (replace->_left)
				{
					parentreplace = replace;
					replace = replace->_left;
				}

				swap(replace->_key, cur->_key);

				if (parentreplace->_left == replace)
				{
					parentreplace->_left = replace->_right;
				}
				else
				{
					parentreplace->_right = replace->_right;
				}

				delete replace;
			}
			return true;
		}
	}
	return false;
}

key/value

使用场景

在现实生活中会遇到很多key/value场景。

如:1.简单中英互译字典,树的结构中(结点)存储key(英⽂)和vlaue(中⽂),搜索时输⼊英⽂,则同时 查找到了英⽂对应的中⽂。

2.统计⼀篇⽂章中单词出现的次数,读取⼀个单词,查找单词是否存在,不存在这个说明第⼀次
出现,(单词,1),单词存在,则++单词对应的次数。

代码实现

其实key/value的实现和key时类似的,需要注意的是key/value场景下的value是可以冗余的,因为是以key为主体进行的。(注意:std库中key和value放在了pair的结构体当中,是为了达到返回多个值的目的)

	template<class K,class V>
	struct BSTreeNode
	{
		K _key;
		V _value;
		BSTreeNode<K,V>* _left;
		BSTreeNode<K,V>* _right;

		BSTreeNode(const K& key,const V& value)
			:_key(key)
			,_value(value)
			, _left(nullptr)
			, _right(nullptr)
		{}
	};

	template<class K,class V>
	class BSTree
	{
		typedef BSTreeNode<K,V> Node;
	public:
		bool Insert(const K& key,const V& value)
		{
			//树为空的情况
			if (_root == nullptr)
			{
				_root = new Node(key,value);
				return true;
			}

			Node* parent = nullptr;
			Node* cur = _root;

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

			cur = new Node(key,value);
			if (parent->_key > key)
			{
				parent->_left = cur;
			}
			else
			{
				parent->_right = cur;
			}

			return true;
		}

		BSTreeNode<K,V>* Find(const K& key)
		{
			Node* cur = _root;

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

		bool Erase(const K& key)
		{
			Node* parent = nullptr;
			Node* 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->_left == cur)
							{
								parent->_left = cur->_right;
							}
							else
							{
								parent->_right = cur->_right;
							}
						}
						delete cur;
					}
					//右节点为空
					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;
							}
						}
						delete cur;
					}
					//有双亲节点
					else
					{
						Node* parentreplace = cur;
						Node* replace = cur->_right;

						while (replace->_left)
						{
							parentreplace = replace;
							replace = replace->_left;
						}

						swap(replace->_key, cur->_key);
						swap(replace->_value, cur->_value);

						if (parentreplace->_left == replace)
						{
							parentreplace->_left = replace->_right;
						}
						else
						{
							parentreplace->_right = replace->_right;
						}

						delete replace;
					}
					return true;
				}
			}
			return false;
		}

		//析构函数
		~BSTree()
		{
			Destroy(_root);
			_root = nullptr;
		}

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

	private:
		Node* _root = nullptr;
	};

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

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

相关文章

【报告】内镜视频图像分析Foundation Model

来源&#xff1a;医疗基础模型 仅供个人学习&#xff0c;侵权请联系我删除

使用HTML5和CSS3实现炫酷的3D立方体动画

使用HTML5和CSS3实现炫酷的3D立方体动画 项目介绍 本文将详细介绍如何使用HTML5和CSS3技术实现一个交互式3D立方体动画。这个项目不仅展示了现代Web前端技术的强大功能&#xff0c;还能帮助读者深入理解CSS3的3D变换和动画特性。 技术栈 HTML5CSS3 (transform-style, persp…

MySQL数据库和表的操作之SQL语句

&#x1f3af; 本文专栏&#xff1a;MySQL深入浅出 &#x1f680; 作者主页&#xff1a;小度爱学习 MySQL数据库和表的操作 关系型数据库&#xff0c;都是遵循SQL语法进行数据查询和管理的。 SQL语句 什么是sql SQL&#xff1a;结构化查询语言(Structured Query Language)&…

多模态大语言模型arxiv论文略读(二)

Identifying the Correlation Between Language Distance and Cross-Lingual Transfer in a Multilingual Representation Space ➡️ 论文标题&#xff1a;Identifying the Correlation Between Language Distance and Cross-Lingual Transfer in a Multilingual Representat…

全局曝光与卷帘曝光

文章目录 曝光方式优点缺点应用场景 为何全局曝光帧率比卷帘曝光方式低 卷帘曝光和全局曝光是CMOS传感器两种常见的曝光模式&#xff0c;以下是二者的对比&#xff1a; 参考&#xff1a;B站优致谱视觉 曝光方式 卷帘曝光&#xff1a;传感器的每一行像素按顺序逐行扫描曝光&…

【一起来学kubernetes】31、Helm使用详解

一、Helm 简介 Helm 是 Kubernetes 的包管理工具&#xff0c;类比 Linux 中的 yum 或 apt&#xff0c;用于简化应用的打包、部署和版本管理。其核心功能包括&#xff1a; Chart 管理&#xff1a;将 Kubernetes 资源&#xff08;Deployment、Service 等&#xff09;打包为可复…

python 常用的6个爬虫第三方库

Python中有非常多用于网络数据采集的库&#xff0c;功能非常强大&#xff0c;有的用于抓取网页&#xff0c;有的用于解析网页&#xff0c;这里介绍6个最常用的库。 1. BeautifulSoup BeautifulSoup是最常用的Python网页解析库之一&#xff0c;可将 HTML 和 XML 文档解析为树形…

blender场景导入Unity的流程(个人总结)

处理找不到贴图的问题 blender场景导入Unity遇到的主要问题是贴图找不到。经研究是blender里材质的着色器结构不是贴图-原理化BSDF-输出导致的。目前还没有自动解决方法&#xff0c;总结了一个效率还可以的手动解决流程。 打开后到材质预览&#xff0c;看一下显示没问题&…

可编辑36页PPT | “新基建”在数字化智慧高速公路中的支撑应用方案智慧高速解决方案智慧交通方案

这份文档是一份关于“新基建”在数字化智慧高速公路中支撑应用方案的PPT内容介绍&#xff0c;它详细阐述了新基建在智慧高速建设中的背景、总体要求和建设内容。从政策背景来看&#xff0c;多个政府部门发布了相关政策文件&#xff0c;推动交通运输基础设施的数字化升级和智慧交…

一文解读DeepSeek在保险业的应用

引言 随着人工智能技术的深度渗透&#xff0c;保险行业正经历从传统经验驱动向数据智能驱动的转型。作为国产高性能开源大模型的代表&#xff0c;DeepSeek 凭借其低成本、高推理效率及跨模态处理能力&#xff0c;已成为保险机构突破服务瓶颈、重构业务逻辑的核心工具。截止目前…

物联网时代,HMI 设计的创新机遇与挑战

随着物联网&#xff08;IoT&#xff09;技术的蓬勃发展&#xff0c;各种智能设备如雨后春笋般涌现&#xff0c;从智能家居到智慧城市&#xff0c;物联网的应用场景愈发广泛。作为人与设备之间的桥梁&#xff0c;人机界面&#xff08;HMI&#xff09;设计在物联网时代扮演着至关…

【区块链安全 | 第二十四篇】单位和全局可用变量(二)

文章目录 单位和全局可用变量&#xff08;Units and Globally Available Variables&#xff09;特殊变量和函数1. 区块和交易属性2. ABI 编码和解码函数3. bytes 成员函数4. string 成员函数5. 错误处理6. 数学和加密函数7. 地址类型成员函数8. 与合约相关9. 类型信息 单位和全…

C语言:指针数组、函数、二级指针

1.指针数组 指针数组是一个数组&#xff0c;数组中的每个元素都是指针。这些指针可以指向各种类型的数据&#xff0c;如整数、字符、结构体等&#xff0c;甚至可以指向其他数组或函数。 指针数组的声明格式通常为&#xff1a; 数据类型 *数组名[数组大小];其中&#xff0c;数…

批量修改记事本文本文件编码,可以解决文本文件乱码问题

对于文本文件来说&#xff0c;通常都可以设置不同的编码格式&#xff0c;每一种不同的编码格式支持的字符都可能是不一样的。因此当编码格式出现错误的时候&#xff0c;文本文件可能会出现乱码的问题。如何将文本文件的编码由一种格式变为另外一种格式呢&#xff1f;如果文件出…

亚马逊云科技提供完全托管的DeepSeek-R1模型

近日&#xff0c;亚马逊云科技宣布在Amazon Bedrock上线完全托管的DeepSeek-R1模型。DeepSeek是首个登陆Amazon Bedrock的国产大模型&#xff0c;自今年1月底推出以来&#xff0c;已有数千客户使用Amazon Bedrock的自定义模型导入功能部署了DeepSeek-R1模型。 DeepSeek在过去几…

利用 Chrome devTools Source Override 实现JS逆向破解案例

之前讲解 Chrome 一大强势技术 override 时&#xff0c;给的案例貌似没有给大家留下多深的印象 浏览器本地替换&#xff08;local overrides&#xff09;快速定位前端样式问题的案例详解&#xff08;也是hook js的手段&#xff09;_浏览器的 overrides 替换功能-CSDN博客 其实…

容器C++ ——STL常用容器

string容器 string构造函数 #include<iostream> using namespace std; #include<string.h> void test01() {string s1;//默认构造const char* str "hello world";string s2(str);//传入char*cout << "s2" << s2 << endl;s…

npu踩坑记录

之前使用qwen系列模型在ascend 910a卡进行了一些生成任务, 贴出踩坑过程也许对遇到类似问题的同学有帮助: ) 目录 千问 qwq32环境配置 代码部署 生成内容清洗 已生成内容清洗 生成过程优化 Failed to initialize the HCCP process问题 assistant 的历史回答丢失 推理执…

Linux信号——信号的产生(1)

注&#xff1a;信号vs信号量&#xff1a;两者没有任何关系&#xff01; 信号是什么&#xff1f; Linux系统提供的&#xff0c;让用户&#xff08;进程&#xff09;给其他进程发送异步信息的一种方式。 进程看待信号的方式&#xff1a; 1.信号在没有发生的时候&#xff0c;进…

【机器学习】——机器学习思考总结

摘要 这篇文章深入探讨了机器学习中的数据相关问题&#xff0c;重点分析了神经网络&#xff08;DNN&#xff09;的学习机制&#xff0c;包括层级特征提取、非线性激活函数、反向传播和梯度下降等关键机制。同时&#xff0c;文章还讨论了数据集大小的标准、机器学习训练数据量的…