〖数据结构〗一棵有点自律的树——搜索二叉树

news2024/11/25 23:03:19

在这里插入图片描述

文章目录

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

💐专栏导读

🌸作者简介:花想云,在读本科生一枚,致力于 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/643981.html

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

相关文章

MATLAB与物联网:如何应用MATLAB进行物联网数据的处理和分析

第一章&#xff1a;引言 物联网&#xff08;Internet of Things, IoT&#xff09;作为当今科技领域的热门话题&#xff0c;正在改变我们的生活方式和工作方式。随着物联网设备的普及和数据的不断增长&#xff0c;如何高效地处理和分析物联网数据成为了一个重要的挑战。MATLAB作…

【C语言之操作符1】

C语言之操作符1 1. 操作符分类2. 算术操作符3. 移位操作符3.1 左移操作符3.2 右移操作符 4. 位操作符5. 赋值操作符 1. 操作符分类 算术操作符 移位操作符 位操作符 赋值操作符 单目操作符 关系操作符 逻辑操作符 条件操作符 逗号表达式 下标引用、函数调用和结构成员 2. 算术…

【哈佛积极心理学笔记】第14讲 过犹不及

第14讲 过犹不及 Recap: Stress is not the problem. The problem is lack of revovery. level of stress -> chronic stress -> chronic anxiety -> depression Recovery on different level: micro level 15 min break, lunch timemezzo level, full night slee…

1.Apollo开发部署-linux

一.官方文档 https://www.apolloconfig.com/#/zh/deployment/quick-start-docker 二.环境准备 1.MySql 5.6.51.单独服务器192.168.2.13 https://downloads.mysql.com/archives/installer/ 2.JDK 1.8.X https://www.oracle.com/java/technologies/downloads/ 三.Apollo部署…

【JVM篇】垃圾回收算法

目录 1、前言 2、标记-清除算法 3、标记-整理算法 4、标记-复制算法 5、总结 1、前言 说起垃圾回收&#xff08;Garbage Collection&#xff09;&#xff08;本文简称GC&#xff09;。相信同时对C和Java有了解的小伙伴都知道&#xff0c;C在new完对象后&#xff0c;是需要…

【Linux】发展史浅谈

为什么要学Linux&#xff1f; LInux和微软的Windows在定位上都是一样的&#xff0c;即操作系统。而且LInux是一款雄霸服务端的操作系统&#xff0c;是在企业端被高频使用的后台操作系统。 科技发展的基本模式 所有的技术进步&#xff0c;本质都是为了促进更好的工具的发明&a…

【Python ORM】零基础也能轻松掌握的学习路线与参考资料

Python orm&#xff08;Object-Relational Mapping&#xff09;是一种模式&#xff0c;它将对象和关系型数据库之间的映射相互转换&#xff0c;使得应用程序可以通过面向对象的方式来操作数据库。Python orm 在开发大型 Web 应用程序中特别有用&#xff0c;使得与数据库的交互变…

【IMX6ULL驱动开发学习】08.IMX6ULL通过GPIO子系统函数点亮LED

通过GPIO子系统函数点亮LED 1、GPIO子系统函数 1.1 确定 led 的GPIO标号&#xff0c;查看内核中的gpiochip 查看 gpiochip &#xff0c;以正点原子的IMX6ULL阿尔法开发板为例 [root100ask:/sys/class/gpio]# cat /sys/kernel/debug/gpio 查看原理图&#xff0c;发现led接的引…

Linux虚拟网络设备---之使用Veth pair连接linux网桥bridge

本文目录 1、我们可以用以下命令来创建veth pair: veth0----veth12、创建五个命名空间namespaces后&#xff0c;可以用以下命令将veth设备对的一端移入namespaces命名空间&#xff0c;并开启veth2、将veth设备对的另外一端连接到linux网桥&#xff0c;并将所有namespaces命名空…

【Redis应用】UV统计(四)

&#x1f697;Redis应用学习第四站~ &#x1f6a9;本文已收录至专栏&#xff1a;Redis技术学习 一.引入 首先我们要搞懂两个概念&#xff1a; UV&#xff1a;全称Unique Visitor&#xff0c;也叫独立访客量&#xff0c;是指通过互联网访问、浏览这个网页的自然人。1天内同一个…

【动态规划】斐波那契数列模型

冻龟算法系列之斐波那契数列模型 文章目录 【动态规划】斐波那契数列模型1. 第N个泰波那契数1.1 题目解析1.2 算法原理1.2.1 状态表示1.2.2 状态转移方程1.2.3 初始化1.2.4 填表顺序1.2.5 返回值 1.3 编写代码1.4 空间优化 2. 三步问题2.1 题目解析2.2 算法原理2.2.1 状态表示2…

解决Centos安装时找不到磁盘:未选择任何磁盘(no diks selected),本地标准磁盘为空

文章目录 问题描述问题原因解决办法 问题描述 笔者最近又买了一台新电脑&#xff0c;并打算在上面安装 Linux 来充当一个新的服务器结点。但很不幸的是&#xff0c;每次笔者略微尝试新事物时&#xff0c;都要踩很多坑。笔者在使用 U 盘刻录 CentOS 8 镜像之后&#xff0c;准备在…

公平锁/非公平锁/可重入锁/自旋锁

在JAVA中我们知道有很多加锁的方式&#xff0c;比如常见的 通过synchronized关键字&#xff0c;还有Lock&#xff0c;还有之前说原子CAS操作时有看到过的死循环方式的自旋锁。 借此来说一下锁的分类: 公平锁: 是指多个线程按照申请的顺序来获取锁&#xff0c;每次获取锁时会…

dp算法篇Day1

"多希望有人来陪我&#xff0c;度过末日啊~" 讲讲我为什么突然想更新这篇栏目。 想想自己也算 "系统" 接触计算机这个学科也有差不多一年了&#xff0c;回想起当初下定决心要全身心投入到这个专业或者说行业中来&#xff0c;现在到了这样的地步&#xff0c…

CSS基础学习--10 margin(外边距)

一、定义&#xff1a; CSS margin(外边距)属性定义元素周围的空间。 二、margin margin 清除周围的&#xff08;外边框&#xff09;元素区域。margin 没有背景颜色&#xff0c;是完全透明的。 margin 可以单独改变元素的上&#xff0c;下&#xff0c;左&#xff0c;右边距&a…

今天面了个35k字节跳动出来,真是砂纸擦屁股,给我露了一手...

​2023年春招已经结束&#xff0c;很多小伙伴收获不错&#xff0c;拿到了心仪的 offer。 各大论坛和社区里也看见不少小伙伴慷慨地分享了常见的面试题和八股文&#xff0c;为此咱这里也统一做一次大整理和大归类&#xff0c;这也算是划重点了。 俗话说得好&#xff0c;他山之石…

AI 绘画(0):导论

文章目录 导论感谢人员Ai绘画前期准备软件环境硬件条件 Ai绘画介绍Ai绘画简单流程介绍Ai绘画软件介绍参数输入介绍 Ai绘画公约 导论 Ai绘画是最近比较热门的绘画方式&#xff0c;以干掉原画师为口号&#xff0c;引起了激烈的讨论。Ai绘画能否取代人工我们先不谈&#xff0c;但…

一个专科生的 Python 转行之路

自学之路 正式开始学编程是在十月底的样子, 那时候在知乎 flask 话题下看到一个问题 「有多少人按萧井陌大神给出的PythonFlask路线找到工作了&#xff1f;」。就觉得自己也可以啊, 就开始辞职自学 Python,。 刚开始的那一个月确实能够做到每天 10 个小时写代码, 学了大概一个…

java设计模式之:外观模式

前言 举个现实生活中例子&#xff0c;泡茶和去茶馆喝茶的区别&#xff0c;如果是自己泡茶需要自行准备茶叶、茶具和开水&#xff0c;而去茶馆喝茶&#xff0c;最简单的方式就是跟茶馆服务员说想要一杯什么样的茶&#xff0c;是铁观音、碧螺春还是西湖龙井&#xff1f;正因为茶…

互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景

多线程访问共享资源的时候&#xff0c;避免不了资源竞争而导致数据错乱的问题&#xff0c;所以我们通常为了解决这一问题&#xff0c;都会在访问共享资源之前加锁。 最常用的就是互斥锁&#xff0c;当然还有很多种不同的锁&#xff0c;比如自旋锁、读写锁、乐观锁等&#xff0…