【数据结构】-----二叉搜索树(C++)

news2024/9/23 11:18:30

目录

前言

一、是什么

​编辑

二、实现 

Ⅰ、结点类

Ⅱ、结构及基本接口实现

​编辑

①插入

 ②查找

③删除(重难点) 

情况一:待删除结点为叶子结点(无孩子)

情况二:待删除结点存在一个孩子结点(可能左/右)

情况三:待删除结点存在两个孩子 

④中序遍历

⑤构造函数

⑥拷贝构造

⑦赋值重载

⑧析构函数

Ⅲ、完整实现代码

三、应用

1.K模型

2.KV 模型

3.K简单改造KV模型 

 四、性能分析


前言

前面浅谈了二叉树,但是单谈没啥太大意义,来看点不一样的。。二叉搜索

一、是什么

二叉搜索树也称二叉排序树,它具有以下性质:

  • 子树不为空,则左子树上所有结点的值都小于根结点的值
  • 子树不为空,则右子树上所有结点的值都大于根结点的值
  • 左右子树也分别为二叉搜索树
  • 可以是空树
  • 中序遍历的结果是有序的! 

形如:

二、实现 

Ⅰ、结点类

template<class K>
class BSTreeNode
{
public:
	BSTreeNode<K*> _left;
	BSTreeNode<K*> _right;
	K _key;

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

结点一般对外开放,所以设置为公有,当然你也可以直接用struct关键字!

Ⅱ、结构及基本接口实现

①插入

整体操作:

1.判断是否为空树,为空可直接插入!

2.不为空,找到合适的位置插入,按照二叉搜索树的性质,比较待插入结点和根结点的值,若 新结点<根结点,就插入左子树,反之插入右子树,循环即可!

细节:需要记录父亲结点的位置!

 

	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			//小于找左边
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			//大于找右边
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			//不允许数据冗余,相等不插入
			else
			{
				return false;
			}
		}
		cur = new Node(key);
		if (key < parent->_key)
		{
			//放左边
			parent->_left = cur;
		}
		else
		{
			//放右边
			parent->_right = cur;
		}
		return true;
	}

 ②查找

简单的遍历操作,大于找右边,小于找左边

	bool Find(const K& key)
	{
		if (_root == nullptr)
			return false;

		Node* cur = _root;
		while (cur)
		{
			if (key < cur->_key)
			{
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				cur = cur->_right;
			}
			//相等就找到
			else
				return true;
		}
		return false;
	}

③删除(重难点) 

首先需要找元素是否存在,不存在就直接返回即可。若存在,则需要分三种情况

情况一:待删除结点为叶子结点(无孩子)

这种情况可直接删除,具体方法:让父亲结点指向空即可,若删除结点在左,那父亲左指向空,反之,父亲右指向空。

例如:

情况二:待删除结点存在一个孩子结点(可能左/右)

这种情况的本质就是让待删除结点孩子顶替待删除结点位置即可,做法就是待删除结点的父亲指向待删除结点的孩子,在删除。

还需要注意一个特殊情节,如果这棵二叉搜索树如下:

综上代码具体实现如下:

//左边空,即存在右子树,父亲指向右
if (cur->_left == nullptr)
{
        //单分支
	    if (cur == _root){
		    _root = cur->_right;		
	    }
	    else{
			//待删除结点在左
			if (cur == parent->_left){
				parent->_left = cur->_right;//父亲左指向右
			}
			else
				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
				parent->_right = cur->_left;//父亲右指向右
		}
				delete cur;
}

实际上这种情况也包含了情况一,无孩子结点,即既没有左,也没有右

情况三:待删除结点存在两个孩子 

 这种情况采用替换法,就是用左子树的最大结点/或者右子树的最小结点去替换,交换后,在删除。

替换后:

代码实现如下:

以找右子树的最小值为例,右子树的最小值就是在右子树的最左边。

else
{
	Node* RightMinp = cur;
	Node* RightMin = cur->_right;
	//找右子树的最左边
	while (RightMin->_left)
	{
		RightMinp = RightMin;
		RightMin = RightMin->_left;
	}
	swap(RightMin->_key, cur->_key);//交换

	//左为空,存在右子树,父亲指向右
	if (RightMin = RightMinp->_left)
    {
		RightMinp->_left = RightMin->_right;
	}
	else
	{
		RightMinp->_right = RightMin->_right;
	}
	delete RightMin;
}

④中序遍历

这里为了对外不能访问私有的成员变量,所以采用以下写法:

public:
    void Inoder()
    {
	    _Inoder(_root);
	    cout << endl;
    }
private:
    Node *_root;//私有成员变量
	void _Inoder(Node* root)
	{
		if (root == nullptr)
			return;
		_Inoder(root->_left);
		cout << root->_key << " ";
		_Inoder(root->_right);
	}

⑤构造函数

BSTree()
    :_root(nullptr)
{}

⑥拷贝构造

和构造二叉树类似,先构造根结点,在分别构造左右子树!

	Node* Copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;
		Node* newroot = new Node(root->_key);//拷贝根
		newroot->_left = Copy(root->_left);//拷贝左
		newroot->_right = Copy(root->_right);//拷贝右
		return newroot;
	}


	BSTree(const BSTree<K>& t)
	{
		_root = Copy(t._root);
	}

⑦赋值重载

//现代写法,复用拷贝构造
BSTree<K>& operator=(BSTree<K> t)
{
	swap(_root, t._root);
	return *this;
}

⑧析构函数

这里要先分别清理左右,最后在清理根!

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

~BSTree()
{
	Destoy(_root);
	_root = nullptr;//要置空
}

Ⅲ、完整实现代码

template<class K>
class BSTreeNode
{
public:
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;

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

//二叉树结构
template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	BSTree()
		:_root(nullptr)
	{}
	BSTree(const BSTree<K>& t)
	{
		_root = Copy(t._root);
	}
	BSTree<K>& operator=(BSTree<K> t)
	{
		swap(_root, t._root);
		return *this;
	}
	~BSTree()
	{
		Destoy(_root);
		_root = nullptr;
	}

	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			//小于找左边
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			//大于找右边
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			//不允许数据冗余,相等不插入
			else
			{
				return false;
			}
		}
		cur = new Node(key);
		if (key < parent->_key)
		{
			//放左边
			parent->_left = cur;
		}
		else
		{
			//放右边
			parent->_right = cur;
		}
		return true;
	}
	bool Find(const K& key)
	{
		if (_root == nullptr)
			return false;

		Node* cur = _root;
		while (cur)
		{
			if (key < cur->_key)
			{
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				cur = cur->_right;
			}
			//相等就找到
			else
				return true;
		}
		return false;
	}
	bool Erase(const K& key)
	{
		if (_root == nullptr)
			return false;

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			//相等就找到
			else
			{
				//删除
				//左边空,即存在右子树,父亲指向右
				if (cur->_left == nullptr)
				{
					//单分支
					if (cur == _root)
					{
						_root = cur->_right;
						
					}
					else
					{
						//待删除结点在左
						if (cur == parent->_left)
						{
							parent->_left = cur->_right;
						}
						else
							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
							parent->_right = cur->_left;
					}
					delete cur;
				}
				//两边都不为空,即存在左右子树
				//以右边最小值替换
				else
				{
					Node* RightMinp = cur;
					Node* RightMin = cur->_right;
					//找右子树的最左边
					while (RightMin->_left)
					{
						RightMinp = RightMin;
						RightMin = RightMin->_left;
					}
					swap(RightMin->_key, cur->_key);//交换

					//左为空,存在右子树,父亲指向右
					if (RightMin == RightMinp->_left)
					{
						RightMinp->_left = RightMin->_right;
					}
					else
					{
						RightMinp->_right = RightMin->_right;
					}
					delete RightMin;
				}
				return true;
			}
		}
		return false;
	}
	void Inoder()
	{
		_Inoder(_root);
		cout << endl;
	}
private:
	Node* _root;
	void _Inoder(Node* root)
	{
		if (root == nullptr)
			return;
		_Inoder(root->_left);
		cout << root->_key << " ";
		_Inoder(root->_right);
	}
	Node* Copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;
		Node* newroot = new Node(root->_key);//拷贝根
		newroot->_left = Copy(root->_left);//拷贝左
		newroot->_right = Copy(root->_right);//拷贝右
		return newroot;
	}

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

三、应用

 两种模型

1.K模型

K模型:只有Key作为关键码,结构只需要存储Key即可,关键码即为需要搜索到的值。

如:检查单词是否拼写正确,具体实现方式

  • 将所有单词插入一棵二叉搜索树中,单词作为Key
  • 检查二叉搜索树中是否有这个单词存在,存在则正确,不在则错误!

上述实现的代码即为K模型!

2.KV 模型

 KV:每一个关键码Key,都有与之对应的值Value,即<Key,Value>键值对。平时最常见的模型

比如:英汉词典,每个英文单词都有对应的中文意思,即<word,Chinese>键值对

还有统计单词出现的次数,即<Key,count>键值对

3.K简单改造KV模型 

 实际上没有太大的改动

//节点类
template<class K,class V>
class BSTreeNode
{
public:
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;
	V _value;
	BSTreeNode(const K& key,V& value)
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
		,_value(value)
	{}
};

插入和查找功能的改变
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 (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			//大于找右边
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			//不允许数据冗余,相等不插入
			else
			{
				return false;
			}
		}
		cur = new Node(key,value);
		if (key < parent->_key)
		{
			//放左边
			parent->_left = cur;
		}
		else
		{
			//放右边
			parent->_right = cur;
		}
		return true;
	}
	Node* Find(const K& key)
	{
		if (_root == nullptr)
			return nullptr;

		Node* cur = _root;
		while (cur)
		{
			if (key < cur->_key)
			{
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				cur = cur->_right;
			}
			//相等就找到
			else
				return cur;
		}
		return nullptr;
	}
    ………………………………
}
int main()
{
    BSTree<string, string> dict;
	dict.Insert("insert", "插入");
	dict.Insert("erase", "删除");
	dict.Insert("left", "左边");
	dict.Insert("string", "字符串");
    return 0;
}

注意:比较只是和Key有关,和Value没有任何的关系!Key不能修改,Value可以,因为Key修改,二叉树就乱了!

 四、性能分析

 最优情况:二叉搜索树为完全二叉树(或者接近完全二叉树),平均比较次数为高度次,即log_2N

 

最坏情况:接近有序插入可能会退化成单支树,性能就大大的降低了

上述问题能否解决?肯定能,ALV树和红黑树就可以解决,可以看作是二叉搜索树的优化。敬请关注!


好了,铁子们,今天的内容就分享到这里,如果对你有所帮助,欢迎三连!你的支持永远是我的动力!!

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

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

相关文章

【iOS】—— 事件传递链和响应者链总结

事件传递链和响应者链总结 1. 事件传递链&#xff1a;事件传递链&#xff1a;传递流程&#xff1a;总结第一响应者&#xff1a; 2. 响应者链响应者链传递流程总结响应者链流程 总结&#xff1a; 之前也学习过这个内容这次在复习的时候&#xff0c;就想着写一下总结&#xff1a;…

Linux部署python3.0版本——及基本操作

&#xff08;一&#xff09;部署环境 首先查看列表&#xff0c;找到python3.0的包 yum list installed|grep python 如果没有&#xff0c;是因为yum源的问题&#xff0c;可部署阿里云镜像然后下载epel包&#xff0c;这里的内容可参考前面的阿里云镜像部署 然后进行下载 yum…

TensorRT-LLM中的 Quantization GEMM(Ampere Mixed GEMM)的 CUTLASS 2.x 实现讲解

在LLM的推理和部署中&#xff0c;低精度量化对于性能的提升十分关键&#xff0c;本次分享将为大家介绍TRT-LLM中是如何基于CUTLASS 2.x来实现PerChannel/AWQ/SmoothQuant等量化方法在模型推理过程的计算。Slides来自BiliBili NVIDIA英伟达频道 上传的《TensorRT-LLM中的 Quanti…

最新CSS3伪类和伪元素详解

第4章 伪类和伪元素 4.1结构伪类 E:first-child{},第一个元素 样式&#xff1a; p:first-child {color: red; } <div><p>Lorem ipsum</p><p>Dolor sit amet.</p> </div> 4.1.1nth-*伪类 以计数为基础的&#xff0c;默认情况下&…

某赛通电子文档安全管理系统 CDGAuthoriseTempletService1 SQL注入漏洞复现(XVE-2024-19611)

0x01 产品简介 某赛通电子文档安全管理系统(简称:CDG)是一款电子文档安全加密软件,该系统利用驱动层透明加密技术,通过对电子文档的加密保护,防止内部员工泄密和外部人员非法窃取企业核心重要数据资产,对电子文档进行全生命周期防护,系统具有透明加密、主动加密、智能…

RPA在政务服务中的挑战与解决方案

随着数字化时代的到来&#xff0c;数字政务的建设已成必然趋势&#xff0c;RPA作为数字化转型的重要工具之一&#xff0c;能够帮助政府单位快速实现业务流程的自动化和智能化&#xff0c;提高工作效率和质量&#xff0c;为建设数字政务提供强有力的支持&#xff0c;因此正被越来…

深植根基、蓬勃向上 | openKylin 2.0正式发布!

2024年8月8日&#xff0c;openKylin 2.0版本正式发布&#xff01;该版本默认搭载Linux 6.6 LTS内核&#xff0c;完成180操作系统核心组件自主选型升级&#xff0c;深度融合AI技术&#xff0c;上线麒麟AI助手等实用AI功能&#xff0c;并为用户带来包括开明软件包格式、不可变系统…

Unity 在Editor下保存对Text组件的文本的修改

Unity 在Editor下保存对Text组件的文本的修改 /****************************************************文件&#xff1a;TimeStampForText.cs作者&#xff1a;lenovo邮箱: 日期&#xff1a;2024/8/8 1:9:21功能&#xff1a; *************************************************…

聚观早报 | 小米15 Ultra相机规格;一加Ace 5参数规格

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 8月8日消息 小米15 Ultra相机规格 一加Ace 5参数规格 iOS 18.1代码曝光 SK电讯加大AI投入 Figure 02 人形机器人…

【VScode】如何在anaconda虚拟环境中打开vscode项目

文章目录 【必备知识】打开anaconda虚拟环境切换到项目工作目录激活anaconda虚拟路径让vscode从当前目录打开 【必备知识】 anaconda环境变量配置及配置python虚拟环境 https://blog.csdn.net/xzzteach/article/details/140621596 打开anaconda虚拟环境 切换到项目工作目录 …

Ftrans文件摆渡方案:重塑文件传输与管控的科技先锋

一、哪些行业会用到文件摆渡相关方案 文件摆渡相关的产品和方案通常用于需要在不同的网络、安全域、网段之间传输数据的场景&#xff0c;主要是一些有核心数据需要保护的行业&#xff0c;做了网络隔离和划分。以下是一些应用比较普遍的行业&#xff1a; 金融行业&#xff1a;…

第 11 课:多方安全计算在安全核对的行业实践

业务背景&#xff1a;安全核对产生的土壤 产品方案&#xff1a;从试点到规模化的路 技术共建&#xff1a;与隐语的共同成长

three.js 空间坐标绘制多边形围栏(结合react)

空间坐标点绘制多边形&#xff0c;实际上可以理解为是由 “点” 到 “线” 到 “面” 的一个过程。将空间坐标点通过THREE.Shape绘制多条线并闭合而得到一个封闭的二维形状平面对象&#xff0c;使用THREE.ShapeGeometry将Shape对象转换为Geometry对象添加Mesh&#xff0c;最终得…

全国多地公布2024下半年软考报名具体时间

下半年开考科目&#xff1a; 高级资格&#xff1a;系统分析师、系统架构设计师、网络规划设计师、系统规划与管理师 中级资格&#xff1a;软件设计师、网络工程师、信息安全工程师、信息系统监理师、多媒体应用设计师、系统集成项目管理工程师 初级资格&#xff1a;网络管理…

【时时三省】(C语言基础)操作符2

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ——csdn时时三省 赋值操作符 它可以重新赋值 示例: 使用赋值操作符赋值 复合赋值符 &#xff0b;&#xff1d; -&#xff1d; &#xff0a;&#xff1d; /&#xff1d; &#xff05;&#xff1d; &g…

Linux磁盘管理_LVM逻辑卷_SWAP交换分区_Centos-LVM格式磁盘扩容

目录 一、基本磁盘管理1.1 创建分区1.2 创建文件系统1.3 挂载mount1.4 查看挂载信息1.5 重启失效解决方式 二、逻辑卷LVM2.1 LVM2.2 创建LVM2.3 扩大卷组VG2.4 命令汇总 三、交换分区SWAP管理3.1 SWAP3.2 查看swap3.3 增加交换分区 四、Centos调整分区&#xff0c;在线调整分区…

05 数据类型

目录 分类数值类型小数类型字符串类型日期和时间类型集合类型 1. 分类 2. 数值类型 tinyint create table t1 (num tinyint); insert into t1 values (1); insert into t1 values (128); – 越界插入&#xff0c;报错 select * from t1; 说明: 在mysql中&#xff0c;整形可以指…

LeetCode面试150——14最长公共前缀

题目难度&#xff1a;简单 默认优化目标&#xff1a;最小化平均时间复杂度。 Python默认为Python3。 目录 1 题目描述 2 题目解析 3 算法原理及代码实现 3.1 横向扫描 3.2 纵向扫描 3.3 分治 3.4 二分查找 参考文献 1 题目描述 编写一个函数来查找字符串数组中的最长…

MyBatis 基本操作 - 注解版

目录 一&#xff0c;查询 - select 1.1 全列查询 1.2 指定列查询 1.3 赋值问题 方法一&#xff1a;起别名 方法二&#xff1a;结果映射 方法三&#xff1a;添加配置 二&#xff0c;新增 - Insert 2.1 使用对象插入 2.2 获取主键 三&#xff0c;删除 - Delete 四&am…

使用Gitlab实现monorepo多项目CICD

CI/CD是什么 CI/CD&#xff08;Continuous Intergration/Continuous Delpoy&#xff09;&#xff0c;即持续集成/持续部署&#xff0c;或称为持续集成/持续交付&#xff0c;作为一套面向开发和运维团队的解决方案&#xff0c;CI/CD 主要解决集成新代码和向用户频繁交付应用的问…