C++泛型实现搜索二叉树

文章目录

    • 二叉搜索树
      • 查找
      • 插入
      • 删除
      • 实现
      • 应用
      • 性能分析

二叉搜索树

二叉搜索树(BST,Binary Search Tree)又称为二叉排序树,空树也算

二叉搜索树有如下性质

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

例如

屏幕截图 2024-03-08 204448.png

当然如果左大右小也可以

二叉搜索树的一个性质是中序遍历有序

查找

从根节点开始查找比较,比根大向右查找,比根小向左查找

最多查找高度次,如果没找到就代表值不存在

插入

如果为空,新增节点

如果不为空,按照性质插入节点

删除

首先需要确定值是否在二叉树中

要删除就右四种情况

  1. 无子节点——直接删除即可,可以合并到只有一个节点的情况
  2. 只有左节点——删除,令该节点的父节点指向左节点
  3. 只有右节点——删除,令该节点的父节点指向右节点
  4. 有两个子节点——在左子树寻找关键之最大的节点或右子树的最小节点,以最小节点为例,找到最小节点后与删除节点替换,再处理替换后的节点删除问题

实现

#pragma once
#include<iostream>

using namespace std;

template<class K> 
struct BSTreeNode // 二叉树节点,K表示关键字
{
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;

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

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	// C++11
	BSTree() = default; // 强制生成默认构造

	~BSTree()
	{
		Destroy(_root);
	}

	BSTree(const BSTreeNode<K>& t)
	{
		_root = Copy(t._root);
	}

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

	bool Insert(const K& key) // 建树,插入
	{
		if (_root == nullptr) // 空树
		{
			_root = new Node(key);
			return tree;
		}

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

		while (cur) // 找位置
		{
			parent = cur;
			if (cur->_key < key)
				cur = cur->_left; // 插入值比当前值小,进左树
			else if (cur->_key > key)
				cur = cur->_right; // 插入值比当前值大,进右树
			else
				return false; // 不允许出现重复值
		}

		cur = new Node(key);
		if (parent->_key < key) // 连接父节点
			parent->_right = cur;
		else
			parent->_left = cur;
	}

	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* parent = nullptr;
		Node* cur = _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 (cur == _root)
					{
						_root = cur->_right;
					}
					// 当删除节点不是根节点时,需要连接父节点和右子树
					else
					{
						// 判断当前节点是父节点的左孩子还是右孩子,确保正确连接
						if (cur == parent->_left)
						// 这里为什么不用防止parent是空指针呢
						// 因为只有根节点没有父节点,而前面一个判断已经把根节点单独处理过了
							parent->_left = cur->_right;
						else
							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
							parent->_right = cur->_left;
					}
					delete cur;
				}
				else // 左右都不为空
				{
					// 找到右子树的最小节点,替换后删除
					parent = cur; // 因为后面需要替换,防止出现解引用空指针
					Node* SubLeft = cur->_right; 
					// 表示右子树最小值,他一定在右子树的最高的最左边的那个节点上
					while (SubLeft->_left) // 找到该节点
					{
						parent = SubLeft;
						SubLeft = SubLeft->_left;
					}

					swap(cur->_key, SubLeft->_key); // 交换节点值

					// 最左节点一定是左子树为空,因此只需要父节点连接最左节点的右子树
					if (SubLeft == parent->_left) // 判断该节点是父节点的左节点还是右节点,再连接
						parent->_left = SubLeft->_right;
					else
						parent->_right = SubLeft->_right;
					delete SubLeft;
				}
				return true;
			}
			return false;
		}
	}

	void InOrder() // 中序遍历打印
	{
		// 中序遍历需要根节点,又不希望类外得到根节点
		// 这里可以只实现一个接口,内容是private即可
		// 后面的同理
		_InOrder(_root);
		cout << endl;
	}

	bool FindR(const K& key) // 递归查找
	{
		return _FindR(_root, key);
	}

	bool InsertR(const K& key)
	{
		return _InsertR(_root, K);
	}

	bool EraseR(const K& key)
	{
		return _EraseR(_root, key);
	}
private:
	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 Destroy(Node*& root)
	{
		if (root == nullptr)
			return;

		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
		root = nullptr;
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_key << ' ';
		_InOrder(root->_right);
	}

	bool _FindR(Node* root, const K& key)
	{
		if (root == nullptr)
			return false;

		if (root->_key < key)
			return _FindR(root->_right, key);
		else if (root->_key > key)
			return _FindR(root->_left, key);
		else
			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;
	}

	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)
			{
				Node* del = root;
				root = root->_right; // root也是父节点左右子树的别名,因此直接赋值
				delete del;

				return true;
			}
			else if (root->_right == nullptr)
			{
				Node* del = root;
				root = root->_left;
				delete del;

				return true;
			}
			else
			{
				Node* SubLeft = root->_right;
				while (SubLeft->_left)
					SubLeft = SubLeft->_left;
				
				swap(root->_key, SubLeft->_key);

				// 替换之后,转换成在右子树递归删除即可
				return _EraseR(root->_right, key);
			}
		}
	}

	Node* _root = nullptr;
};

应用

二叉搜索树一般有两个应用

第一类是K模型,结构中只需要存储Key即可,关键之就是所需要的值,一般用于检测某个值是否存在

第二类是KV模型,结构中是<Key,Value>键值对,类似于字典

性能分析

插入和删除都必须先查找

插入的次序不同,会影响到二叉树的结构

最优情况下,二叉树为完全二叉树,其平均比较次数为 log ⁡ 2 N \log_2N log2N

最差情况下,二叉树为单支树,其平均比较次数为 N 2 \frac{N}{2} 2N

因此当二叉树为单支树,我们应当如何改进,使其性能都达到最优,就需要引入AVL树和红黑树,这些我们在后面也会陆续讲解和实现

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

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

相关文章

Tonka Finance,BTCFi 浪潮的发动机

在 2023 年年初&#xff0c;Ordinals 技术方案为比特币 Layer1 带来了一种全新的资产发行方式&#xff0c;此后一场以比特币生态为主战场的新一轮资金、注意力价值争夺战打响&#xff0c;并且越来越多的加密原教旨主义者、密码极客们加入这场战争中。我们看到&#xff0c;铭文市…

类和对象(1)(至尊详解版)

相信对于大家而言&#xff0c;对于类和对象都会是一头雾水吧&#xff01;什么是类&#xff1f;或者你有对象吗&#xff1f;那么本期的内容呢&#xff1f;就由我来为大家再次增加对于它们的理解&#xff0c;由于水平上的原因&#xff0c;可能会存在不当之处&#xff0c;敬请读者…

局域网管理工具

每个组织的业务运营方法都是独一无二的&#xff0c;其网络基础设施也是如此&#xff0c;由于随着超融合基础设施等新计算技术的发展&#xff0c;局域网变得越来越复杂&#xff0c;因此局域网管理也应该如此&#xff0c;组织需要量身定制的局域网管理解决方案&#xff0c;这些解…

【FPGA】DDR3学习笔记(一)丨SDRAM原理详解

本篇文章包含的内容 一、DDR3简介1.1 DDR3 SDRAM概述1.2 SDRAM的基础结构 二、 SDRAM操作时序2.1 SDRAM操作指令2.2 模式寄存器&#xff08;LOAD MODE REGISTER&#xff09;2.3 SDRAM操作时序示例2.3.1 SDRAM初始化时序2.3.2 突发读时序2.3.3 随机读时序2.3.4 突发写时序2.3.5 …

计算机这几个炮灰专业,选错毕业直接去搬砖

计算机五大专业&#xff0c;选错毕业后直接去搬砖。 在所有的计算机类专业中&#xff0c;计算机科学与技术这个专业既要学硬件&#xff0c;也要学软件&#xff0c;既要学理论&#xff0c;也要学技术。课程既有数理类&#xff0c;也有电器类&#xff0c;还有计算机类&#xff0…

MySQl基础入门⑥

上一章知识内容 1.数据类型的属性 2.MySql的约束 mysql的约束时指对数据表中数据的一种约束行为&#xff0c;约束主要完成对数据的检验&#xff0c;如果有互相依赖数据&#xff0c;保证该数据不被删除。它能够帮助数据库管理员更好地管理数据库&#xff0c;并且能够确保数据库…

LCR 175. 计算二叉树的深度

一、题目描述 LCR 175. 计算二叉树的深度 二、思路 递归求左右子树的高度 三、解题思路 把大规模的问题拆分成小规模的问题1、要求根节点的二叉树深度 2、转换子问题&#xff1a;求左子树为根节点的二叉树深度 3、转换子问题&#xff1a;成求右子树为根节点的二叉树深度 4、最…

华为北向网管NCE开发教程(2)REST接口开发

华为北向网管NCE开发教程&#xff08;1&#xff09;闭坑选接口协议 华为北向网管NCE开发教程&#xff08;2&#xff09;REST接口开发 华为北向网管NCE开发教程&#xff08;3&#xff09;CORBA协议开发 假设你现在要开始华为北向接口REST协议之前&#xff0c;需要准备如环境 1准…

OpenHarmony教程指南-自定义通知推送

介绍 本示例主要展示了通知过滤回调管理的功能&#xff0c;使用ohos.notificationManager 接口&#xff0c;进行通知监听回调&#xff0c;决定应用通知是否发送。 效果预览 使用说明 1.在使用本应用时&#xff0c;需安装自定义通知角标应用&#xff1b; 2.在主界面&#xff…

Golang基于Redis bitmap实现布隆过滤器(完结版)

Golang基于Redis bitmap实现布隆过滤器&#xff08;完结版&#xff09; 为了防止黑客恶意刷接口&#xff08;请求压根不存在的数据&#xff09;&#xff0c;目前通常有以下几种做法&#xff1a; 限制IP&#xff08;限流&#xff09;Redis缓存不存在的key布隆过滤器挡在Redis前 …

ChatGLM:CPU版本如何安装和部署使用

前段时间想自己部署一个ChatGLM来训练相关的物料当做chatgpt使用&#xff0c;但是奈何没有gpu机器&#xff0c;只能使用cpu服务器尝试使用看看效果 我部署的 Chinese-LangChain 这个项目&#xff0c;使用的是LLM&#xff08;ChatGLM&#xff09;embedding(GanymedeNil/text2vec…

【活动】2024年AI辅助研发:深度变革与无限潜力

作为一名前端程序员&#xff0c;深入探讨2024年AI在软件研发领域的应用趋势及其影响&#xff0c;我们可以看到一场引人注目的转型正在发生。AI辅助研发对于前端开发而言&#xff0c;不仅意味着效率的飞跃&#xff0c;更是在用户体验设计、代码编写、性能优化、项目管理等诸多方…

什么是Java内存模型

当问到 Java 内存模型的时候&#xff0c;一定要注意&#xff0c;Java 内存模型&#xff08;Java Memory Model&#xff0c;JMM&#xff09;它和 JVM 内存布局&#xff08;JVM 运行时数据区域&#xff09;是不一样的&#xff0c;它们是两个完全不同的概念。 1.为什么要有 Java …

Windows按文件类型指定默认应用程序方法,.py文件设置默认打开程序实例演示

有两种方法可以设置按文件类型指定默认应用。 一个是系统的设置&#xff0c;但是部分类型里面是没有的&#xff0c;这种就要通过注册表来添加。 如果没有的话&#xff0c;通过 winR 打开运行&#xff0c;然后输入 regedit 打开注册表&#xff0c;在 计算机\HKEY_CLASSES_ROO…

防御保护--IPSEC VPPN实验

实验拓扑图 实验背景&#xff1a;FW1和FW2是双机热备的状态。 实验要求&#xff1a;在FW5和FW3之间建立一条IPSEC通道&#xff0c;保证10.0.2.0/24网段可以正常访问到192.168.1.0/24 IPSEC VPPN实验配置&#xff08;由于是双机热备状态&#xff0c;所以FW1和FW2只需要配置FW1…

实景三维逛景区,VR智慧景区打造云上旅游新体验

哈尔滨旅游的爆火&#xff0c;让其他地方的文旅景区宣传也纷纷发力。VR智慧景区将传统的旅游体验从线下拓展至线上&#xff0c;为游客带来不一样的旅行体验&#xff0c;人们可以提前在手机上沉浸式体验景区的真实环境&#xff0c;避免实地游玩踩雷&#xff0c;也为人们节省了旅…

COMSOL中使用自定义函数

目录 函数的用法 &#xff08;1&#xff09;解析函数 &#xff08;2&#xff09;插值函数 &#xff08;3&#xff09;分段函数 &#xff08;4&#xff09;高斯脉冲 &#xff08;5&#xff09;斜坡函数 &#xff08;6&#xff09;阶跃函数 &#xff08;7&#xff09;矩形…

JAVA实战开源项目:电子元器件管理系统(Vue+SpringBoot)

目录 一、摘要1.1 项目简介1.2 项目录屏 二、研究内容三、界面展示3.1 登录&注册&主页3.2 元器件单位模块3.3 元器件仓库模块3.4 元器件供应商模块3.5 元器件品类模块3.6 元器件明细模块3.7 元器件类型模块3.8 元器件采购模块3.9 元器件领用模块3.10 系统基础模块 四、…

BlackHole

BlackHole 文章目录 BlackHole一、关于 BlackHole功能描述 二、安装、卸载安装方式一&#xff1a;下载安装器方式二&#xff1a;使用 Homebrew 安装 卸载方式一&#xff1a;使用卸载器方式二&#xff1a;手动卸载 三、用户使用指南1、Logic Pro X2、GarageBand3、Reaper4、录制…

线程有几种状态,状态之间的流转是怎样的?

Java中线程的状态分为6种&#xff1a; 1.初始(NEW)&#xff1a;新创建了一个线程对象&#xff0c;但还没有调用start()方法。 2.运行(RUNNABLE)&#xff1a;Java线程中将就绪&#xff08;READY&#xff09;和运行中&#xff08;RUNNING&#xff09;两种状态笼统的称为“运行”…