[数据结构] 二叉搜索树的详解实现

news2024/9/24 1:21:11

文章目录

  • 概念
  • 实现
    • 架构
    • BSTreeNodea(节点)
    • BSTree
      • 框架
    • 增删查 -- 循环写法
      • insert(尾插)
      • inOrder(遍历)
      • Find(查找)
      • Erase(删除)
      • 默认成员函数
        • 构造
        • 拷贝构造
        • 析构函数
        • 赋值运算符重载
      • 增删查 -- 递归写法
      • _InsertR(递归尾插)
      • _FindR(查)
      • _EraseR(删除)

概念

二叉搜索树(Binary Search Tree,BST),也称为二叉查找树或二叉排序树,是一种特殊的二叉树,其中节点的值按照一定规律排列。具体来说 ,对于任意一个节点,其左子树的节点键值均小于根节点的键值;右子树的节点键值均大于根节点的键值。

即满足以下性质:

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

在这里插入图片描述




实现

架构

这里将二叉搜索树要实现的大致内容贴出;

#pragma once

namespace aiyimu
{
	template<class K>
	// 二叉树节点
	struct BSTreeNode
	{};

	template<class K>
	class BSTree
	{
		typedef BSTreeNode<K> Node;
	public:
		// 尾插
		bool insert(const K& key)
		{}

		// 中序遍历
		void inOrder()
		{}

		bool Find(const K& key)
		{}

		// 删除key
		bool Erase(const K& key)
		{}

		// ---------------------------------------- 递归写法 ---------------------------------------
		// 查找
		bool FindR(const K& key)
		{}

		bool InsertR(const K& key)
		{}

		bool EraseR(const K& key)
		{}


		// 默认成员函数
		// 构造
		BSTree() = default;//C++: 强制编译器生成默认构造

		// 拷贝构造
		BSTree(const BSTree<K>& bst)
		{}

		// 析构
		~BSTree()
		{}

		// 赋值重载
		BSTree<K>& operator=(BSTree<K> bst)
		{}

		// 不能访问_root
	private:
		// 拷贝
		Node* _Copy(Node* root)
		{}

		// 销毁BST
		void _Destory(Node*& root)
		{}

		bool _FindR(Node* root, const K& key)
		{}

		bool _InsertR(Node* root, const K& key)
		{}

		bool _EraseR(Node* root, const K& key)
		{}

		void _inOrder(Node* root)
		{}
	private:
		Node* _root = nullptr;
	};
}



BSTreeNodea(节点)

template<class K>
// 二叉树节点
struct BSTreeNode
{
	// 成员变量
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;


	// 构造函数
	BSTreeNode(const K& key)
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
	{}
};
  • _left 表示指向当前节点的左子节点的指针;
  • _right 表示指向当前节点的右子节点的指针;
  • _key 表示当前节点所存储的键值(key),即二叉搜索树中的排序关键字。

_left 和 _right 成员变量的类型均为 BSTreeNode*,即指向某个类型为 K 的节点的指针。这是因为二叉树节点本身也是一种自定义数据类型,其成员变量也可以是指向其他节点的指针。

构造函数

  1. 首先将 _left 和 _right 成员变量初始化为 nullptr,表示当前节点暂时没有左子节点和右子节点。
  2. 然后将 _key 成员变量初始化为传入的参数 key,表示当前节点存储的键值就是 key。


BSTree

框架

增删查 – 循环写法

insert(尾插)

在这里插入图片描述

  1. 插入时如果此时无节点,直接创建新节点作为_root,返回true
  2. 首先查找key值需要插入的位置,如果key值大于节点值,向右继续查找;如果key值小于节点值,向左继续查找
  3. 如果插入的key值已存在,则直接返回false(二叉搜索树没有重复的元素,这一步去重),最后的cur即为待插入的位置
  4. 当找到要插入的cur时,最后将key作为cur的子节点插入
  5. 具体代码都有相应的注释做解释
// 尾插
bool insert(const K& key)
{
	// 如果此时无节点,直接创建新节点作为_root,返回true
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}

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

	// 查找要插入的位置
	while (cur)
	{
		// 如果key大,则向右插入
		// 反之key小向左插入
		if (key > cur->_key)
		{
			//parent存cur:代表改变后的cur的父节点
			parent = cur;
			cur = cur->_right;
		}
		else if (key < cur->_key)
		{
			parent = cur;
			cur = cur->_left;
		}
		// 元素重复则返回false
		else
		{
			return false;
		}
	}
	
	// 此时找到了待插入的位置cur
	// 将key插入作为cur的左/右子节点
	
	//创建节点
	cur = new Node(key);
	if (key > parent->_key)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	return true;	//插入成功返回true
}

inOrder(遍历)

在上文框架重可以知道,inOrder()是public访问,而_inOrder()是private访问

好处:

  1. 封装性: 将具体实现细节封装在私有函数中,可以保证外部用户无法直接访问和修改数据结构,提高程序的封装性和安全性。
  2. 灵活性: 将遍历操作和遍历入口分离开来,可以在不暴露内部实现的前提下对外提供更多的功能接口,比如后序遍历、层序遍历等。
  3. 可读性: 通过将不同的功能或逻辑分别实现在不同的函数中,可以大大提高代码的可读性和可维护性。
  • 在inOrder()中,直接调用_inOrder()并传入_root成员函数
public:
void inOrder()
{
	_inOrder(_root);
	cout << endl;
}
  • 中序遍历符合 左 中 右 的顺序,当root为空时返回,按照顺序递归即可
private:
void _inOrder(Node* root)
{
	if (root == nullptr)
	{
		return;
	}

	_inOrder(root->_left);
	cout << root->_key << " ";
	_inOrder(root->_right);
}

Find(查找)

  1. 查找所执行的操作和插入时一致,遍历二叉搜索树,如果key大向右找,key小想左找即可。
bool Find(const K& key)
{
	Node* cur = _root;

	// 遍历二叉搜索树查找key
	while (cur)
	{
		// 如果key大,则向右找
		if (key > cur->_key)
		{
			cur = cur->_right;
		}
		// key小,向左找
		else if (key < cur->_key)
		{
			cur = cur->_left;
		}
		// 找到了
		else
		{
			return true;
		}
	}
	// 退出循环,找不到
	return false;
}

Erase(删除)

首先查找待删除的节点,如果找不到,直接返回false
在这里插入图片描述

找到节点后,执行删除操作,一共有三种情况:

  1. 待删除节点左树为空
  2. 待删除节点右树为空
  3. 待删除节点左右都不为空

下图做解释:

当左树为空待删除的节点是根节点

在这里插入图片描述

当左树为空待删除的节点不是根节点

在这里插入图片描述

当右树为空

当右树为空的操作思路和左树为空时一致,按照左树的思路写即可。

当左右树都不为空

在这里插入图片描述

另外:在这段代码中,由于是在查找 cur 节点并删除其子树的过程中,无论 cur 是否是根节点都不会影响查找和删除的过程。

bool Erase(const K& key)
{
	Node* parent = nullptr;
	Node* cur = _root;

	while (cur)
	{
		// 找到要删除的节点
		if (key > cur->_key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (key < cur->_key)
		{
			parent = cur;
			cur = cur->_left;
		}
		// 执行删除操作
		else
		{
			// 三种情况
			// 1、左为空
			// 2、右为空
			// 3、左右都不为空
			
			// 1. 左为空时
			if (cur->_left == nullptr)
			{
				// 当要删除的为根时,parent会出问题,单独写这种情况
				if (cur == _root)
				{
					_root = cur->_right; // 直接将根改为右节点(左为空)
				}
				// 判断 cur 在 parent 的左侧还是右侧,然后将 parent 对应的子节点指向 cur 的右子节点。
				else
				{
					if (cur == parent->_left)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
				}

				// 清理掉 cur 的内存空间
				delete cur;
				cur = nullptr;
			}
			// 2. 右为空时
			else if (cur->_right == nullptr)
			{
				if (cur == _root)
				{
					_root = cur->_left;
				}
				else
				{
					if (cur == parent->_left)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
				}

				delete cur;
				cur = nullptr;
			}
			// 3. 左右都不为空
			else
			{
				// 找到右子树的最小节点替换
				Node* minParent = cur;
				Node* min = cur->_right;

				while (min->_left)
				{
					minParent = min;
					min = min->_left;
				}

				swap(cur->_key, min->_key);
				if (minParent->_left == min)
					minParent->_left = min->_right;
				else
					minParent->_right = min->_right;

				delete min;
			}
			return true;
		}
	}

	return false;
}


默认成员函数

构造

这里使用了 C++11 中的新特性:= default 它表示对于该函数,我们采用编译器默认生成的实现方式,而不需要手写构造函数的实现代码。

直接用默认构造进行成员变量的初始化

BSTree() = default;//C++: 强制编译器生成默认构造

拷贝构造

拷贝构造依然调用_Copy()私有函数,传入bst._root

BSTree(const BSTree<K>& bst)
{
	_root = _Copy(bst._root);
}

_Copy()

  1. 如果root为空,直接返回空
  2. 先创建新节点拷贝root的key值
  3. 然后拷贝左右指针,最后返回新节点
// 拷贝
Node* _Copy(Node* root)
{
	// root为空返回空
	if (root == nullptr)
	{
		return nullptr;
	}

	// 拷贝节点和指向关系
	Node* copyRoot = new Node(root->_key);
	copyRoot->_left = _Copy(root->_left);
	copyRoot->_right = _Copy(root->_right);

	return copyRoot;
}

析构函数

  • 同理拷贝构造,共有函数调用_Destory(),传入_root
public:
~BSTree()
{
	_Destory(_root);
}
  • _Destory()利用递归删除左右子树,最后销毁根节点
private:
void _Destory(Node*& root)
{
	if (root == nullptr)
		return;

	// 递归销毁根节点所有左右节点
	_Destory(root->_left);
	_Destory(root->_right);
	// 销毁根节点
	delete root;
	root = nullptr;
}

赋值运算符重载

函数体中调用了 swap 函数,将当前对象的 根节点 _root 和新建的对象 bst 的根节点 bst._root 进行交换 ,从而实现了二叉搜索树的值拷贝和赋值操作

BSTree<K>& operator=(BSTree<K> bst)
{
	swap(_root, bst._root); // 交换根节点
	return *this;
}

增删查 – 递归写法

_InsertR(递归尾插)

  • 首先判断 root 是否为空,如果为空,则直接创建一个新的节点并将 key 值赋值给新节点的键值 _key,然后将新节点作为根节点返回,表示插入操作成功。
  • 若root不为空则查找要插入的位置,若key值大则向右递归key值小则向左递归
  • 如果key值等于某一个节点的值,不再插入,返回false
bool _InsertR(Node* root, const K& key)
{
	// 为空,直接创建新节点
	if (root == nullptr)
	{
		root = new Node(key);
		return true;
	}

	// 查找位置并插入节点
	if (key > root->_key)
	{
		return _InsertR(root->_right, key);
	}
	else if (key < root->_key)
	{
		return _InsertR(root->_left, key);
	}
	else 
		return false;
}

_FindR(查)

相同的思路,递归查找

bool _FindR(Node* root, const K& key)
{
	// 遇空返回
	if (root == nullptr)
	{
		return;
	}

	// 如果key大向右找
	if (key > root->_key)
		return _FindR(root->_right, key);
	else if (key < root->_key)
		return _FindR(root->_left, key);
	else
		return true;
}

_EraseR(删除)

bool _EraseR(Node* root, const K& key)
{
	// 为空返回false
	if (root == nullptr)
	{
		return false;
	}

	// 寻找要删除的节点
	if (key > root->_key)
	{
		return _EraseR(root->_right, key);
	}
	else if (key < root->_left)
	{
		return _EraseR(root->_left, key);
	}
	else
	{
		// 执行删除操作
		Node* del = root;
		if (root->_left == nullptr)
		{
			root = root->_right;
		}
		else if (root->_right == nullptr)
		{
			root = root->_right;
		}
		else
		{
			// 找到右树的最 小/左 节点并替换
			Node* min = root->_right;
			while (min->_left)
			{
				min = min->_left;
			}
			swap(root->_key, min->_key);
			return _EraseR(root->_right, key);
		}
		// 删除节点
		delete del;
		return true;
	}
}

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

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

相关文章

哈夫曼编码文件压缩和解压

哈夫曼编码&文件压缩和解压 文章目录 哈夫曼编码&文件压缩和解压哈夫曼编码基本介绍原理解析代码实现 文件的压缩文件的解压完整代码 哈夫曼编码 基本介绍 赫夫曼编码也翻译为 哈夫曼编码(Huffman Coding)&#xff0c;又称霍夫曼编码&#xff0c;是一种编码方式, 属于…

实现c++轻量级别websocket协议客户端

1 websocket 轻量客户端 因以前发过这个代码&#xff0c;但是一直没有整理&#xff0c;这次整理了一下&#xff0c;持续修改&#xff0c;主要是要使用在arm的linux上&#xff0c;发送接收的数据压缩成图片发送出去。 要达到轻量websocket 使用&#xff0c;必须要达到几个方面…

MySQL:数学函数和字符串函数

目录 前言&#xff1a; 数学函数&#xff1a; 求绝对值&#xff1a; 求PI&#xff1a; 求平方根&#xff1a; 求余数&#xff1a; 取整&#xff1a; 随机数&#xff1a; 四舍五入&#xff1a; 只舍不入&#xff1a; 返回参数符号&#xff1a; 幂运算&#xff1a; …

Illustrator如何编辑图形对象之实例演示?

文章目录 0.引言1.绘制海浪插画2.绘制时尚波浪发型3.绘制一条鲸鱼 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对Illustrator进行了学习&#xff0c;本文通过《Illustrator CC2018基础与实战》及其配套素材结合网上相关资料进行学习笔记总结&#xff0c;本文对图形…

快速上手Pytorch实现BERT,以及BERT后接CNN/LSTM

快速上手Pytorch实现BERT&#xff0c;以及BERT后接CNN/LSTM 本项目采用HuggingFace提供的工具实现BERT模型案例&#xff0c;并在BERT后接CNN、LSTM等 HuggingFace官网 一、实现BERT&#xff08;后接线性层&#xff09; 1.引用案例源码&#xff1a; from transformers impo…

开关电源基础01:电源变换器基础(2)

说在开头&#xff1a;关于德布罗意的电子波&#xff08;3&#xff09; 1923年&#xff0c;德布罗意在求出他的相波之前&#xff0c;康普顿刚好用光子说解释了康普顿效应&#xff08;记性好的胖友们应该还记得&#xff1a;散射波的波长变长问题&#xff09;&#xff0c;从而带领…

开关电源基础02:基本开关电源拓扑(2)-BOOST-BUCKBOOST拓扑

说在开头&#xff1a;关于海森堡的矩阵&#xff08;2&#xff09; 海森堡写完论文就回到了哥廷根大学&#xff0c;他一看见玻恩就把这份论文拿出来请老师把关&#xff0c;还说要趁着假期去趟英国剑桥大学讲课交流。玻恩拿过论文一看&#xff0c;海森堡画的这个表格是啥玩意啊&…

【操作系统】高性能网络模式:Reactor 和 Proactor

【操作系统】高性能网络模式&#xff1a;Reactor 和 Proactor 参考资料&#xff1a; 高性能 RPC 通信的实现- 巧用 reactor 模式 高性能网络模式&#xff1a;Reactor 和 Proactor NIO Reactor模型 Netty「基石」之Reactor模式 高性能IO模型分析-Reactor模式和Proactor模式 【…

【服务器】无公网IP,异地远程连接威联通NAS

Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员&#xff0c;2024届电子信息研究生 目录 前言 1. 威联通安装cpolar内网穿透 2. 内网穿透 2.1 创建隧道 2.2 测试公网远程访问 3. 配置固定二级子域名 3.1 保留二级子域名 3.2 配置二级子域名 4. 使用固定二级子…

Linux诊断原因:生产环境服务器变慢,诊断思路和性能评估

Linux诊断原因&#xff0c;生产环境服务器变慢&#xff0c;诊断思路和性能评估 1 整机&#xff1a;top&#xff0c;查看整机系统性能 使用top命令的话&#xff0c;重点关注的是 %CPU、%MEM 、load average 三个指标 load average三个指标&#xff1a;分别代表1、5、15时分钟系…

2022年NOC大赛编程马拉松赛道初赛图形化低年级A卷-正式卷,包含答案

目录 选择题: 下载文档打印做题: 2022年NOC大赛编程马拉松赛道【初赛】图形化低年级A卷-正式卷 2022NOC-图形化初赛低年级A卷正式卷 选择题: 1、答案:B 禾木是一个军事迷,他打算利用业余时间制作一款射击游戏。在游戏中,玩家可以通过鼠标控制手枪移动。请问,给手枪…

springboot+vue体质测试数据分析及可视化设计(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的体质测试数据分析及可视化设计。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&a…

[svg-icon]引入vue项目后,use标签为0,已解决

这个bug我之前遇到过一次&#xff0c;解决了也就没记录 但是好记性不如烂笔头&#xff0c;这次重新遇到&#xff0c;又重新排查bug花了1个多小时 svg引入vue项目&#xff0c;需要依赖svg-sprite-loader npm install svg-sprite-loader在vue.config.js中 chainWebpack(conf…

NOC大赛·核桃编程马拉松赛道知识点大纲(高年级及初中组)

NOC核桃编程马拉松知识点大纲(高年级及初中组) (一)基础语法 1.掌握运动积木的用法。 包括“移动 10 步”、“左/右转 X 度”、“面向 X 方向/鼠标指针/ 角色”、“移到 XY 坐标/鼠标/角色”、“X/Y 坐标的设定和增加”、 “滑行到 XY/鼠标/角色”等积木用法,详细如下。 1…

【数据结构】链表OJ:力扣160. 相交链表

最近这几篇内容都有关链表&#xff0c;接下来几篇内容会分享一些链表的OJ题&#xff0c;希望对你有所帮助。 今天要分享的内容是力扣160. 相交链表&#xff1a;160. 相交链表 - 力扣&#xff08;LeetCode&#xff09; 目录 题目&#xff1a; 题解 题目&#xff1a; 给你…

数据分析02——numpy模块的在jupyter中的使用

0、numpy&#xff1a; 在计算机中会把数学当中的矩阵叫做数组&#xff0c;在很多应用中的表格也就是通过矩阵表示的&#xff0c;所以numpy广泛用于机器学习&#xff0c;数据分析&#xff0c;和图像处理领域。 1、numpy常用方法和函数&#xff1a; 前言&#xff1a;在使用nump…

腾讯云轻量应用服务器使用限制说明(十大限制)

腾讯云轻量应用服务器和云服务器CVM相比具有一些限制&#xff0c;比如轻量服务器不支持更换内网IP地址&#xff0c;轻量服务器只能套餐整体升级且不支持降配&#xff0c;轻量不支持用户自定义配置私有网络VPC&#xff0c;还有如实例配额、云硬盘配额、备案限制和内网连通性等限…

Ububtu20.04 无法连接外屏(显卡驱动问题导致)

Ububtu20.04 无法显示第二个屏幕&#xff08;显卡驱动问题&#xff09; Ububtu20.04 无法显示第二个屏幕&#xff08;显卡驱动问题&#xff09; Ububtu20.04 无法显示第二个屏幕&#xff08;显卡驱动问题&#xff09; 1. 问题描述2. 解决方案 1. 问题描述 一开始我的ububt…

JavaGuide复习1——常见面试题总结部分

1、关于包装类的缓存机制 两种浮点数类型的包装类 Float,Double 并没有实现缓存机制。 对应源码&#xff1a; 拆装箱 都是调用了包装类的方法&#xff1a;valueOf&#xff08;基本转包装&#xff09;、xxxValue&#xff08;包装转基本&#xff09; 注意char char在Java中占用…

Illustrator如何进行文本的创建与编辑之实例演示?

文章目录 0.引言1.创建点文字2.串接文本3.石刻文字 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对Illustrator进行了学习&#xff0c;本文通过《Illustrator CC2018基础与实战》及其配套素材结合网上相关资料进行学习笔记总结&#xff0c;本文对文本的创建与编辑进…