二叉搜索树介绍以及实现

news2024/10/2 16:16:38

        二叉树无论是在实际运用还是面试题中,都是一种十分热门的数据结构,而二叉搜索树则是进阶版的二叉树,在map和set中也有应用。

什么是二叉搜索树

二叉搜索树又叫二叉排序树,它可以是一颗空树,又或者是有以下三个特点的树。

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

因为二叉搜索树具有以上三个特性,因此二叉搜索树的最优搜索次数为 O(log^2) ,最差搜索次数为 O(N)。

此外,中序遍历一个二叉搜索树所得到的结果应该是一个有序的数组

二叉搜索树的实现

二叉搜索树的查找

  • 从根节点开始查找,若查找的 val 大于根节点的值,则向右子树查找,否则向左子树查找
  • 最多查找高度次,走到空还没有找到,就返回nullptr,否则就返回这个节点。
pNode find(const T& val)
{
	pNode cur = Root;
	if (cur->_data == val)
	{
		return cur;
	}
	while (cur != nullptr)
	{
		if (val > cur->_data)
		{
			cur = cur->_right;
		}
		else if (val < cur->_data)
		{
			cur = cur->_left;
		}
		else
		{
			return cur;
		}
	}
	return nullptr;
}

二叉搜索树的插入

 二叉树的插入需要找到正确的位置。

  • val大于当前节点的值就向右子树走
  • val小于当前节点的值就向左子树走
  • 直到找到一个空节点就插入
bool insert(T val)
{
	pNode cur = new Node(val);
	pNode root = Root;
	if (Root!=nullptr&&find(val) != nullptr)
	{
		return false;
	}
	if (root == nullptr)
	{
		Root = cur;
		return true;
	}

	while (root != nullptr)
	{
		if (val > root->_data)
		{
			if (root->_right == nullptr)
			{
				root->_right = cur;
				break;
			}
			root = root->_right;
		}
		else if (val < root->_data)
		{
			if (root->_left == nullptr)
			{
				root->_left = cur;
				break;
			}
			root = root->_left;
		}
	
	}
	return true;
}

二叉搜索树的删除

二叉搜索树的删除是最难的,需要分情况说明。

  • 左子树为空,但右子树不为空
  • 右子树为空,但左子树不为空
  • 左右都为空
  • 左右都不为空

对于最后一种情况来说,这个节点是一个叶子节点,可以直接删除

右为空左不为空

当 cur 节点是父节点的左子树,就需要将父节点的左向量连接在cur节点的左子树上。

        

反之当cur节点是父节点的右子树,就需要将父节点的右向量连接在cur节点的左子树上。

左为空右不为空

若cur节点的左子树为空,且cur节点是父节点的左子树,就需要将父节点的左向量连接在cur节点的右子树上。

cur节点是父节点的右子树,则需要将父节点的右向量连接在cur节点的右子树上。

左右不为空 

若节点的左右子树都不是空树,为了不破坏二叉搜索树的结构,就需要利用一个中间变量(minright)来辅助删除。

minright应是被删节点的右子树中的最左节点。

1:若minright是被删节点的右节点。

这种情况下就直接交换cur和minright 的值,然后让cur 的右指针指向 minright 的右节点。

2:minright不是被删节点的右节点。

 

这种情况下,就需要记录 minright 的 parent 节点,让 parent 的左指针指向 minright 的右节点。

然后再交换 cur 和 minright 的值,再删除 minright 的节点。

	bool erase(T val)
	{
		pNode cur = Root;
		pNode parent = nullptr;
		while (cur != nullptr)
		{
			//大于向右走
			if (val > cur->_data)
			{
				parent = cur;
				cur = cur->_right;
			}
			//小于向左走
			else if (val < cur->_data)
			{
				parent = cur;
				cur = cur->_left;
			}
			//等于则开始消除
			else
			{
				if (cur->_right == nullptr)
				{
					//如果是根节点,就直接拿左节点当根
					if (cur == Root)
					{
						Root = cur->_left;

					}
					//否则就正常进行
					else
					{
						//如果cur是parent的右节点,就直接拿cur的左节点和parent的右节点相连
						if (cur == parent->_right)
						{
							parent->_right = cur->_left;
						}
						//如果cur是parent的左节点,就拿cur的左节点和parent的左节点相连
						else if (cur == parent->_left)
						{
							parent->_left = cur->_left;
						}
					}
					delete cur;
				}
				else if (cur->_left == nullptr)
				{
					//如果是根节点
					if (cur == Root)
					{
						Root = cur->_right;
					}
					//否则就正常进行
					else
					{
						//如果cur是parent的右节点,就直接拿cur的左节点和parent的右节点相连
						if (cur == parent->_right)
						{
							parent->_right = cur->_right;
						}
						//如果cur是parent的左节点,就拿cur的左节点和parent的左节点相连
						else if (cur == parent->_left)
						{
							parent->_left = cur->_right;
						}
					}
					delete cur;
				}
				//左右都不为空
				else
				{
					pNode minright = cur->_right;
					parent = cur;
					while (minright->_left)
					{
						parent = minright;
						minright = minright->_left;
					}
					cur->_data = minright->_data;
					if (minright == parent->_right)
					{
						parent->_right = minright->_right;
					}
					else
					{
						parent->_left = minright->_right;
					}
					delete minright;
				}
				return true;
			}
			
		}
		return false;
	}

 

完整代码

using namespace std;
template<class T>
class BSTNode {
public:
	BSTNode(T data)
		:_left(nullptr),_right(nullptr),_data(data)
	{
	}
	T _data;
	BSTNode<T>* _left;
	BSTNode<T>* _right;
};

template<class T>
class BSTree {
	typedef BSTNode<T> Node;
	typedef Node* pNode;
public:
	BSTree()
		:Root(nullptr)
	{}

	~BSTree()
	{
		destroy(Root);
	}
	void destroy(pNode root)
	{
		if (root->_left != nullptr)
		{
			destroy(root->_left);
		}
		if (root->_right != nullptr)
		{
			destroy(root->_right);
		}
		delete(root);
	}

	pNode find(const T& val)
	{
		pNode cur = Root;
		if (cur->_data == val)
		{
			return cur;
		}
		while (cur != nullptr)
		{
			if (val > cur->_data)
			{
				cur = cur->_right;
			}
			else if (val < cur->_data)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}
	bool erase(T val)
	{
		pNode cur = Root;
		pNode parent = nullptr;
		while (cur != nullptr)
		{
			//大于向右走
			if (val > cur->_data)
			{
				parent = cur;
				cur = cur->_right;
			}
			//小于向左走
			else if (val < cur->_data)
			{
				parent = cur;
				cur = cur->_left;
			}
			//等于则开始消除
			else
			{
				if (cur->_right == nullptr)
				{
					//如果是根节点,就直接拿左节点当根
					if (cur == Root)
					{
						Root = cur->_left;

					}
					//否则就正常进行
					else
					{
						//如果cur是parent的右节点,就直接拿cur的左节点和parent的右节点相连
						if (cur == parent->_right)
						{
							parent->_right = cur->_left;
						}
						//如果cur是parent的左节点,就拿cur的左节点和parent的左节点相连
						else if (cur == parent->_left)
						{
							parent->_left = cur->_left;
						}
					}
					delete cur;
				}
				else if (cur->_left == nullptr)
				{
					//如果是根节点
					if (cur == Root)
					{
						Root = cur->_right;
					}
					//否则就正常进行
					else
					{
						//如果cur是parent的右节点,就直接拿cur的左节点和parent的右节点相连
						if (cur == parent->_right)
						{
							parent->_right = cur->_right;
						}
						//如果cur是parent的左节点,就拿cur的左节点和parent的左节点相连
						else if (cur == parent->_left)
						{
							parent->_left = cur->_right;
						}
					}
					delete cur;
				}
				//左右都不为空
				else
				{
					pNode minright = cur->_right;
					parent = cur;
					while (minright->_left)
					{
						parent = minright;
						minright = minright->_left;
					}
					cur->_data = minright->_data;
					if (minright == parent->_right)
					{
						parent->_right = minright->_right;
					}
					else
					{
						parent->_left = minright->_right;
					}
					delete minright;
				}
				return true;
			}
			
		}
		return false;
	}
	bool insert(T val)
	{
		pNode cur = new Node(val);
		pNode root = Root;
		if (Root!=nullptr&&find(val) != nullptr)
		{
			return false;
		}
		if (root == nullptr)
		{
			Root = cur;
			return true;
		}

		while (root != nullptr)
		{
			if (val > root->_data)
			{
				if (root->_right == nullptr)
				{
					root->_right = cur;
					break;
				}
				root = root->_right;
			}
			else if (val < root->_data)
			{
				if (root->_left == nullptr)
				{
					root->_left = cur;
					break;
				}
				root = root->_left;
			}
		
		}
		return true;
	}
private:
	pNode Root;
};

总结

二叉搜索树是一种进阶版的二叉树结构,实际应用中十分广泛,面试题中经常出现,而在二叉搜索树之上还有AVL树和红黑树,不过这些都是后话了。

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

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

相关文章

编译原理笔记(三)

一、词法分析程序的设计 1、词法分析程序的输出 在识别出下一个单词同时验证其词法正确性之后&#xff0c;词法分析程序将结果以单词符号的形式发送至语法分析程序以回应其请求。 单词符号一般分下列5类&#xff1a; 关键字&#xff1a;如&#xff1a;begin、end、if、whil…

力扣2807.在链表中插入最大公约数

思路&#xff1a;遍历链表&#xff0c;对于每一个结点求出它与下一个结点的最大公约数并插入到俩个结点之间 代码&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}…

算法日志的存在核心在于搭建自检系统

"相信每一个人执行与日志有关的任务都会遇到这样难题吧&#xff1f;长达几万行的日志&#xff0c;如果我们单纯用肉眼去一个个排查&#xff0c;那么恐怕所耗费的时间是以天为计量单位了。当然这是一种比较夸张的情况&#xff0c;根据我的项目经验&#xff0c;正常情况是十…

【langchain】在单个文档知识源的上下文中使用langchain对GPT4All运行查询

In the previous post, Running GPT4All On a Mac Using Python langchain in a Jupyter Notebook, 我发布了一个简单的演练&#xff0c;让GPT4All使用langchain在2015年年中的16GB Macbook Pro上本地运行。在这篇文章中&#xff0c;我将提供一个简单的食谱&#xff0c;展示我们…

GeoServe本地部署结合内网穿透实现远程访问Web管理界面

文章目录 前言1.安装GeoServer2. windows 安装 cpolar3. 创建公网访问地址4. 公网访问Geo Servcer服务5. 固定公网HTTP地址 前言 GeoServer是OGC Web服务器规范的J2EE实现&#xff0c;利用GeoServer可以方便地发布地图数据&#xff0c;允许用户对要素数据进行更新、删除、插入…

STM32的在线升级(IAP)实现方法:BOOT+APP原理详解

0 工具准备 Keil uVision5 Cortex M3权威指南&#xff08;中文&#xff09; STM32参考手册 1 在线升级&#xff08;IAP&#xff09;设计思路 为了实现STM32的在线升级&#xff08;IAP&#xff09;功能&#xff0c;通常会将STM32的FLASH划分为BOOT和APP两个部分&#xff0c;BOO…

Vue组件封装

组件封装 一个封装好的组件可以在项目的任意地方使用&#xff0c;甚至我们可以直接从npm仓库下载别人封装好的组件来进行使用&#xff0c;比如iview、element-ui这一类的组件库。但是每个公司的需求是不一样的&#xff0c;我们可以封装自己的组件库并发布到npm上去&#xff0c…

【ONE·MySQL || 基本查询(CRUD)】

总言 主要内容&#xff1a;表的增删查改&#xff08;DML操作&#xff09;。insert插入&#xff08;包含插入更新、插入查询&#xff09;&#xff0c;replace替换。select查询&#xff08;包含列别名、distinct去重、where条件筛选、order排序、limit子句、group by子句、having…

【2023年度总结】多变的2023 | 成长的2023 | 蜕变的2023

文章目录 2023年&#x1f4cc;&#xff0c;对我来说2023年&#xff0c;是多变的一年&#x1f393;2023年&#xff0c;是挑战的一年&#x1f38a;2023年&#xff0c;是惊喜的一年&#x1f389;2023年&#xff0c;是好多第一次的一年&#x1f3a8; 2024年&#xff0c;是新的开始2…

计算机组成原理-进位计数制(进制表示 进制转换 真值和机器树)

文章目录 现代计算机的结构总览最古老的计数方法十进制计数法推广&#xff1a;r进制计数法任意进制->十进制二进制<--->八进制&#xff0c;十六进制 各种进制常见的书写方式十进制->任意进制整数部分小数部分 十进制->二进制&#xff08;拼凑法&#xff09;真值…

一起学docker(六)| docker网络

Docker网络 不启动docker&#xff0c;网络情况&#xff1a; 启动docker&#xff0c;网络情况&#xff1a; 作用 容器间的互联和通信以及端口映射容器IP变动时候可以通过服务名直接网络通信而不受影响 常用命令 docker network --help 查看docker网络相关命令docker network…

Elasticsearch:结合 ELSER 和 BM25 文本查询的相关搜索

Elastic Learned Spare EncodeR (ELSER) 允许你执行语义搜索以获得更相关的搜索结果。 然而&#xff0c;有时&#xff0c;将语义搜索结果与常规关键字搜索结果相结合以获得最佳结果会更有用。 问题是&#xff0c;如何结合文本和语义搜索结果&#xff1f; 首先&#xff0c;让我…

大数据 MapReduce如何让数据完成一次旅行?

专栏上一期我们聊到MapReduce编程模型将大数据计算过程切分为Map和Reduce两个阶段&#xff0c;先复习一下&#xff0c;在Map阶段为每个数据块分配一个Map计算任务&#xff0c;然后将所有map输出的Key进行合并&#xff0c;相同的Key及其对应的Value发送给同一个Reduce任务去处理…

1_开闭原则(Open Closed Principle)

开闭原则(Open Closed Principle) 1.概念 开闭原则&#xff08;Open-Closed Principle&#xff09;是指一个软件实体如类、模块和函数应该对扩展开放&#xff0c; 对修改关闭。所谓的开闭&#xff0c;也正是对扩展和修改两个行为的一个原则。强调的是用抽象构建框架&#xff…

“TypeError: Cannot read properties of null (reading ‘getContext‘)“

目录 一、报错截图 二、使用场景 三、代码截图 四、报错原因 五、解决办法 一、报错截图 二、使用场景 第一次在vue项目种使用canvas&#xff0c;跟着网上教程做&#xff0c;标签canvas写好了&#xff0c;dom元素获取了&#xff0c;简单“画”了一下&#xff0c;运行之后报…

基于Rangenet Lib的自动驾驶LiDAR点云语义分割与可视化

这段代码是一个C程序&#xff0c;用于处理来自KITTI数据集的激光雷达&#xff08;LiDAR&#xff09;扫描数据。程序主要实现以下功能&#xff1a; 1. **读取和解析命令行参数**&#xff1a;使用Boost库中的program_options模块来定义和解析命令行参数。这包括扫描文件路径、模型…

程序员副业之无人直播助眠

介绍和概览 大家好&#xff0c;我是小黑&#xff0c;本文给大家介绍一个比较轻松简单的副业&#xff0c;无人直播助眠副业。 这个项目的核心就是通过直播一些助眠素材来赚钱。比如你可以放一些舒缓的雨声之类的&#xff0c;吸引观众进来。然后&#xff0c;咱们可以挂个小程序…

K8Spod组件

一个pod能包含几个容器 一个pause容器(基础容器/父容器/根容器&#xff09; 一个或者多个应用容器(业务容器) 通常一个Pod最好只包含一个应用容器&#xff0c;一个应用容器最好也只运行一个业务进程。 同一个Pod里的容器都是运行在同一个node节点上的&#xff0c;并且共享 net、…

深度学习中的知识蒸馏

一.概念 知识蒸馏&#xff08;Knowledge Distillation&#xff09;是一种深度学习中的模型压缩技术&#xff0c;旨在通过从一个教师模型&#xff08;teacher model&#xff09;向一个学生模型&#xff08;student model&#xff09;传递知识来减小模型的规模&#xff0c;同时保…