【C++】二叉搜索树的实现(递归和非递归实现)

news2024/11/17 2:47:53

文章目录

    • 1、二叉搜索树
      • 1.1 构建二叉搜索树
      • 1.2 二叉搜索树的插入
      • 1.3 二叉搜索树的删除
      • 1.4 二叉搜索树插入和删除的递归实现

为了学习map和set的底层实现,需要知道红黑树,知道红黑树之前需要知道AVL树。
红黑树和AVL树都用到了二叉搜索树结构,所以先谈谈二叉搜索树。

1、二叉搜索树

二叉搜索树(Binary Search Tree)也称二叉排序树,它最重要的是能给数据排序以及去重。
其性质:

  1. 若左子树不为空,左子树的键值都小于根以及右子树。
  2. 若右子树不为空,右子树的键值都大于根以及左子树。
  3. 二叉搜索树的子树都是二叉搜索树。

二叉搜索树顾名思义,根据其特性可以很方便让我们搜索一个值。
二叉树的中序遍历就是一个排序。
二叉搜索树的结点没有相同的值。

在这里插入图片描述

值得注意的是:

  • 二叉搜索树没有要求严格平衡,所以查找一个值的时间复杂度最坏可能是O(N)(成为单枝树,就是一个链表。)
  • 二叉搜索树不支持值修改,因为会打乱树的结构。

1.1 构建二叉搜索树

在二叉树的模型中,有K模型和KV模型,就是一个结点一个值和一个结点一个键值对两个模型。
一个值的很简单,而KV模型就是一个结点存放一个key和一个value。

下面实现的是KV模型的基本框架

#include <iostream>
#include <assert.h>
#include <string>
using namespace std;

template<class K, class V>
struct BSTreeNode
{
	//设置成三叉链的结构,让子树能方便访问根结点
	struct BSTreeNode<K, V>* _left;
	struct BSTreeNode<K, V>* _right;
	struct BSTreeNode<K, V>* _parent;
	K _key;
	V _value;

	//构造
	BSTreeNode(const K& key, const V& value)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _key(key)
		, _value(value)
	{}
};


template<class K, class V>
class BSTree
{
	typedef BSTreeNode<K, V> Node;
public:
private:
	Node* _root = nullptr;
};

1.2 二叉搜索树的插入

二叉树插入很简单。
1、如果树是空,直接创建结点返回。
2、树不为空,根据搜索树的特性通过值的大小确定应该放在左还是右子树,如果到达空结点,那么就到达该放的位置。
3、确认好放的位置,因为需要链接,所以需要有一个parent能指向上一个结点。通过上一个结点和新结点的大小判断应该链接在哪边。
4、因为设计的是三叉链结构,所以最后还得指向父节点。

bool Insert(const K& key, const V& value)
	{	
		//树为空
		if (_root == nullptr)
		{
			_root = new Node(key, value);
			return true;
		}
		
		Node* cur = _root;
		Node* parent = _root;
		//找到新结点应该放的位置
		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, value);
		if (parent->_key < cur->_key)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}


		return true;
	}

1.3 二叉搜索树的删除

在这里插入图片描述
在这里插入图片描述

	bool Erase(const K& key)
	{
		//空树返回
		if (_root == nullptr)
		{
			return false;
		}
		Node* cur = _root;
		Node* parent = _root;
		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 (parent == cur)
					{
						_root = cur->_right;
					}
					else
					{
						//需要确定父节点哪边指向cur
						if (parent->_right == cur)
						{
							parent->_right = cur->_right;
						}
						else
						{
							parent->_left = cur->_right;
						}
					}

					delete cur;
				}
				else if (cur->_right == nullptr)
				{
					//删的结点右为空
					//删的结点为根节点情况
					if (parent == cur)
					{
						_root = cur->_left;
					}
					else
					{
						if (parent->_right == cur)
						{
							parent->_right = cur->_left;
						}
						else
						{
							parent->_left = cur->_left;
						}
					}

					delete cur;
				}
				else
				{
					//左右都不为空,替换右子树最小的
					Node* minRight = cur->_right;
					while (minRight->_left)
					{
						minRight = minRight->_left;
					}

					cur->_key = minRight->_key;
					cur->_value = minRight->_value;


					parent = minRight->_parent;
					//需要确定父节点哪边指向minRight
					if (parent->_right == minRight)
					{
						parent->_right = minRight->_right;
					}
					else
					{
						parent->_left = minRight->_right;
					}
					
					//因为值交换了,所以删除右子树最小结点
					delete minRight;
				} //else
				
				return true;
			} //else
		} // while
		
		return false;
	} //Erase

1.4 二叉搜索树插入和删除的递归实现

有一点必须明确的是,非递归一定是比递归要好的,这里实现递归只是练习,增强代码能力。

首先是InOrder()方法的实现,当调用的方法是不含参数的,实现又需要有参数的,就可以再嵌套一层,并且_InOrder(Node* root)不想提供给类外调用,就可以放在私有域。

...
template<class K, class V>
class BSTree
{
	typedef BSTreeNode<K, V> Node;
public:
	bool Insert(const K& key, const V& value){}
	bool Erase(const K& key){}

	void InOrder()
	{
		_InOrder(_root);
	}
private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		
		_InOrder(root->_left);
		cout << root->_key << ":" << root->_value << endl;
		_InOrder(root->_right);
	}
	
	Node* _root = nullptr;
};

插入的递归实现

插入递归很简单,值得说的是,通过给root添加引用,能很方便的将新结点链接起来。

...
template<class K, class V>
class BSTree
{
	typedef BSTreeNode<K, V> Node;
public:
	...
	bool Insert(const K& key, const V& value)
	{
		return _InsertR(_root, key, value);
	}
	bool Erase(const K& key){}
	...
private:
	...
	bool _InsertR(Node*& root, const K& key, const V& value)
	{
		if (root == nullptr)
		{
			//因为需要对root修改,所以在参数部分需要对root添加引用(Node*& root)
			root = new Node(key, value);
			return true;
		}

		if (root->_key < key)
		{
			_InsertR(root->_right, key, value);
		}
		else if (root->_key > key)
		{
			_InsertR(root->_left, key, value);
		}
		else
		{
			return false;
		}
	}
	Node* _root = nullptr;
};

删除的递归实现

删除的思路整体上和非递归差不多,不同的是。
1、因为删除需要改变树的结构,肯定是要改变每次递归的根节点的,所以需要传引用。
2、删除的思路是和右子树最小结点值交换后,删除最小结点。需要往右找到最小结点。

...
template<class K, class V>
class BSTree
{
	typedef BSTreeNode<K, V> Node;
public:
	bool Erase(const K& key)
	{
		_EraseR(_root, key);
	}
private:
...
	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得加&
				//引用加完后,改变root也代表着改变父结点的指向
				//所以就是父节点指向root的指向变成指向root的右子树
				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);

				// 转换成子树中去删除节点
				// 因为和最小节点的值交换后,原本root的值成了最小值
				// 再凭借key去查找最小值的结点删
				// 最小节点左边一定为空
				_EraseR(root->_right, key);
			}

			delete del;
			return true;
		} //else
	}
	Node* _root = nullptr;
};

本章完~

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

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

相关文章

机器人操作规划——Deep Visual Foresight for Planning Robot Motion(2017 ICRA)

1 简介 model-based RL方法&#xff0c;预测Action对图像的变化&#xff0c;以push任务进行研究。 采用完全自监督的学习方式&#xff0c;不需要相机标定、3D模型、深度图像和物理仿真。 2 数据集 采用几百个物体、10个7dof机械臂采集了包括5万个push attempts的数据集。 每…

【软件测试】测试工程师的等级划分(初/中/高/专家),你的晋升之路......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 新手&#xff08;测…

linux下实现Nginx + consul + upsync 完成动态负载均衡

一、yum安装consul #安装yum-utils yum install -y yum-utils#配置consul的下载仓库 yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo#必须上面步骤&#xff0c;不然会找不到仓库 yum -y install consul#查看版本 consul -v 二、启动…

计算机SCI论文课题设计需要注意什么? - 易智编译EaseEditing

课题设计就要本着严谨性和可行性来进行。实验设计的类型要选择准确&#xff0c;统计学的方法要运用合理&#xff0c;研究对象和观察指标的选择也要符合研究目的的要求&#xff0c;技术路线要清晰明了。 关于课题的设计的可行性也要综合考虑&#xff0c;比如前期的相关工作基础…

一文详解Redis持久化的两种方案

一文详解Redis持久化的两种方案1.RDB持久化2.RDB持久化原理3.AOF持久化4.RDB VS AOF1.RDB持久化 RDB全称Redis Database Backup file(Redis数据备份文件)&#xff0c;也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后&#xff0c…

分享107个HTML电子商务模板,总有一款适合您

分享107个HTML电子商务模板&#xff0c;总有一款适合您 107个HTML电子商务模板下载链接&#xff1a;https://pan.baidu.com/s/1VW67Wjso1BRpH7O3IlbZwg?pwd0d4s 提取码&#xff1a;0d4s Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 Aplustemplates 购物模板…

Redis之集群搭建

redis的集群模式简介&#xff1a; redis的集群模式中可以实现多个节点同时提供写操作&#xff0c;redis集群模式采用无中心结构&#xff0c;每个节点都保存数据&#xff0c;节点之间互相连接从而知道整个集群状态。 集群搭建步骤如下 (一台服务器模拟多台服务器) 1.创建6个配置…

关于使用CMT2300A FIFO缓存区间设置为64Byte的问题

首先请看&#xff0c;CMT2300A 是什么产品&#xff0c;或者说是 模组吗&#xff1f; 请看介绍&#xff1a; https://blog.csdn.net/sishuihuahua/article/details/105095994 以及RFPDK 的使用: 这博客&#xff0c;记录了 RFPDK 的使用,以及遇到的一些问题 我说一下&#…

Windows瘦身方法

一、快速删除系统盘临时文件方法, 1、winr打开运行对话框&#xff0c;输入%temp%命令&#xff0c;如图1 图1 2、打开temp文件夹&#xff0c;如图2&#xff0c;选择所有文件&#xff0c;鼠标右键删除或按Del键删除。 图2 二、磁盘清理 1、winr&#xff0c;输入cleanmgr&#x…

重生之我是赏金猎人-SRC漏洞挖掘(十二)-记一次对抗飞塔流量检测的文件上传

0x00 前言 https://github.com/J0o1ey/BountyHunterInChina 欢迎亲们点个star 0x01 起因 某项目靶标&#xff0c;是一个人员管理系统&#xff0c;通过webpack暴露的接口 我们成功找到了一个未鉴权的密码修改接口&#xff0c;通过fuzz 我们获取到了该接口的参数username与…

干了1年“点点点”,自己辞职了,下一步是继续干测试还是转开发?

最后后台有个粉丝向我吐槽&#xff0c;不知道怎么选择了....下面就他的情况说说怎么选择&#xff1f; 目前已经提桶跑路&#xff0c;在大工厂里混了半年初级低级功能测试经验&#xff0c;并没有什么用。测试培训班来的。从破山村贫困户贫困专项出去的&#xff0c;学校上海的。…

基于RK3588的嵌入式linux系统开发(二)——uboot源码移植及编译

由于官方的SDK占用空间较大&#xff08;大约20GB左右&#xff09;&#xff0c;需要联系相关供应商提供&#xff0c;且官方的SDK通过各种脚本文件进行集成编译&#xff0c;难以理解系统开发的详细过程。本章介绍直接从官方Github网站下载源码进行移植&#xff0c;进行uboot移植及…

动态规划【Day02】

动态规划初探坐标型动态规划115 不同的路径 II序列型动态规划515 房屋染色划分型动态规划题目坐标型动态规划 115 不同的路径 II 题目链接 题目描述&#xff1a; “不同的路径” 的跟进问题&#xff1a; 有一个机器人位于一个 mn 网格左上角。 机器人每一时刻只能向下或…

分布式入门

目录 一、RPC 1.1 RPC调用流程 二、SOA 三、微服务 3.1.什么是微服务 3.2.微服务与微服务架构 1 &#xff09;微服务架构 2&#xff09; 微服务 3.3.微服务的优缺点 3.1 微服务的优点 3.2 微服务的缺点 四.微服务的技术栈有哪些 一、RPC RPC&#xff08;Remote Pro…

Linux磁盘查看,使用(分区、格式化、挂载)

目录 0、观察磁盘分区状态&#xff1a;lsblk、blkid、parted 0.1 lsblk列出系统上的所有磁盘列表 0.2 blkid列出设备的UUID等参数 0.3 parted列出磁盘的分区表类型与分区信息 1、磁盘分区&#xff1a;gdisk、fdisk 1.1 fdisk 2、磁盘格式化&#xff08;创建文件系统…

Redis高级:数据结构

动态字符串SDS Redis保存的Key是字符串&#xff0c;value往往是字符串或者字符串的集合。可见字符串是Redis中最常用的一种数据结构。不过Redis没有直接使用C语言中的字符串&#xff0c;因为C语言字符串存在很多问题&#xff1a; 获取字符串长度需要进行一个运算非二进制安全…

【Unity VR开发】结合VRTK4.0:其他类抓取

语录&#xff1a; 一个正确的认识&#xff0c;往往需要经过由物质到精神&#xff0c;由精神到物质&#xff0c;即由实践到认识&#xff0c;由认识到实践这样多次的反复&#xff0c;才能够完成。 前言&#xff1a; 前面我们简单的使用了Interactions.Interactable的抓取。今天…

Ant Design Chart词云图

什么是词云图&#xff1f;词云图&#xff0c;也叫文字云&#xff0c;是对网络文本中出现频率较高的“关键词”予以视觉上的突出&#xff0c;出现越多&#xff0c;显示的字体越大&#xff0c;越突出&#xff0c;这个关键词也就越重要。让浏览者通过词云图一眼就可以快速感知最突…

Java_Maven:3. 分模块构建工程

目录 1 需求 2 案例实现 2.1 maven-parent 父模块 2.1.1 创建父工程 2.1.2 定义 pom.xml 2.1.3 将父工程发布至仓库 2.2 ssm_dao 子模块 2.2.1 创建 dao 子模块 2.2.2 定义 pom.xml 2.2.3 dao 代码 2.2.4 配置文件 2.2.5 单元测试 2.2.6 把 dao 模块 install 到本…

MySQL的索引视图练习题

学生表&#xff1a;Student (Sno, Sname, Ssex , Sage, Sdept) 学号&#xff0c;姓名&#xff0c;性别&#xff0c;年龄&#xff0c;所在系 Sno为主键 课程表&#xff1a;Course (Cno, Cname,) 课程号&#xff0c;课程名 Cno为主键 学生选课表&#xff1a;SC (Sno, Cno, Score)…