二叉搜索树相关

news2024/11/25 4:53:56

二叉搜索树

  • 定义:
  • 对二叉搜索树的一些操作
    • 基本结构
    • Insert操作
    • Find操作
    • Erase操作
  • InOrder遍历二叉树操作
  • 模拟字典
  • 模拟统计次数

定义:

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

对二叉搜索树的一些操作

基本结构

首先,我们先定义出一些结构
第一个结构是树里面的内容,包含左指针右指针,以及key和value。
接下来的一个类,用来表示这是一个二叉搜索树

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

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

template<class K, class V>
class BSTree
{
	typedef BSTreeNode<K, V> Node;
public:
		Node* _root = nullptr;
};

Insert操作

Insert的思路是通过key去查找,看在树里面有没有这个节点,如果有就插入失败(因为二叉搜索树中不允许有重复的值出现,否则就失去了意义)。 如果没有那就去找一个合适的位置插入。
找合适的位置:①如果根为空,直接创建新节点当根节点插入 ②根不为空,就去子树上找。 这里我们要注意为了方便控制我们的循环条件,我们大循环是以cur为循环条件去找的,所以我们需要有parent来记录cur的父亲位置,这样后面才方便插入。(为什么不直接改循环条件? 因为这里判断不仅仅是一个节点,它既需要判断左节点又要判断右节点,所以我们选择以parent的形式去记录)

bool Insert(const K& key, const V& value)
{
	// 1.根为空,直接插入一个新节点
	if (_root == nullptr)
	{
		_root = new Node(key, value);
		return true;
	}
	// 2.根不为空,则去找到合适位置插入
	Node* parent = _root;
	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已经是空指针了,我们应该需要的是cur的parent位置,所以上面需要有parent来记录
	cur = new Node(key, value);   // 直接重新赋值cur就可以了,不用再加一个新的变量进来
	if (key > parent->_key)
		parent->_right = cur;
	if (key < parent->_key)
		parent->_left = cur;
	return true;
}

Find操作

Find操作非常简单,找根,左孩子右孩子,如果遍历完了依然没有找到,那就返回空指针。

	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)
				cur = cur->_left;
			else if (cur->_key < key)
				cur = cur->_right;
			else return cur;
		}
		return nullptr;
	}

Erase操作

Erase操作相对就会比较的麻烦了。
Erase的思路:首先,我们需要去判断这个节点是否存在,不存在返回false。存在才会有下面的删除操作(不存在的情况放在最外面了)。
删除节点分为三个情况:①删除的节点只有左孩子
②删除节点只有右孩子(没有孩子包含在这两种情况)
③既有左孩子又有右孩子
那么针对三种情况,我们有不同的要求,建议通过图形来进行理解:
①、②种是类似的。在这里插入图片描述
我们进行判断又分三种情况,第一种:我们要删除的节点就是根节点,那么就直接对根进行处理(我们说了删除节点无左右孩子也包含在①、②的情况里,这里就体现出来了,这里也可以处理)。 第二种:我们要删除的节点cur是parent的左孩子 第三种: 我们要删除的节点cur是parent的右孩子。 第二种和第三种情况是不一样的,这里结合图片就可以看出来。

难处理的是第③种情况
如果说我们删除的节点既有左孩子又有右孩子,那么在处理的时候需要用到交换法。 交换的对象是cur和cur左孩子中的最大值或右孩子中的最小值。
所以第一步是找到最大的左孩子或最小的右孩子(这里我们以找到最小右孩子为例)
找到之后,还要分情况讨论: 一种是这个最小的右孩子有左节点也有右节点的情况
还一种是这个最小的右孩子只有右节点的情况(注意:这里不可能出现这个节点还有左节点的情况,因为在此之前我们找到的这个RightMin是右子树的最小节点,就不可能出现它还有比他小的左节点的情况)
第二种可以看到,我们的RightMin还有一个右节点的情况,这个情况就得特殊处理了,如果RightMinParent->left = RightMin。我们RightMinParent的左节点就得指向RightMin的右结点(同样,RightMin不会有左节点)
这里我们怎么样更好的理解记忆呢?
我们可以看第一种情况是RightMinParent和RightMin二者成一条线。 第二种情况是不成一条线,RightMin为RightMinParent左孩子的情况。
在这里插入图片描述

bool Erase(const K& key)
{
	// 删除分为3个情况
	// 1.删除节点只有左孩子 2.删除节点只有右孩子(没有孩子包含在这两种情况)  3.既有左孩子又有右孩子
	Node* parent = _root;
	Node* cur = _root;
	while (cur)
	{
	    // 先找节点,找到了才有后面的删除操作,否则要返回false
		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(cur == parent->_left)   // 这个节点为parent的左孩子的情况
				{
					parent->_left = cur->_right;
				}
				else if (cur == parent->_right) // 这个节点为parent的右孩子的情况
				{
					parent->_right = cur->_right;
				}
				delete cur;
			}
			else if (cur->_right == nullptr)  // 无右孩子
			{
				if (cur == _root)
					_root = cur->_left;  // 直接让左孩子作为根就可以了
				else if (cur == parent->_left)
				{
					parent->_left = cur->_left;
				}
				else if (cur == parent->_right)
				{
					parent->_right = cur->_left;
				}
				delete cur;
			}
			else  // 此时为左右孩子都有的情况
			{
				Node* RightMinParent = cur;
				Node* RightMin = cur->_right;
				while (RightMin->_left)
				{
					RightMinParent = RightMin;
					RightMin = RightMin->_left;
				}

				swap(RightMin->_key, cur->_key);
				swap(RightMin->_value, cur->_value);
				
				if (RightMinParent->_right == RightMin)   // 特殊情况得特殊处理
					RightMinParent->_right = RightMin->_right;
				else
					RightMinParent->_left= RightMin->_right;  //此时,RightMin
				//只可能有右孩子,因为它是最小的右子树节点(不能有比它更小的了)
				delete RightMin;
			}
		}
		return true;
	}
	return false;
}

InOrder遍历二叉树操作

这里我们需要注意两个点: 第一个是如何遍历,也就是我们调用采用递归的方法。
第二个是为什么我们这里要封装一层,而不是直接用,直接传值root并接收。 因为root是一个私有变量,我们在类外是无法访问到的,此时我们要执行遍历操作又必须得从根开始。想访问到,就需要通过函数进入到内部,才能访问到这个私有变量。

	void InOrder()  // 为什么这里要封装一层而不是直接传入root,因为root是私有成员,
	//我们在外面传参时是访问不到这个私有成员变量的。我们只有通过函数进入到类内,
	//才能访问到root,并传参给包了一层的InOrder函数
	{
		_InOrder(_root);
		cout << endl;
	}
private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_key << ":" << root->_value << endl;
		_InOrder(root->_right);
	}

模拟字典

下面的代码可以用来模拟字典操作

BSTree<string, string> dict;
dict.Insert("insert", "插入");
dict.Insert("erase", "删除");
dict.Insert("left", "左边");
dict.Insert("string", "字符串");
dict.Erase("insert");
dict.Erase("string");
string str;
while (cin >> str)
{
	auto ret = dict.Find(str);
	if (ret)
	{
		cout << str << ":" << ret->_value << endl;
	}
	else
	{
		cout << "单词拼写错误" << endl;
	}
}

模拟统计次数

下面的这个代码,可以用来统计次数

	string strs[] = { "苹果", "西瓜", "苹果", "樱桃", "苹果", "樱桃", "苹果", "樱桃", "苹果" };
	// 统计水果出现的次
	BSTree<string, int> countTree;
	for (auto str : strs)
	{
		auto ret = countTree.Find(str);
		if (ret == NULL)
		{
			countTree.Insert(str, 1);   // 不存在就插入
		}
		else
		{
			ret->_value++;  //存在就让value++
		}
	}
	countTree.InOrder();

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

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

相关文章

《Decoupled Contrastive Learning for Long-Tailed Recognition》阅读笔记

论文标题 《Decoupled Contrastive Learning for Long-Tailed Recognition》 针对长尾识别的解耦对比学习 作者 Shiyu Xuan 和 Shiliang Zhang 来自北京大学计算机学院多媒体信息处理国家重点实验室 初读 摘要 监督对比损失&#xff08;Supervised Contrastive Loss, SC…

LeetCode //C - 85. Maximal Rectangle

85. Maximal Rectangle Given a rows x cols binary matrix filled with 0’s and 1’s, find the largest rectangle containing only 1’s and return its area. Example 1: Input: matrix [[“1”,“0”,“1”,“0”,“0”],[“1”,“0”,“1”,“1”,“1”],[“1”,“…

Netty HTTP 示例-响应式编程-013

🤗 ApiHug {Postman|Swagger|Api...} = 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱,有温度,有质量,有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin | Marketplace The Next Generation API Development Platform…

The provided password or token is incorrect or your account

IDEA使用git技巧 【/n】 01 问题出现场景 我的gitlab上个月生成的token到期了,于是今天推上去的时候报了这个错误 The provided password or token is incorrect or your account has 2FA enabled and you must use a personal access token instead of a password. See ht…

【知识碎片】2024_05_07

今天记录了两个代码和C的几个破碎知识。 第一段代码是基础型的&#xff0c;关于数组。第二段代码是二分的&#xff0c;一开始没通过全部案例&#xff0c;值得再看。 每日代码 1.记负均正 输入一个数组&#xff0c;输出负数的个数&#xff0c;整数的平均值&#xff08;0都不参…

AI伦理和安全风险管理终极指南

人工智能&#xff08;AI&#xff09;正在迅速改变各个领域的软件开发和部署。驱动这一转变的两个关键群体为人工智能开发者和人工智能集成商。开发人员处于创建基础人工智能技术的最前沿&#xff0c;包括生成式人工智能&#xff08;GenAI&#xff09;模型、自然语言处理&#x…

深入探索van Emde Boas树:原理、操作与C语言实现

van Emde Boas (vEB) 树是一种高效的数据结构&#xff0c;用于处理整数集合。它是由荷兰计算机科学家Jan van Emde Boas在1977年提出的。vEB树在处理整数集合的查找、插入、删除和迭代操作时&#xff0c;能够以接近最优的时间复杂度运行。vEB树特别适合于那些元素数量在某个较小…

Jenkins +配置邮件 centos8.5 安装部署 运维系列一

1 jenkins的war包下载地址: Download and deploy 2 xftp 等方式上传到服务器 #安装jdk tar zxvf jdk-11.0.8_linux-x64_bin.tar.gz mv jdk-11.0.8/ /usr/local/jdk vim /etc/profile export JAVA_HOME/usr/local/jdk export PATH$JAVA_HOME/bin:$PATH CLASSPATH.:$JAVA_…

WPF基础学习笔记

目录 基础知识&#xff1a; WPF的特点: WPF的优点 什么是XAML&#xff1f; 布局基础&#xff1a; 样式的应用&#xff1a; 控件模板&#xff08;ControlTemplate&#xff09;&#xff1a; 数据模板&#xff08;DataTemplate&#xff09;&#xff1a; 静态资源StaticRe…

智慧工地,筑牢安全防线:严防塔吊相撞,守护施工安全之巅!

塔吊相撞的事故是一个严重的施工安全问题&#xff0c;而智慧工地则是一种利用现代科技手段提高施工安全性的解决方案。 为了避免类似事故的发生&#xff0c;智慧工地可以采取以下措施&#xff1a; 一、建立全面的监控系统 智慧工地可以建立完善的监控系统&#xff0c;通过安装…

Github 2024-05-08 C开源项目日报 Top8

根据Github Trendings的统计,今日(2024-05-08统计)共有8个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量C项目8PHP项目1Python项目1C++项目1PHP:流行的Web开发脚本语言 创建周期:4710 天开发语言:C, PHP协议类型:OtherStar数量:37340 个Fork数量…

Layer创建流程

在SurfaceFlinger中&#xff0c;Layer表示一个显示图层&#xff0c;是surfaceflinger合成过程中最重要的基本单元&#xff0c;它提供了一系列属性定义了如何参与合成并与其他Layer交互&#xff0c;包括&#xff1a; 位置&#xff1a;定义Layer出现在屏幕上的位置&#xff0c;包…

营销H5测试综述

H5页面是营销域最常见的一种运营形式&#xff0c;业务通过H5来提供服务&#xff0c;可以满足用户对于便捷、高效和低成本的需求。H5页面是业务直面用户的端点&#xff0c;其质量保证工作显得尤为重要。各业务的功能实现具有通用性&#xff0c;相应也有共性的测试方法&#xff0…

蓝桥杯13届JAVA A组 国赛

​​​​​​​ package 蓝桥杯国赛; // 贪心选个数最少的进行摆 // 2:1 ,3:1, 4:1,5 : 3,6:3,7:1 // 选 1&#xff0c;7&#xff0c;4&#xff0c;2&#xff0c;3&#xff0c;5&#xff0c;9 // 然后都选满10个 public class 火彩棒数字 {public static void main(String[] a…

人工智能-2024期中考试

前言 人工智能期中考试&#xff0c;认真准备了但是没考好&#xff0c;结果中游偏下水平。 第4题没拿分 &#xff08;遗传算法&#xff1a;知识点在课堂上一笔带过没有细讲&#xff0c;轮盘赌算法在书本上没有提到&#xff0c;考试的时候也没讲清楚&#xff0c;只能靠猜&…

主数据准确性和完整性竟如此重要?确保这两大特性做好这些就够了

主数据是企业运营的心脏&#xff0c;它包含了客户、产品、供应商和员工等关键业务实体的详细信息。这些数据的准确性、一致性和完整性对于确保企业决策的质量、优化业务流程、提高客户满意度、推动数据驱动的创新、遵守法规要求以及维护数据安全至关重要。 主数据的准确性指的…

亚信科技精彩亮相2024中国移动算力网络大会,数智创新共筑“新质生产力”

4月28至29日&#xff0c;江苏省人民政府指导、中国移动通信集团有限公司主办的2024中国移动算力网络大会在苏州举办。大会以“算力网络点亮AI时代”为主题&#xff0c;旨在凝聚生态伙伴合力&#xff0c;共同探索算力网络、云计算等数智能力空间&#xff0c;共促我国算网产业和数…

目标检测实战(八): 使用YOLOv7完成对图像的目标检测任务(从数据准备到训练测试部署的完整流程)

文章目录 一、目标检测介绍二、YOLOv7介绍三、源码/论文获取四、环境搭建4.1 环境检测 五、数据集准备六、 模型训练七、模型验证八、模型测试九、错误总结9.1 错误1-numpy jas mp attribute int9.2 错误2-测试代码未能跑出检测框9.3 错误3- Command git tag returned non-zero…

利用Jenkins完成Android项目打包

问题和思路 目前存在的问题 打包操作由开发人员完成&#xff0c;这样开发进度容易被打断。 解决问题的思路 将打包操作交测试/产品/开发人员来完成&#xff0c;主要是测试/开发。 按照以上的思路&#xff0c;那么JenkinsGradle的解决方案是比较经济的&#xff0c;实现起来…

[Kotlin]创建一个私有包并使用

1.创建Kotlin项目 创建项目&#xff1a; 在Android Studio或其他IDE中选择“Create New Project”。选择Kotlin和Gradle作为项目类型和构建系统。指定项目名称和位置&#xff0c;完成设置。 添加依赖: 如果你的库需要额外的依赖&#xff0c;可以在 build.gradle (Module: app…