C++数据结构 —— 二叉搜索树

news2024/9/22 5:35:25

目录

1.二叉搜索树的基本概念

1.1二叉搜索树的基本特征

 2.二叉搜索树的实现

2.1数据的插入(迭代实现)

2.2数据的搜索(迭代实现)

2.3中序遍历(递归实现)

2.4数据的删除(迭代实现)

2.5数据的搜索(递归实现)

2.6数据的插入(递归实现)

2.7数据的删除(递归实现)

2.8类的完善

3.二叉搜索树的应用

4.完整代码

 二叉搜索树

1.二叉搜索树的基本概念

二叉搜索树又称二叉排序树,它可以是一颗空树。二叉搜索树的作用是搜索,排序(二叉搜索树的中序遍历是一组递增有序数据)。

1.1二叉搜索树的基本特征

如果某颗二叉树(包括空树)满足以下性质,可以作为一颗二叉搜索树:

        1.如果左子树不为空,其键值应小于根节点的键值。

        2.如果右子树不为空,其键值应大于根节点的键值。

        3.左右子树都满足上述条件。

没有二叉搜索树之前,常用的查找算法为二分查找。但是二分查找是有局限性的(必须针对有序数组)。二叉搜索树因其特性,例如我们需要查找Key值,只需要与根节点的键值做比较:若Key小于根节点的键值,则往根节点的左子树遍历;若Key值大于根节点的键值,则往根节点的右子树遍历。经计算,查找的次数等于二叉搜索树的深度。正因为如此,二叉搜索树并不是一个优秀的数据结构,因为一但碰到极端情况,二叉搜索树的搜索效率将会大打折扣。所以在往后的章节中,将会使其平衡。

 2.二叉搜索树的实现

 将二叉搜索树定义为一个类,现在将展示类的框架。往后所有的演示代码,都可以直接加入其中:

// 节点
template <class K>
struct BST_node
{
	BST_node<K>* _left;		//左子树
	BST_node<K>* _right;	//右子树
	K _key;

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

template <class K>		//节点键值的数据类型
class BST
{
	typedef BST_node<K> Node;
public:

private:
	Node* _root;	//根节点
};

2.1数据的插入(迭代实现)

bool insert(const K& key)
{
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}
		
	Node* prev = nullptr;	// cur的父节点
	Node* cur = _root;
	while (cur)
	{
		if (key < cur->_key)	//如果比根节点的键值小
		{
			prev = cur;
			cur = cur->_left;
		}
		else if(key > cur->_key)	//如果比根节点的键值大
		{
			prev = cur;
			cur = cur->_right;
		}
		else
		{
			// 我们不允许插入重复的数据
			return false;
		}
	}

	// 直到遍历到空,才施行插入
	cur = new Node(key);
	if (key < prev->_key)
	{
		prev->_left = cur;
	}
	else if (key > prev->_key)
	{
		prev->_right = cur;
	}
	return true;
}

2.2数据的搜索(迭代实现)

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;
}

2.3中序遍历(递归实现)

void MidTraval()	//此接口作公有
{
	__MidTraval(_root);
	cout << endl;
}

void __MidTraval(Node* root)	//此接口做私有
{
	if (root == nullptr)
	{
		return;
	}

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

2.4数据的删除(迭代实现)

需要注意,要删除二叉搜索树的节点,就必须分两种情况讨论:

        1.要删除节点的左子树或右子树为空。

        2.要删除节点的左、右子树都不为空。

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

	Node* prev = _root;
	Node* cur = _root;
	while (cur)
	{
		if (key < cur->_key)
		{
			prev = cur;
			cur = cur->_left;
		}
		else if (key > cur->_key)
		{
			prev = cur;
			cur = cur->_right;
		}
		else
		{
			// 如果左子树为空
			if (cur->_left == nullptr)
			{
				// 假设右子树不为空,则将右子树托孤给父节点
				if (_root == cur)
				{
					_root = _root->_right;
				}
				else if (prev->_left == cur)
				{
					prev->_left = cur->_right;
				}
				else if (prev->_right == cur)
				{
					prev->_right = cur->_right;
				}

				delete cur;
				return true;
			}

			// 如果右子树为空
			else if (cur->_right == nullptr)
			{
				//假设左子树不为空,则将左子树托孤给父节点
				if (_root == cur)
				{
					_root = _root->_left;
				}
				else if (prev->_left == cur)
				{
					prev->_left = cur->_left;
				}
				else if (prev->_right == cur)
				{
					prev->_right = cur->_left;
				}

				delete cur;
				return true;
			}

			// 如果左右子树都不为空
			else
			{
				// 假设使用右子树的最小值替代
				Node* prev = _root;
				Node* minRight = cur->_right;

				while (minRight->_left)		//二叉树特性,越往左越小
				{
					prev = minRight;
					minRight = minRight->_left;
				}

				cur->_key = minRight->_key;

				// 替换好后,就要删除minRight
				if (prev->_left == minRight)
				{
					prev->_left = minRight->_right;
				}
				else if (prev->_right == minRight)
				{
					prev->_right = minRight->_right;
				}
					
				delete minRight;
				return true;
			}
		}
	}
	return false;
}

2.5数据的搜索(递归实现)

bool findR(const K& key)
{
	return __findR(_root, key);
}

bool __findR(Node* root, const K& key)	//此接口作私有
{
	if (root == nullptr)
	{
		return false;
	}

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

2.6数据的插入(递归实现)

bool insertR(const K& key)
{
	return __insertR(_root, key);
}

bool __insertR(Node*& root, const K& key)	//此接口作私有
{
	if (root == nullptr)
	{
		root = new Node(key);	//注意引用传参,root相当于root->left或root->right的别名
		return true;
	}


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

2.7数据的删除(递归实现)

bool eraseR(const K& key)
{
	return __eraseR(_root, key);
}

bool __eraseR(Node*& root, const K& key)	//此接口作私有
{
	if (root == nullptr)
	{
		return false;
	}

	if (key < root->_key)
	{
		return __eraseR(root->_left, key);
	}
	else if (key > root->_key)
	{
		return __eraseR(root->_right, key);
	}
	else
	{
		Node* del = root;
		if (root->_left == nullptr)
		{
			// 此时root就是要删除的节点,并且是root的父节点的子节点的引用(root == root->_left...)
			root = root->_right;

			delete del;
			return true;
		}
		else if (root->_right == nullptr)
		{
			root = root->_left;

			delete del;
			return true;
		}
		else
		{
			Node* prev = _root;
			Node* minRight = root->_right;

			while (minRight->_left)		//二叉树特性,越往左越小
			{
				prev = minRight;
				minRight = minRight->_left;
			}

			root->_key = minRight->_key;

			// 替换好后,就要删除minRight
			if (prev->_left == minRight)
			{
				prev->_left = minRight->_right;
			}
			else if (prev->_right == minRight)
			{
				prev->_right = minRight->_right;
			}

			delete minRight;
			return true;
		}
	}
	return false;
}

2.8类的完善

BST()
	:_root(nullptr)
{}

~BST()
{
	Destructor(_root);
	_root = nullptr;
}

void Destructor(Node* root)	//此函数作私有
{
	if (root == nullptr)
	{
		return;
	}

	// 后序删除
	Destructor(root->_left);
	Destructor(root->_right);
	delete root;
}

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

Node* Copy(Node* root)	//此接口作私有
{
	if (root == nullptr)
	{
		return nullptr;
	}

	Node* ret = new Node(root->_key);
	ret->_left = Copy(root->_left);
	ret->_right = Copy(root->_right);
	return ret;
}

BST<K>& operator==(BST<K> t)	//现代写法
{
	swap(_root, t._root);
	return *this;
}

3.二叉搜索树的应用

1.K模型:

        K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。上面模拟实现的搜索就是K模型。

        例如将英文字典所有的英文单词存储二叉搜索树这个数据结构,那么可将英文单词看作关键码Key,假设我们想查找"hello"这个单词,直接去数据结构找即可。

2.KV模型:

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

        例如英汉互译,一个英文单词对应了多个汉语翻译。我们将<英文单词,中文翻译的数组>这样的键值对放入二叉搜索树中。例如查找"hello"这个单词的中文翻译,只需要查找键值对的英文单词即可。

KV模型例题:

给定下面数组,求每种水果出现的次数:

string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };

第一步:先实现二叉搜索树(为了方便,这里只保留插入数据、查找和中序遍历的接口):

namespace KV
{

	// 节点
	template <class Key,class Val>
	struct BST_node
	{
		BST_node<Key,Val>* _left;		//左子树
		BST_node<Key,Val>* _right;	//右子树
		Key _key;
		Val _val;
		BST_node(const Key& key,const Val& val)
			:_key(key), _val(val),_left(nullptr), _right(nullptr)
		{}
	};

	template <class Key,class Val>
	class BST
	{
		typedef BST_node<Key,Val> Node;
	public:
		bool insert(const Key& key,const Val& val)
		{
			if (_root == nullptr)
			{
				_root = new Node(key,val);
				return true;
			}

			Node* prev = nullptr;
			Node* cur = _root;
			while (cur)
			{
				if (key < cur->_key)
				{
					prev = cur;
					cur = cur->_left;
				}
				else if (key > cur->_key)
				{
					prev = cur;
					cur = cur->_right;
				}
				else
				{
					return false;
				}
			}

			cur = new Node(key,val);
			if (key < prev->_key)
			{
				prev->_left = cur;
			}
			else if (key > prev->_key)
			{
				prev->_right = cur;
			}
			return true;
		}


		Node* find(const Key& 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;
		}


		bool erase(const Key& key)
		{
			if (_root == nullptr)
			{
				return false;
			}

			Node* prev = _root;
			Node* cur = _root;
			while (cur)
			{
				if (key < cur->_key)
				{
					prev = cur;
					cur = cur->_left;
				}
				else if (key > cur->_key)
				{
					prev = cur;
					cur = cur->_right;
				}
				else
				{
					if (cur->_left == nullptr)
					{
						if (_root == cur)
						{
							_root = _root->_right;
						}
						else if (prev->_left == cur)
						{
							prev->_left = cur->_right;
						}
						else if (prev->_right == cur)
						{
							prev->_right = cur->_right;
						}

						delete cur;
						return true;
					}

					else if (cur->_right == nullptr)
					{
						if (_root == cur)
						{
							_root = _root->_left;
						}
						else if (prev->_left == cur)
						{
							prev->_left = cur->_left;
						}
						else if (prev->_right == cur)
						{
							prev->_right = cur->_left;
						}

						delete cur;
						return true;
					}

					else
					{
						Node* prev = _root;
						Node* minRight = cur->_right;

						while (minRight->_left)
						{
							prev = minRight;
							minRight = minRight->_left;
						}

						cur->_key = minRight->_key;
						if (prev->_left == minRight)
						{
							prev->_left = minRight->_right;
						}
						else if (prev->_right == minRight)
						{
							prev->_right = minRight->_right;
						}

						delete minRight;
						return true;
					}
				}
			}
			return false;
		}


		void MidTraval()	
		{
			__MidTraval(_root);
			cout << endl;
		}

	private:
		Node* _root = nullptr;

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

			__MidTraval(root->_left);
			cout << root->_key << ":" << root->_val << endl;
			__MidTraval(root->_right);
		}
	};
}

第二步:算法实现:

void test_count()
{
	KV::BST<string, int> bt;
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };

	for (auto& e : arr)
	{
		KV::BST_node<string, int>* ret = bt.find(e);
		if (ret)	//不为空,证明数据结构已有
		{
			ret->_val++;	//次数++
		}
		else
		{
			bt.insert(e, 1);
		}
	}

	bt.MidTraval();
}

4.完整代码

 二叉搜索树

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

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

相关文章

元宇宙开始告别以资本为主导的野蛮生长,新的竞争格局和态势将形成

欲要成为这样一场洗牌的胜利者&#xff0c;元宇宙的玩家需要真正站在商业的角度&#xff0c;而非资本市场的角度来看待元宇宙&#xff0c;来寻找元宇宙的正确的发展模式和方法。原因在于&#xff0c;在这样一场洗牌过程当中&#xff0c;仅仅只是对于以往以资本为主导的发展模式…

Web自动化测试——selenium篇(二)

文章目录一、浏览器相关操作二、键盘操作三、鼠标操作四、弹窗操作五、下拉框选择六、文件上传七、错误截图一、浏览器相关操作 浏览器窗口大小设置 driver.manage().window().maximize();//窗口最大化 driver.manage().window().minimize();//窗口最小化 driver.manage().wi…

【GD32F427开发板试用】01适配RTX4+调试组件Event Recorder

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;汪阳 感谢极术社区和兆易创新组织的开发板试用活动。收到开发板有一周多了&#xff0c;因为工作关系好久没有折腾MDK了&#xff0c;上手浪费的…

教你文本生成图片——stablediffusion

今天来点轻松的话题&#xff0c;带大家玩一个用文字生成图片的模型。相信大家如果关注AIGC领域&#xff0c;对文本生成图片&#xff0c;对Stablefiffusion、DEALL.E应该不陌生。今天给大家介绍的就是基于SD2 finetune出来的一个模型&#xff08;&#xff09;这篇文章不会教大家…

ArcGIS中基于网格实现可视化

1 数据来源介绍 土地利用数据、高程数据、植被指数数据均来源于中国科学院资源环境科学与数据中心&#xff08;https://www.resdc.cn/&#xff09;。 2 数据预处理 我们从中国科学院资源环境科学与数据中心官网上下载下来的土地利用栅格数据是整型的&#xff0c;我们可以利用…

高低温真空磁场探针台T8-EM4的技术指标

锦正茂高低温真空磁场探针台探针台配备4个&#xff08;可选6个或8个&#xff09;拥有高精度位移的探针臂&#xff0c;同时配有高精度电子显微镜&#xff0c;便于微小样品的观察操作。探针可通过直流或者低频交流信号&#xff0c;用来测试芯片、晶圆片、封装器件等&#xff0c;广…

火眼审阅 | 基于NLP和OCR识别技术赋能合同审阅

合同作为确定权利义务的法律文件&#xff0c;贯穿企业内外部活动的所有环节&#xff0c;可见合同数据之于企业是非常重要的数据资产。 合同管理是企业营业中的重要部分&#xff0c;其中合同审核是企业法务的基本工作之一。而对于所有的法务人员一直存在一个问题&#xff1a;合…

Java读取mysql导入的文件时中文字段出现�??的乱码如何解决

今天在写程序时遇到了一个乱码问题&#xff0c;困扰了好久&#xff0c;事情是这样的&#xff0c; 在Mapper层编写了查询语句&#xff0c;然后服务处调用&#xff0c;结果控制器返回一堆乱码 然后查看数据源头处&#xff1a; 由重新更改解码的字符集&#xff0c;在数据库中是正…

算法总结c++

文章目录基本概念时间复杂度空间复杂度基本结构1. 数组前缀和差分数组快慢指针(索引)左右指针&#xff08;索引&#xff09;盛水容器三数之和最长回文子串2. 链表双指针删除链表的倒数第 n 个结点翻转链表递归将两个升序链表合并为一个新的 升序 链表链表翻转3. 散列表twoSum无…

Camtasia2023最新版本新功能及快捷键教程

使用Camtasia&#xff0c;您可以毫不费力地在计算机的显示器上录制专业的活动视频。除了录制视频外&#xff0c;Camtasia还允许您从外部源将高清视频导入到录制中。Camtasia的独特之处在于它可以创建包含可单击链接的交互式视频&#xff0c;以生成适用于教室或工作场所的动态视…

UDS诊断之DTC码构成

DTC(Diagnostic Trouble Code)表示诊断故障码&#xff08;全局唯一&#xff09;&#xff0c;是故障类型的"身份ID"&#xff1b;用于汽车故障时对故障部位及原因的排查。 格式如下&#xff1a; 其中&#xff0c;DTCHighByte、DTCMiddleByte这两个字节表示故障内码&…

nvm控制node版本

安装 nvm 1、下载 nvm 官网安装包&#xff1a; github 选择 nvm-setup.exe 下载 2、安装 1、选择 nvm 安装目录&#xff08;可自定义&#xff09; 2、选择 node 安装目录&#xff08;如有安装过&#xff0c;可以选择以前安装目录&#xff0c;可 cdm 输入 where node 查看原nod…

嵌入式Qt 开发一个音乐播放器

上篇文章&#xff1a;RK3568源码编译与交叉编译环境搭建&#xff0c;进行了OK3568开发板软件开发环境搭建&#xff0c;通过编译RK3568的源码&#xff0c;可以得到Qt开发的交叉编译相关工具。 本篇&#xff0c;就来在搭建好的软件开发中&#xff0c;进行Qt软件的开发测试。由于…

package-lock.json,深度内容

前言 看完本文&#xff0c;你将从整体了解依赖版本锁定原理&#xff0c;package-lock.json 或 yarn.lock 的重要性。首先要从最近接连出现两起有关 npm 安装 package.json 中依赖包&#xff0c;由于依赖包版本更新 bug 造成项目出错问题说起。 事件一&#xff1a;新版本依赖包…

JavaScript 二叉树

文章目录前言一、何为 树1.根节点2.外&内部节点3.子树4.深度5.高度二、二叉树 & 二叉搜索树1.二叉搜索树插入值2.遍历二叉搜索树I.中序遍历II.先序遍历III.后序遍历3.查找节点4.移除节点总结前言 同前面说到的散列表结构, 树也是一种非顺序数据结构, 对于存储需要快速…

【浅学Nginx】Nginx安装和基础使用

Nginx安装和基础使用1. Nginx是什么2. Nginx的安装3. Nginx的目录结构4. Nginx的配置文件结构5. Nginx的具体应用5.1 部署静态资源5.2 反向代理5.3 负载均衡1. Nginx是什么 Nginx是一个轻量级的 web服务器 / 反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服…

kettle开发-Day37-SQ索引优化

前言&#xff1a;在上一个生产项目中&#xff0c;有个单表数据超249G了&#xff0c;里面存储的数据时间跨度就1年左右&#xff0c;那为啥会出现这种情况呢&#xff1f;数据来源为&#xff0c;一个生产基地所有电表的每分钟读数&#xff0c;一个基地大概500个电表左右&#xff0…

【C++】---Stack和Queue的用法及其模拟实现

文章目录Stack最小栈栈的弹出压入序列逆波兰表达式求值用栈实现队列模拟实现queue用队列实现栈模拟实现Stack stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行元素的插入与提取操作。它的使用和之前学习的ve…

KDZD880 智能蓄电池放电测试仪

一、产品概述 智能蓄电池放电测试仪主要用于电信、移动、联通、电力直流行业的后备电源铅酸蓄电池的放电测试&#xff0c;具备蓄电池快速容量测试、在线监测及容量核对测试三大功能于一体的产品&#xff0c;集成化程度高、体积小巧、功能完善。 该设备是针对整组 12V-600V 蓄…

JavaScript高级程序设计读书分享之3章——3.4数据类型

JavaScript高级程序设计(第4版)读书分享笔记记录 适用于刚入门前端的同志 ECMAScript 有 6 种简单数据类型&#xff08;也称为原始类型&#xff09;&#xff1a;Undefined、Null、Boolean、Number、String 和 Symbol&#xff08;es6新增&#xff09;。 还有一种复杂数据类型叫…