二叉搜索树(BSTree)

目录

一、二叉搜索树

 二、二叉搜索树的接口及实现

1、二叉搜索树的查找

2、二叉搜索树的插入

3、二叉搜索树的删除

三、二叉搜索树的递归版本

本期博客主要分享二叉搜索树的底层实现。(主要是笔记,供自己复习使用😂)

一、二叉搜索树

二叉搜索树(BST,Binary Search Tree)又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

若它的左子树不为空,则左子树上所有节点的值都小于根节点的值。

若它的右子树不为空,则右子树上所有节点的值都大于根节点的值。

它的左右子树也分别为二叉搜索树。

 二、二叉搜索树的接口及实现

1、二叉搜索树的查找

a、从根开始比较,查找,比根大则往右边查找,比根小则往左边走查找。

b、最多查找高度次,如果走到空,还没找到,这个值在树中不存在。

代码实现:

bool 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 true;
				}
			}
			return false;
		}

2、二叉搜索树的插入

a、树为空,则直接新增节点,赋值给root指针

b、树不空,按二叉搜索树性质查找插入位置,插入新节点。

代码实现:

//插入
		bool Insert(const K& key)
		{
			if (_root == nullptr)
			{
				_root == new Node(key);
				return true;
			}
			Node* cur = _root;
			Node* parent = nullptr;
			//查找插入位置
			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 = new Node(key);
			if (key < parent->_key)
			{
				parent->_left=cur;
			}
			else
			{
				parent->_right=cur;
			}
			return true;
		}

3、二叉搜索树的删除

删除比较麻烦。我们要对它的几种情境进行分析。

a、要删除的节点无孩子节点

b、要删除的节点只有左孩子节点

c、要删除的节点只有右孩子节点

d、要删除的节点有左、右孩子节点

实际情况a可以和情况b或者情况c一块处理。如果右孩子为空,则托孤给父亲节点它的左孩子。如果左孩子为空,则托孤给父亲节点它的右孩子。如果左右孩子都不为空,则要找替换节点。

替换规则:

找右子树的最左节点(右子树值最小),或者找左子树的最右节点(左子树值最大)与要删除节点替换。目的是为了满足根大于左子树而小于右子树。

代码:

bool Erase(const K& key)
		{
			//左孩子为空
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				if (cur->_key < key)
				{
					//key大于_key--去右子树查找
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
					//左子树
				}
				else
				{
					//找到了
					//分为三种情况
					if (cur->_left == nullptr)
					{
						//左孩子为空
						//托孤右孩子
						//判断cur是parent左孩子还是右孩子
						if (cur == _root)//考虑删根的情况
						{
							_root = cur->_right;
						}
						else
						{
							if (cur == parent->_left)
							{
								parent->_left = cur->_right;
							}
							else
							{
								//cur是右孩子
								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
							{
								//cur是右孩子
								parent->_right = cur->_left;
							}
						}
						delete cur;
					}
					else
					{
						//左右都不为空
						Node* minRight = cur->_right;//找右子树的最左节点
						parent = cur;
						while (minRight->_left)
						{
							parent = minRight;
							minRight = minRight->_left;
						}
						cur->_key = minRight->_key;
						//最左节点,不可能有左孩子,只可能有右孩子
						if (minRight == parent->_left)
						{
							parent->_left = minRight->_right;
						}
						else
						{
							parent->_right = minRight->_right;
						}
						delete minRight;
					}
					return true;
				}
			}
			return false;
		}

以上是循环版本主要接口的实现。而二叉搜索树递归版本也是非常有趣的。

三、二叉搜索树的递归版本

template<class K>
	struct BSTreeNode
	{
		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>& copyt)
		{
			//拷贝构造
			_root = Copy(copyt._root);

		}

		BSTree<K>& operator=(BSTree<K> t)
		{
			swap(_root, t._root);
			return *this;
		}
		~BSTree()
		{
			Destory(_root);
			_root = nullptr;
		}

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
		
		bool FindR(const K& key)
		{
			return _FindR(_root, key);
		}
		bool InsertR(const K& key)
		{
			return _InsertR(_root, key);
		}
		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}
	private:
		bool _InsertR(Node*& root, const K& key)
		{
			if (root == nullptr)
			{
				root = new Node(key);
				return true;
			}
			if (root->_key < key)
			{
				return _InsertR(root->_right, key);
			}
			else if (root->_key > key)
			{

				return _InsertR(root->_left, key);
			}
			else
				return false;
			
		}
		bool _FindR(Node* root, const K& key)
		{
			if (root == nullptr)
				return false;
			if (key > root->_key)
			{
				//右子树
				return _FindR(root->_right, key);
			}
			else if (key < root->_key)
			{
				return _FindR(root->_left, key);
			}
			else
				return true;
		}
		bool _EraseR(Node*& root, const K& key)
		{
			if (root == nullptr)
			{
				return false;
			}
			if (root->_key < key)
			{
				return _EraseR(root->_right,key);
			}
			else if (root->_key > key)
			{
				return _EraseR(root->_left, key);
			}
			else
			{
				//找到要删除的节点
				//替换删除?
				if (root->_left == nullptr)
					root = root->_right;
				else if (root->_right == nullptr)
					root = root->_left;
				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;
			}
		}
		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 Destory(Node* root)
		{
			if (root == nullptr)
				return;
			Destory(root->_left);
			Destory(root->_right);
			delete root;
		}
		void _InOrder(Node* root)
		{
			//根左右
			if (root == nullptr)
				return;
			_InOrder(root->_left);
			cout << root->_key << " ";
			_InOrder(root->_right);
		}
		Node* _root;
	};

递归版本实现非常巧妙的地方在于插入接口和删除接口的实现:

他们使用的是root地址的引用而不是地址的拷贝,这一点很是灵巧,博主就不多说大家细细品味其中的妙处使得代码大大简化。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MybatisPlus主键策略

Mybatis默认主键策略是TableId(type IdType.ASSIGN_ID) 这是默认策略雪花算法 此时主键类型可以是String 数据表字段类型可以是bigint int varchar 无需数据表主键自增 TableId(type IdType.ASSIGN_AUTO) 是主键自增策略:该策略为跟随数据库表的主键递增策略&…

一致性框架设计方案

补充组件依赖 前言 对于供应链业务&#xff0c;一般对数据一致性要求高。且由于业务复杂&#xff0c;可能会存在一个业务功能触发几个异步操作的场景&#xff0c;且要保证相关操作同时触发或不触发。 为了降低技术设计难度、代码编写难度&#xff0c;特意设计最终一致性框架&a…

ChatGPT+Ai绘图【stable-diffusion实战】

ai绘图 stable-diffusion生成【还有很大的提升空间】 提示词1 Picture a planet where every living thing is made of light. The landscapes are breathtakingly beautiful, with mountains and waterfalls made of swirling patterns of color. What kind of societies m…

程序员跳槽,要求涨薪50%过分吗?

如果问在TI行业涨工资最快的方式是什么&#xff1f; 回答最多的一定是&#xff1a;跳槽&#xff01; 前段时间&#xff0c;知乎上这样一条帖子引发了不少IT圈子的朋友的讨论 &#xff0c;有网友提问 “程序员跳槽要求涨薪50%过分吗&#xff1f;” 截图来源于知乎&#xff0c;…

摄影知识整理

目录 焦距 焦距分类 对焦 相机的MF与AF 自动对焦操作 自动对焦方式 镜头防抖 防抖模式 景深 景深的作用 影响景深的因素 景深预览 摄影三大元素 光圈 光圈的作用 光圈与景深的关系 感光度&#xff08;ISO) 注意 感光度的作用 快门 B门与T门 快门速度 闪…

【SSM】SpringMVC(三:SpringMVC拦截器)

文章目录 1. 登录案例2. 拦截器2.1 应用2.2 拦截器的执行原理2.3 拦截器执行的时机2.4 拦截器的实现方法2.5 拦截器的实现步骤2.6 开发拦截器 1. 登录案例 【login.jsp】 <%--Created by IntelliJ IDEA.User: BeyongDate: 2023/4/17Time: 11:43To change this template use…

SQL的函数

文章目录 一、SQL LCASE() 函数二、SQL MID() 函数三、SQL LEN() 函数四、SQL ROUND() 函数五、SQL NOW() 函数六、SQL FORMAT() 函数总结 一、SQL LCASE() 函数 LCASE() 函数把字段的值转换为小写。 SQL LCASE() 语法 SELECT LCASE(column_name) FROM table_name;用于 SQL …

入行IC选择国企、私企还是外企?(内附各IC大厂薪资福利情况)

不少人想要转行IC&#xff0c;但不知道该如何选择公司&#xff1f;下面就来为大家盘点一下IC大厂的薪资和工作情况&#xff0c;欢迎大家在评论区补充。 一&#xff0e;老 牌 巨 头 在 IC 设计领域深耕许久&#xff0c;流程完善、技术扎实&#xff0c;公司各项制度都很完善、前…

IT知识百科:什么是暴力破解?

暴力破解是一种常见的网络安全攻击方法&#xff0c;它利用计算机程序自动尝试大量的密码组合来破解密码。这种攻击方法通常用于获取未经授权的访问权限&#xff0c;如入侵网络系统或个人账户。在本文中&#xff0c;我们将探讨暴力破解的原理、工具和防范方法。 暴力破解的原理 …

TCP/UDP协议 (详解)

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点!人生格言&#xff1a;当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔&#x1f9be;&am…

Linux搭建SVN服务器详细教程

前言 本文讲解 Linux 系统下如何搭建 SVN 服务器&#xff0c;详细说明各配置项的功能&#xff0c;最终实现可管控多个项目的复杂配置。 SVN 是 subversion 的缩写&#xff0c;是一个开放源代码的版本控制系统&#xff0c;通过采用分支管理系统的高效管理&#xff0c;实现最终集…

HANA SDA连接外部数据库到BW的步骤

咱都知道&#xff0c;我们不能直接从BW连接到外部数据库。第一步得从HANA database通过SDA去建一个到外部DB的连接。 数据库连接好了&#xff0c;那么接下来别忘了&#xff0c;还得建一个源系统。 也就是说第一步&#xff0c;我们要用HANA SDA通过Linux ODBC driver去连接外部…

PHP快速入门05-时间日期与时区,附30个常用案例

文章目录 前言一、时间日期与时区1.1 时间与日期1.2 时区 二、 30个日期时间函数的用法示例2.1 获取当前的时间戳2.2 将时间戳格式化为日期时间2.3 获取当前的日期2.4 获取当前的时间2.5 获取当前年份2.6 获取当前月份2.7 获取当前日期的第几天2.8 计算两个日期之间的天数差2.9…

【生活工作经验 十】ChatGPT模型对话初探

最近探索了下全球大火的ChatGPT&#xff0c;想对此做个初步了解 一篇博客 当今社会&#xff0c;自然语言处理技术得到了迅速的发展&#xff0c;人工智能技术也越来越受到关注。其中&#xff0c;基于深度学习的大型语言模型&#xff0c;如GPT&#xff08;Generative Pre-train…

Java:MybatisPlus--条件构造器

1、条件构造器类别 ①wrapper&#xff1a;抽象类&#xff0c;条件类的顶层&#xff0c;提供了一些获取和判断相关的方法。 ②AbstractWrapper&#xff1a;抽象类&#xff0c;Wrapper的子类&#xff0c;提供了所有的条件相关方法。 ③AbstractLambdaWrapper&#xff1a;抽象类…

Tinymce富文本编辑器在vue项目中的使用;引入第三方插件和上传视频、图片等

先放张效果图 第一步&#xff1a;安装依赖 npm install tinymce5.0.12 第二步&#xff1a;在项目中的public文件夹中新建tinymce文件夹&#xff08;因为我的项目是脚手架创建的&#xff0c;所以公共文件夹是public&#xff09;&#xff1b;在node_modules中找到skins文件夹复制…

Java day11

第11章 在用户界面上排列组件 11.1 基本的界面布局11.1.1 布置界面11.1.2 顺序布局11.1.3 方框布局11.1.4 网格布局11.1.5 边框布局 11.2 使用多个布局管理器11.3 卡片布局11.3.1 在应用程序中使用卡片布局11.3.2 单元格内边距和面板内边距 11.1 基本的界面布局 11.1.1 布置界…

社科院与杜兰大学中外合作办学金融管理硕士项目——比起过往,前路更值得期待

当结束一天工作陷入沉思时&#xff0c;你有没有特别遗憾的事情呢&#xff0c;人生有太多的不确定性&#xff0c;比起过往&#xff0c;未知的人生更值得我们期待。与其懊恼没完成的遗憾&#xff0c;不如珍惜当下&#xff0c;努力创造未来。人生没有太晚的开始&#xff0c;在职读…

macOS设置环境变量和别名

因为我的mac所用shell是bash&#xff0c;所以本文中涉及的环境变量和别名配置均在~/.zshrc文件中,且在每次配置完成后&#xff0c;需要执行source ~/.zshrc命令使配置文件生效 环境变量 通过配置环境变量&#xff0c;我们可以将某个路径暴露到全局&#xff0c;这样可以在全局…

【C语言学习3——基本的C语言语法知识2】

C语言学习3——基本的C语言语法知识 标识符关键词什么是字面常量&#xff1f;printf函数printf函数更多用法 #include命令 标识符 在前面的代码中&#xff0c;由我们自己命名&#xff0c;用于指代某一个实体的名称&#xff0c;例如:add&#xff0c;result&#xff0c;函数的参…