数据结构: 二叉搜索树

news2025/1/23 6:14:43

目录

1.二叉搜索树概念

2.二叉搜索树的操作

3.二叉搜索树的实现

3.1定义BST

3.2功能实现

1.默认成员函数

2.非递归

插入

查找

删除

3.递归

插入

查找

删除

4.二叉搜索树的应用


1.二叉搜索树概念

二叉搜索树又称二叉排序树,它可以是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

2.二叉搜索树的操作

1.查找

从根开始查找:

--若查找的值比根节点的值大, 向右查找. 

--若比根节点的值小向左查找.

--循环下去,若到nullptr都没找到,返回false

2.插入

a.空树

1.直接将其设为根节点

b.非空树

1.根据二叉搜索树的性质,找到插入的位置

2.在该位置上新生成一个节点,并与父节点链接

(可能是左,也可能是右) 根据这个节点的key与parent的key值判断在那边链接

3.删除

1.找到要删除的节点

2.对该节点进行讨论:

a.该节点没有节点或只有1子个节点

--可以直接删除, 并将它的子节点给它的父点

b.该节点有2个子节点

--找到它左子树最大节点/右子树最小节点来代替它, 并将其删除

3.细节处理

当删除到root->left/right为空的时候, 需要更新头节点

3.二叉搜索树的实现

3.1定义BST

节点

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

	//构造函数
	BSTreeNode(const K& k) 
		:_left(nullptr)
		,_right(nullptr)
		,_key(k)
	{}
};

BSTree

template<class K, class V>
class BSTree
{
	typedef BSTreeNode<K, V> Node;
public:

    //功能
	bool Insert(const K& key, const V& value);
	Node* Find(const K& key);
	bool Erase(const K& key);
	void _InOrder(Node* root);
	void InOrder();

private:
	Node* _root = nullptr;
};

3.2功能实现

1.默认成员函数

构造函数

	//构造函数
	BSTree()
		:_root(nullptr)
	{}

拷贝构造

	//拷贝构造
	BSTree(const BSTree<K>& t) 
	{
		Copy(t._root);
	}
	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<K>& operator=(BSTree<K> t)
	{
		swap(_root,t._root);
		return *this;
	}

析构函数

	//析构函数
	~BSTree() 
	{
		Destory(_root);
	}

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

2.非递归

插入

a.空树

--插入的节点直接变为根节点

b.非空树

--根据二叉搜索树性质找到插入的位置

--与父节点链接(讨论parent的key与插入节点的key值来决定链接在parent的那边)

	bool Insert(const K& key)
	{
		//a.空树(直接将该节点设为根节点)
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}

		//b.非空树
		//1.找到插入的位置
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		//2.链接节点
		cur = new Node(key);
		if (parent->_key > key)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		return true;
	}

查找

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

删除

找到删除节点的位置

1.删除的节点只有1个节点或者没有节点(直接删除,将cur的子节点给parent)

--没有节点也可以这么操作,相当于把空的子节点给父亲

--若有1个子节点,把该子节点给父亲 (讨论cur的位置,可能是p的左也可能是p的右)

2.删除的节点有2个节点:  找到它左子树最大的节点/ 右子树最小的节点代替它, 并删除

--找到删除的节点

--找到左子树最大节点/右子树最小节点来代替它(把cur存的key变为leftMax/rightMin的key)

--找该节点的过程, cur是在leftMax/rightMin的位置,删除cur节点

(当然要处理它的子节点,讨论一下leftMax是pleftMax左边还是右边)

3.处理空指针: 当删除的节点为cur, 并且cur只有左子树或者只有右子树,更新头节点

	bool Erase(const K& key) 
	{
		//找到要删除的节点
		Node* parent = nullptr;
		Node* cur = _root;

		while (cur) 
		{
			if (cur->_key > key) 
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else 
			{
				//删除
				//1.左为空
				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;
				}
				//2.右为空
				else if (cur->_right == nullptr) 
				{
					if (cur = _root) 
					{
						_root = cur->_left;
					}
					else 
					{
						if (parent->_left == cur) 
						{
							parent->_left = cur->_left;
						}
						else 
						{
							parent->_right = cur->_left;
						}
					}
				}
				//3.左右不为空(找左子树最大节点/右子树最小节点)
				else 
				{
					Node* pmaxLeft = cur;
					Node* maxLeft = cur->_left;

					while (maxLeft->_right) 
					{
						pmaxLeft = maxLeft;
						maxLeft = maxLeft->_right;
					}
					cur->_key = maxLeft->_key;
					
					//把maxLeft的子节点交给其parent,然后删除maxLeft
					if (pmaxLeft->_left == maxLeft) 
					{
						pmaxLeft->_left = maxLeft->_left;
					}
					else 
					{
						pmaxLeft->_right = maxLeft->_left;
					}
					delete maxLeft;
				}
				return true;
			}
		}
		return false;
	}

3.递归

插入

	//1.插入
	bool _InsertR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}

		if (root->_key > key)
		{
			return _InsertR(root->_left, key);
		}
		else if (root->_key < key)
		{
			return _InsertR(root->_right, key);
		}
	}

查找

根据二叉搜索树的性质查找:

	//2.查找
	bool _FindR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			return false;
		}

		if (root->_key == key)
			return true;

		if (root->_key > key)
			return _FindR(root->_left, key);
		else
			return _FindR(root->_right, key);

	}

删除

	//3.删除
	bool _EraseR(Node*& root, const K& key) 
	{
		if (root == nullptr)
			return false;

		//找到要删除的节点
		if (root->_key > key) 
		{
			return _EraseR(root->_left, key);
		}
		else if (root->_key < key) 
		{
			return _EraseR(root->_right, key);
		}
		else 
		{
			//删除
			Node* del = root;
			//1.左为空
			if (root->_left == nullptr) 
			{
				root = root->_right;
			}
			//2.右为空
			else if (root->_right == nullptr) 
			{
				root = root->_left;
			}
			//3.左右不为空
			else 
			{
				Node* minRight = root->_right;
				while (minRight->_left) 
				{
					minRight = minRight->_left;
				}
				swap(root->_key,minRight->_key );

				//转换为在它的右子树去删除
				return _EraseR(root->_right,key);
			}
			delete del;

			return true;
		}
	}

效果:

4.二叉搜索树的应用

1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:

  • 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方
式在现实生活中非常常见:

  • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
  • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对

示例

英汉词典

统计次数

中序遍历打印的时候,补上value就行:

达到上面的效果:把K改为KV型

节点:

BSTree

5.性能分析

最优: logN

最差: N

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

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

相关文章

Java连接PostGreSql

本次试验怎么用jdbc连接PostGreSql数据库。首先需要安装一个pgsql数据库&#xff0c;安装就不介绍了。安装后自己用SQL创建表&#xff0c;如有下图的库和表&#xff0c;怎么用java带SQL参数连接他取的数据。 PG库下载地址 首先到官网上根据自己的Java版本下载jdbc驱动jdbc下载…

HCIP静态路由综合实验

题目&#xff1a; 步骤&#xff1a; 第一步&#xff1a;搭建上图所示拓扑; 第二步&#xff1a;为路由器接口配置IP地址&#xff1b; R1&#xff1a; [R1]display current-configuration intinterface GigabitEthernet0/0/0ip address 192.168.1.1 255.255.255.252 interfa…

FL Studio 2023中文安装设置指南!四招教你玩转FL Studio21!

Fl Studio是一款极具时尚元素的音乐制作软件&#xff0c;对于粉丝群体来说简直是一大福利&#xff01;不仅可以充分发挥你的创造力&#xff0c;还能展现你的音乐才华。这里给你分享几个设置中文的技巧&#xff0c;让你的Fl Studio体验更上一层楼&#xff01; 编曲软件FL Studi…

警惕这款记录音频和电话的Android木马软件SpyNote

导语&#xff1a;近日&#xff0c;一款名为SpyNote的Android木马软件被揭示出其多样化的信息收集功能。该木马软件通常通过短信钓鱼攻击传播&#xff0c;攻击链通过欺骗潜在受害者点击嵌入链接来安装该应用程序。除了要求入侵性权限以访问通话记录、摄像头、短信和外部存储等&a…

vite react react-pdf pdfjs-dist 加载不全的解决方案 cmaps本地路径

pdf.js 字体无法显示 pdfjs-dist加载不全的解决方案 Rollup 配置 rollup-plugin-copy插件&#xff0c;进行打包构建时的文件复制 参考了网上诸多解决方案&#xff0c;都是webpack的引入包方式&#xff0c; 照猫画虎&#xff0c;把vite解决方案奉献给大家 vite.config.js impo…

小程序:uniapp解决主包体积过大的问题

已经分包但还是体积过大 运行时勾选“运行时是否压缩代码”进行压缩 在manifest.json配置&#xff08;开启分包优化&#xff09; "mp-weixin" : {"optimization" : {"subPackages" : true}//.... },在app.json配置&#xff08;设置组件按需注入…

【CANN训练营】UART、SPI、I2C串口通信介绍笔记

UART、SPI、I2C串口通信介绍 UART通信 串行通信和并行通信 数据通信&#xff1a;若干个数据设备之间的信息交换称为数据通信。 两种方式&#xff1a;并行通信和串行通信 并行通信&#xff1a;数据的各位同时传送&#xff0c;每一位数据都需要一条传输线并且需要若干条控制…

H5+Vue3编写官网,并打包发布到同一个域名下

背景 因为html5有利于搜索引擎抓取和收录我们网站更多的内容&#xff0c;对SEO很友好&#xff0c;可以为网站带来更多的流量,并且多端适配&#xff0c;兼容性和性能都非常不错&#xff0c;所以使用h5来编写官网首页。 因为用户个人中心可以通过官网跳转&#xff0c;不需要被浏…

jmeter压测

jmeter强大到很强大 hh~也要压测go的一些东西&#xff0c;这是三年前做的东西了&#xff0c;jmeter不支持grpc调用所以写了一个spring小服务中间层&#xff1a; 具体的jmeter开始了 这里设置线程相关 <h3>调用哪个服务的哪个方法</h3> <h3>BeanShell PrePro…

“脆皮大学生”上热搜,体质变差不能轻视

蕞近&#xff0c;“脆皮大学生”这一词条在网络上走红。“脆皮大学生”指现在新一代大学生&#xff0c;虽然年纪轻轻&#xff0c;但是身体毛病却极多&#xff0c;脆弱到了一碰就坏的地步&#xff0c;出现了“脆皮现象”。 仅2023年9月一个月&#xff0c;郑州一家医院急诊科接诊…

Spring5应用之高级注解开发

作者简介&#xff1a;☕️大家好&#xff0c;我是Aomsir&#xff0c;一个爱折腾的开发者&#xff01; 个人主页&#xff1a;Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客 当前专栏&#xff1a;Spring5应用专栏_Aomsir的博客-CSDN博客 文章目录 参考文献前言Conf…

JOSEF约瑟 10KV高压漏电保护继电器BLD-20 φ100mm 50-500mA 导轨安装

系列型号 BLD-20A高压漏电保护继电器 BLD-20高压漏电继电器 BLD-20高压漏电保护继电器 BLD-20X高压漏电保护装置 BLD-G20X高压漏电保护装置 BLD-20RG高压漏电保护继电器 用途 BLD-20漏电继电器(以下简称继电器)主用于交流电压1-10KV系统中,频率为50HZ,对供电系 统的漏…

【C语言】#define宏与函数的优劣对比

本篇文章目录 1. 预处理指令#define宏2. #define定义标识符或宏&#xff0c;要不要最后加上分号&#xff1f;3.宏的参数替换后产生的运算符优先级问题3.1 问题产生3.2 不太完美的解决办法3.3 完美的解决办法 4.#define的替换规则5. 有副作用的宏参数6. 宏与函数的优劣对比6.1 宏…

从0开始学Java:运算符(Operator)与标点符号(Separators)

文章目录 1. 运算符的分类2. 算术运算符3. 赋值运算符4. 关系运算符/比较运算符5. 逻辑运算符6. 条件运算符练习 7. 位运算符&#xff08;了解&#xff09;左移&#xff1a;<<右移&#xff1a;>>无符号右移&#xff1a;>>>按位与&#xff1a;&按位或&…

阿里云优惠券(代金券)免费领取方法及使用教程分享

阿里云优惠券是阿里云提供给用户的一种优惠凭证&#xff0c;通常包括代金券和折扣券&#xff0c;领取之后支付订单时可以抵扣或者打折&#xff0c;是阿里云的一种重要优惠方式。本文将为大家详细介绍阿里云优惠券的免费领取方法及使用教程&#xff0c;帮助大家在购买阿里云产品…

qt 读取txt文本内容时,中文乱码

项目场景&#xff1a; 项目中&#xff0c;需要在TF卡中做类似txt阅读器的功能&#xff0c;因为app是在嵌入式系统下运行的&#xff0c;发现当读取txt的文本格式为ANSI时&#xff0c;中文的显示是乱码&#xff0c;故记录下解决方法 问题解决 中文乱码问题还是涉及到编码问题&…

Steam通过短信验证来遏制带有恶意软件的更新

导语段落 近期&#xff0c;Steam平台上的游戏开发者账户遭受了来自恶意软件的更新攻击&#xff0c;为了应对这一问题&#xff0c;Valve公司宣布将实施额外的安全措施&#xff0c;其中包括基于短信的确认码验证。本文将为大家介绍这一新措施以及其对游戏开发者和玩家的影响。 短…

ORA-12541:TNS:no listener 无监听程序

问题截图 解决方法 1、删除Listener 新建一个新的 2、主机为服务器ip 3、设置数据库环境 只需要设置实例名不需要设置路径 4、服务命名 一样设置为ip 服务名与监听名一直 eg&#xff1a;orcl

React中的key有什么作用

一、是什么 首先&#xff0c;先给出react组件中进行列表渲染的一个示例&#xff1a; const data [{ id: 0, name: abc },{ id: 1, name: def },{ id: 2, name: ghi },{ id: 3, name: jkl } ];const ListItem (props) > {return <li>{props.name}</li>; };co…

Windows窗口过程

一、窗口类&#xff08;WNDCLASS&#xff09; 定义&#xff1a;窗口类是一个属性集&#xff0c;是Windows编程中用于创建窗口的模板。窗口类包含了窗口的各种信息的数据结构&#xff0c;每个窗口都具有窗口类&#xff0c;每个窗口都是基于自己的窗口类来进行创建窗口的。每一个…