一棵有点自律的树——搜索二叉树

news2024/11/23 19:45:10

在这里插入图片描述

文章目录

  • 💐专栏导读
  • 💐文章导读
  • 🌷搜索二叉树概念
  • 🌷二叉搜索树的构建
    • 🌺查找操作
    • 🌺插入操作
    • 🌺删除操作
    • 🌺遍历操作
    • ☘️测试
  • 🏵️拓展——递归实现
    • 🍃递归查找
    • 🍃递归插入
    • 🍃递归删除
  • ❄️完整源码
    • 🐙非递归版
    • 🐌递归版本

💐专栏导读

🌸作者简介:花想云,在读本科生一枚,致力于 C/C++、Linux 学习。

🌸本文收录于 C++系列,本专栏主要内容为 C++ 初阶、C++ 进阶、STL 详解等,专为大学生打造全套 C++ 学习教程,持续更新!

🌸相关专栏推荐:C语言初阶系列C语言进阶系列数据结构与算法Linux从入门到精通

💐文章导读

本章我们将认识一种新的二叉树——搜索二叉树。这棵树有个神奇的功能就是会对数据自动排序且有着非常高的查找效率。搜索二叉树作为set、map的基础结构,同样又是接下来将要学到的AVL树以及红黑树的实现基础非常值得我们去深入学习~

在这里插入图片描述

🌷搜索二叉树概念

二叉搜索树本质上也是一种二叉树,只不过多了一个约束规则——

若一棵二叉树不为空,则:

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

所以构建一个搜索二叉树,只需要在插入结点时满足约束规则即可。

🌷二叉搜索树的构建

与二叉树相同,二叉搜索树由一个个结点链接而成。每个结点包含三个成员——

  • _left(左孩子);
  • _right(有孩子);
  • _key(键值);

所以首先定义一个BSTNode(Binary Search Tree简写)结构体——

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

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

同样的,再定义一个搜索二叉树的类,即class BSTree——

template <class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	// 成员函数的实现
	// 插入、删除、查找...
private:
	Node* _root = nullptr;
};

接着就是各种成员函数的实现了~

🌺查找操作

搜索二叉树的查找比较简单而且更容易帮助我们理解搜索二叉树的性质,所以先从查找入手。

在这里插入图片描述

以上图为例,倘若我们要查找 7,具体的思路是这样的——

  1. 7 < 8,因此去 8 的左子树去查找;
  2. 7 > 3,因此去 3 的右子树去查找;
  3. 7 > 6,因此去 6 的右子树去查找;
  4. 7 = 7,找到了,返回true

于是我们试着着手实现一个Find函数。

bool Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key < key) // 大于则去右子树查找
			cur = cur->_right;
		else if (cur->_key > key) // 小于则去左子树查找
			cur = cur->_left;
		else
			return true; // 找到返回true
	}
	return false; // 未找到返回false
}

🌺插入操作

理解了如何查找,插入也就非常简单。
在这里插入图片描述
还是以此图为例,倘若我们要插入 9 ,具体步骤为——

  1. 首先确定cur的位置,并随时更新parent
  2. 最终,cur走到10的左节点的位置,即cur = nullptr,循环结束;
  3. 此时patent = Node*(10)
  4. 最后一步,new一个结点Node*(key)并赋值给parent->_left即可。
bool Insert(const K& key)
{
	// 如果是第一次插入,直接new一个新结点给_root
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}
	
	Node* cur = _root; // cur用来定位插入的位置
	Node* parent = cur; // 记录parent的父亲
	
	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 = new Node(key);
	// 插入时依旧要进行判断
	if (parent->_key < key)
		parent->_right = cur;
	else
		parent->_left = cur;
	return true;
}

🌺删除操作

二叉搜索树的删除是最精华的部分。对与叶子节点,例如4、7、13,删除非常简单,只需将自身的位置替换为nullptr即可。

在这里插入图片描述
如果要删除14或者10,也是比较简单的,因为10的左右子树只有一方为nullptr(10的左子树为空),所以只需要载删除的时候让父结点接管自己不为空的子树即可。

倘若要删除6或者3,由于它们的左右子树都不为空,删除时无法将两个子树都交给父结点,情况就较为复杂。

所以此种情况,我们只能想办法请一个人来接替自己的位置,但是并不是谁来都能胜任这个位置的。这个接替者必须满足二叉搜索树的条件——左子树都比它小,右子树都比它大。那么这个接替者的人选只能有这两个——

  • 左子树的最大(最右)节点;
  • 或右子树的最小(最左)节点;

例如,倘若要删除3,此时有两种做法都可行——

  1. 1替换3
  2. 7替换3

综上所述,删除操作共分为一下几种情况——

  1. 左子树为空;
  2. 右子树为空;
  3. 左右子树都不为空 ;
  4. (左右子树都为空其实可以归并到1或2的情况中);
bool Erase(const K& key)
{
	Node* cur = _root;
	Node* parent = cur;
	while (cur)
	{
		// 找到值为key的结点
		if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else // 找到了
		{	
			// 删除
			if (cur->_left == nullptr) // 1.左子树为空
			{
				if (cur == _root) // 根节点的删除
				{
					_root = cur->_right;
					return true;
				}
				else
				{
					if (parent->_left == cur)
						parent->_left = cur->_right;
					else
						parent->_right = cur->_right;
					delete cur;
				}
			}
			else if (cur->_right == nullptr) // 2.右子树为空
			{
				if (cur == _root) // 根节点的删除
				{
					_root = cur->_left;
					return true;
				}
				else
				{
					if (parent->_left == cur)
						parent->_left = cur->_left;
					else
						parent->_right = cur->_left;
					delete cur;
				}
			}
			else // 左右子树都不为空
			{
				// 找左子树的最大结点 或者 右子树的最小结点
				Node* minRight = cur->_right;
				Node* pminRight = cur;

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

				cur->_key = minRight->_key; // 替换
				
				if (pminRight->_left == minRight)
				{
					pminRight->_left = minRight->_right;
				}
				else
				{
					pminRight->_right = minRight->_right;
				}

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

🌺遍历操作

最后,二叉搜索树的遍历非常简单,就是之前学习过的二叉树的中序遍历

void InOrder()
{
	_InOrder(_root);
	cout << endl;
}

void _InOrder(Node* root)
{
	if (root == nullptr)
		return;
	_InOrder(root->_left);
	cout << root->_key << ' ';
	_InOrder(root->_right);
}

注:由于调用函数时C++封装的特性,需设计两个函数,InOrder接口对外提供,-—_InOrder不对外提供。

☘️测试

在这里插入图片描述

我们可以构建一棵这样的搜索二叉树,再对每一个结点进行删除操作,验证代码是否正确~

void BTreeTest()
{
	BSTree<int> tree;
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	for (auto e : a)
	{
		tree.InsertR(e);
	}

	for (auto e : a)
	{
		tree.EraseR(e);
		tree.InOrder();
	}
}

在这里插入图片描述

🏵️拓展——递归实现

对于搜索二叉树来说,上面实现的非递归版本是比递归版本更优的。此处的递归实现完全属于多余了,但是作为拓展内容看一看也未尝不可。

🍃递归查找

bool FindR(const K& key)
{
	return _FindR(_root, key);
}

bool _FindR(Node* root, const K& key)
{
	if (root == nullptr)
		return false;
	if (root->_key == key)
		return true;
	if (root->_key > key)
		_FindR(root->_left, key);
	else
		_FindR(root->_right, key);
}

🍃递归插入

bool EraseR(const K& key)
{
	return _EraseR(_root, key);
}

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 EraseR(const K& key)
{
	return _EraseR(_root, key);
}

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
	{
		Node* del = root;
		if (root->_left == nullptr)
			root = root->_right;
		else if (root->_right == nullptr)
			root = root->_left;
		else
		{
			Node* maxLeft = root->_left;
			while (maxLeft->_right)
				maxLeft = maxLeft->_right;
			std::swap(root->_key, maxLeft->_key);
			return _EraseR(root->_left, key);
		}
		delete del;
		return true;
	}
}

❄️完整源码

🐙非递归版

#include<iostream>
using namespace std;

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

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

template <class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	BSTree() = default;
	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}

		Node* cur = _root;
		Node* parent = cur;

		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 = new Node(key);
		if (parent->_key < key)
			parent->_right = cur;
		else
			parent->_left = cur;
		return true;
	}

	bool Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
				cur = cur->_right;
			else if (cur->_key > key)
				cur = cur->_left;
			else
				return true;
		}
		return false;
	}
	bool Erase(const K& key)
	{
		Node* cur = _root;
		Node* parent = cur;
		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				// 删除
				if (cur->_left == nullptr)
				{
					if (cur == _root)
					{
						_root = cur->_right;
						return true;
					}
					else
					{
						if (parent->_left == cur)
							parent->_left = cur->_right;
						else
							parent->_right = cur->_right;
						delete cur;
					}
				}
				else if (cur->_right == nullptr)
				{
					if (cur == _root)
					{
						_root = cur->_left;
						return true;
					}
					else
					{
						if (parent->_left == cur)
							parent->_left = cur->_left;
						else
							parent->_right = cur->_left;
						delete cur;
					}
				}
				else
				{
					// 找左子树的最大结点 或者 右子树的最小结点
					Node* minRight = cur->_right;
					Node* pminRight = cur;

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

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

					delete minRight;
				}
				return true;
			}
		}
		return false;
	}
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
protected:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_key << ' ';
		_InOrder(root->_right);
	}
private:
	Node* _root = nullptr;
};

🐌递归版本

#pragma once
#include<iostream>
using namespace std;

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

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

template <class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	BSTree() = default;
	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);
	}
protected:
	bool _FindR(Node* root, const K& key)
	{
		if (root == nullptr)
			return false;
		if (root->_key == key)
			return true;
		if (root->_key > key)
			_FindR(root->_left, key);
		else
			_FindR(root->_right, key);
	}
	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
		{
			Node* del = root;
			if (root->_left == nullptr)
				root = root->_right;
			else if (root->_right == nullptr)
				root = root->_left;
			else
			{
				Node* maxLeft = root->_left;
				while (maxLeft->_right)
					maxLeft = maxLeft->_right;
				std::swap(root->_key, maxLeft->_key);
				return _EraseR(root->_left, key);
			}
			delete del;
			return true;
		}
	}

	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;
	}
	
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_key << ' ';
		_InOrder(root->_right);
	}
private:
	Node* _root = nullptr;
};

在这里插入图片描述

⭐抽奖活动⭐

抽奖文章链接——Spring Cloud——演进与应用的分布式系统开发利器(文末赠书3册)

⭐感谢赞助⭐

618,清华社 IT BOOK 多得图书活动开始啦!活动时间为2023年6月7日至6月18日,清华社为您精选多款高分好书,涵盖了C++、Java、Python、前端、后端、数据库、算法与机器学习等多个IT开发领域,适合不同层次的读者。全场5折,扫码领券更有优惠哦!

优惠购书请戳这里

在这里插入图片描述

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

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

相关文章

数据结构与算法·第9章【查找】

概念 关键字&#xff1a; 是数据元素&#xff08;或记录&#xff09;中某个数据项的值&#xff0c;用以标识&#xff08;识别&#xff09;一个数据元素&#xff08;或记录&#xff09;。若此关键字可以识别唯一的一个记录&#xff0c;则称之谓“主关键字”。若此关键字能识别…

2.3 利用MyBatis实现关联查询

一、创建数据库表 1、创建教师表 执行SQL语句&#xff0c;创建教师表t_teacher CREATE TABLE t_teacher ( t_id int(11) NOT NULL AUTO_INCREMENT, t_name varchar(20) DEFAULT NULL, PRIMARY KEY (t_id) ) ENGINEInnoDB AUTO_INCREMENT4 DEFAULT CHARSETutf8mb4;执行SQL语句…

使用OpenFlow和Ryu控制器实现网络交换机的软件定义网络(SDN)控制

使用OpenFlow和Ryu控制器实现网络交换机的软件定义网络&#xff08;SDN&#xff09;控制 &#xff08;1&#xff09;环境介绍 硬件环境&#xff1a;系统最低要求为2个CPU 、2 GB内存。 拓扑介绍&#xff1a;云平台具体安装拓扑如图5-4所示。 图5-4 云平台安装拓扑 搭建云平…

使用pipreqs生成requirements文件,并在服务器(矩池云)上通过requirements文件安装环境采坑记录

目录 问题描述问题1&#xff1a;问题2&#xff1a;发现问题问题解决 问题3&#xff1a;问题4&#xff1a;问题5&#xff1a;解决方案 关键&#xff01;&#xff01;&#xff01;正常安装成功的操作流程备注1.我为何不在vscode的终端中装pipreqs包&#xff1f;2.在vscode终端中输…

Spring Cloud构建微服务架构:服务注册与发现

Spring Cloud简介 Spring Cloud是一个基于Spring Boot实现的云应用开发工具&#xff0c;它为基于JVM的云应用开发中的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。 Spring Cloud包…

面了一个来华为要22K的人,啥都不会,还不如找个应届生来代替···

最近有个在华为的朋友跟我分享了他面试招人的过程&#xff0c;感觉华为还是挺难进的。面试前后进行了20天左右&#xff0c;包含4轮电话面试、1轮笔试、1轮主管视频面试、1轮hr视频面试。 据他所说&#xff0c;80%的人都会栽在第一轮面试。 其实&#xff0c;第一轮的电话面试除…

ASEMI代理英飞凌TLE7244SL功率电子开关,TLE7244SL参数

编辑-Z TLE7244SL参数描述&#xff1a; 型号&#xff1a;TLE7244SL 数字电源电压VDD&#xff1a;3.0 V ~ 5.5 V 模拟电源电压VDDA&#xff1a;4.5 V ~ 5.5 V 每个通道在Tj150C时的最大导通状态电阻RDS(ON,max)&#xff1a;1.7 Ω 额定负载电流IL (nom)&#xff1a;290 mA…

Nginx【反向代理负载均衡动静分离】--中

Nginx【反向代理负载均衡动静分离】–中 负载均衡-配置实例 示意图 负载均衡配置-思路分析/图解 示意图 负载均衡配置规则 负载均衡就是将负载分摊到不同的服务单元&#xff0c;既保证服务的可用性&#xff0c;又保证响应足够快 linux 下有Nginx、LVS、Haproxy 等等服务可…

在Apifox中,使用后置脚本显示响应结果reponse中的base64图片

背景 在使用Apifox去请求有图片的接口时&#xff0c;我想要请求成功的同时&#xff0c;可以显示出来图片&#xff0c;这个时候就开始百度找官方文档。最终发现可以使用后置脚本显示reponse中的图片。 方案 如下图所示&#xff0c;接口请求成功后&#xff0c;返回的json结构为…

【Spring Boot 初识丨四】主应用类

上一篇讲了 Spring Boot 的启动器 本篇来讲一讲 主程序类 Main Application Class 及注解 Spring Boot 初识&#xff1a; 【Spring Boot 初识丨一】入门实战 【Spring Boot 初识丨二】maven 【Spring Boot 初识丨三】starter 主程序类 一、定义二、注解2.1 SpringBootApplicati…

秋招指南(菜狗版)-Java前/后端开发方向

期末考试结束&#xff0c;菜的人还在享受假期&#xff0c;即将进大厂的已经在学习了&#xff08;狗头&#xff09; 作为经受去年秋招摧残的老学姐&#xff0c;给大家带来一些秋招学习的小经验&#xff0c;希望可以帮助大家避免一些求职路上的坑&#xff0c;能快速顺利地找到心仪…

论文笔记与实战:对比学习方法MOCO

目录 1. 什么是MOCO2. MOCO是干吗用的3. MOCO的工作原理3.1 一些概念1. 无监督与有监督的区别2. 什么是对比学习3. 动量是什么 3.2 MOCO工作原理1. 字典查找2. 如何构建一个好的字典3. 工作流程 3.3 &#xff08;伪&#xff09;代码分析 4. 其他一些问题5. MOCO v2和MOCO v35.1…

Nginx【反向代理负载均衡动静分离】--下

Nginx【反向代理负载均衡动静分离】–下 Nginx 工作机制&参数设置 master-worker 机制 示意图 图解 一个master 管理多个worker 一说master-worker 机制 ● 争抢机制示意图 图解 一个master Process 管理多个worker process, 也就是说Nginx 采用的是多进程结构, 而…

字节8年经验总结:13 条自动化测试框架设计原则(建议收藏)

1.代码规范 测试框架随着业务推进&#xff0c;必然会涉及代码的二次开发&#xff0c;所以代码编写应符合通用规范&#xff0c;代码命名符合业界标准&#xff0c;并且代码层次清晰。特别在大型项目、多人协作型项目中&#xff0c;如果代码没有良好的规范&#xff0c;那么整个框…

leetcode109. 有序链表转换二叉搜索树(java)

有序链表转换二叉平衡搜索树 leetcode109. 有序链表转换二叉搜索树题目描述 解题思路代码演示链表和二叉树专题 leetcode109. 有序链表转换二叉搜索树 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/convert-sorted-lis…

QCameraViewfinder的使用

目录 引言核心代码完整代码 引言 本文是简单使用Qt快速使用摄像头完成截图等工作&#xff0c;主要涉及QCamera、QCameraViewfinder、QCameraImageCapture这三个类。QCamera通过相机的信息创建&#xff0c;用于控制开始接收图形、停止接收图像。QCameraViewfinder则是图像的展示…

(1Gbit)MT28EW01GABA1LPC-0SIT、MT28EW01GABA1HPC-0SIT FLASH - NOR 存储器

MT28EW01GABA1LPC-0SIT、MT28EW01GABA1HPC-0SIT 1Gbit并行NOR闪存器件具有较高的密度、就地执行 (XiP) 性能和架构灵活性&#xff0c;可满足汽车、消费类和移动产品的设计要求。该器件非常适合用于GPS/导航、汽车后视摄像头、手机、智能手机和电子阅读器。该器件还具有较宽的温…

【小沐学Python】Python实现turtle绘画

文章目录 1、简介2、接口说明2.1 海龟动作2.1.1 移动和绘制2.1.2 获取海龟的状态 2.2 画笔控制2.2.1 绘图状态2.2.2 颜色控制2.2.3 填充2.2.4 更多绘图控制 2.3 TurtleScreen/Screen 方法2.3.1 窗口控制2.3.2 使用屏幕事件2.3.3 Screen 专有方法 3、示例测试3.1 Turtle star3.2…

关于proxy的较深入研究

关于proxy的较深入研究 proxy是什么控制台打印proxy&#xff0c;展示一下vue3中的prxoy和es6的proxy有何不同&#xff1f;proxy对js和vue3来说有什么含义&#xff1f;proxy的封装机制 proxy是什么 Proxy是ES6中新增的一个对象&#xff0c;它可以用来代理另一个对象&#xff0c…

微服务eureka和nacos

服务远程调用 /*** 创建RestTemplate并注入Spring容器* return*/Beanpublic RestTemplate restTemplate(){return new RestTemplate();} Autowiredprivate RestTemplate restTemplate;public Order queryOrderById(Long orderId) {// 1.查询订单Order order orderMapper.fin…