[C++][数据结构]二叉搜索树:介绍和实现

news2024/10/6 16:24:46

二叉搜索树

概念

二叉搜索树又称二叉排序树,它是一棵空树,或者是具有以下性质的二叉树:

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

模拟实现(非递归)

节点

	template<class K, class V>
	struct BSTreeNode	//节点
	{
		using Node = BSTreeNode<K, V>;
		Node* _left;	//左节点
		Node* _right;	//右节点
		K _key;			//键
		V _value;		//值

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

寻找

根据BSTree的特点,

  • 比该节点的值大,就走到该节点右节点
  • 比该节点的值小,就走到该节点左节点

插入

  1. 先寻找,有相同值则return false
  2. 插入,链接父节点和原来的子树

删除

没有孩子/一个孩子

对于这种情况,

  • 当删除节点左边为空,就将该节点右边托付给父亲
  • 右边为空,就将左边托付给父亲
    所以对于删除叶子节点也可以归纳到这里面

两个孩子:替换法

找一个能替换删除节点的节点,交换值,转换删除替换节点
比如:删除图中3这个节点
我们可以将左子树的最大节点或者右子树的最左节点替换掉3
即左子树最右节点或者右子树最左节点

在这里插入图片描述

rightMin的问题

假如rightMinParent为空指针,那下面这句特殊情况会出错

rightMinParent->_left = rightMin->_right;

在这里插入图片描述

这幅图中,当删除根节点时,右子树最左节点为空,不进循环,rightMinParent为空,访问空节点的左子树会出现错误

代码

测试代码

void TestBSTree()
{
	BSTree<string, string> dict;
	dict.Insert("insert", "插入");
	dict.Insert("erase", "删除");
	dict.Insert("left", "左边");
	dict.Insert("string", "字符串");

	string str;
	while (cin >> str)
	{
		auto ret = dict.Find(str);
		if (ret)
		{
			cout << str << ":" << ret->_value << endl;
		}
		else
		{
			cout << "单词拼写错误" << endl;
		}
	}

	string strs[] = { "苹果", "西瓜", "苹果", "樱桃", "苹果", "樱桃", "苹果", "樱桃", "苹果" };
	// 统计水果出现的次
	BSTree<string, int> countTree;
	for (auto str : strs)
	{
		auto ret = countTree.Find(str);
		if (ret == NULL)
		{
			countTree.Insert(str, 1);
		}
		else
		{
			ret->_value++;
		}
	}
	countTree.InOrder();
}

完整代码

namespace key_value
{
	template<class K, class V>
	struct BSTreeNode	//节点
	{
		using Node = BSTreeNode<K, V>;
		Node* _left;	//左节点
		Node* _right;	//右节点
		K _key;			//键
		V _value;		//值

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

	template<class K, class V>
	class BSTree
	{
		using Node = BSTreeNode<K, V>;
	public:
		//插入
		bool Insert(const K& key, const V& value)
		{
			if (_root == nullptr)
			{
				_root = new Node(key, value);
				return true;
			}//如果树为空,特殊判断

			Node* parent = nullptr;
			//父节点
			//方便记录父节点原来的子树
			Node* cur = _root;
			while (cur != nullptr)
			{
				if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					return false;
				}
			}

			//查找完再判断是父节点的左树还是右数
			//标记为A
			cur = new Node(key, value);
			if (parent->_key > key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_right = cur;
			}
			return true;
		}

		//查找,并返回这个节点的指针(位置)
		Node* Find(const K& key)
		{
			Node* cur = _root;
			while (cur != nullptr)
			{
				if (cur->_key > key)
				{
					cur = cur->_right;
				}
				else if (cur->_key < key)
				{
					cur = cur->_left;
				}
				else
				{
					return cur;
				}
			}
			return nullptr;
		}

		//删除节点
		bool Erase(const K& key)
		{
			//并没有在开头判断删除节点是否为根节点
			//而是在下面
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur != nullptr)
			{
				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
						{	//功能A
							if (cur == parent->_right)
							{
								parent->_right = cur->_right;
							}
							else
							{
								parent->_left = cur->_right;
							}
						}
						delete cur;
						return true;
					}
					else if (cur->_right == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->left;
						}
						else
						{	//功能A
							if (cur == parent->_right)
							{
								parent->_right = cur->_left;
							}
							else
							{
								parent->_left = cur->_left;
							}
						}
						delete cur;
						return true;
					}
					else
					{
						// 采用右树最左节点替换法
						Node* rightMin = cur->_right;
						Node* rightMinParent = cur;
						while (rightMin->_left)
						{
							rightMinParent = rightMin;
							rightMin = rightMin->_left;
						}
						cur->_key = rightMin->_key;
						if (rightMin == rightMinParent->_right)
						{
							rightMinParent->_right = rightMin->_right;
						}
						else
						{
							rightMinParent->_left = rightMin->_left;
						}
						delete rightMin;
						return true;
					}
				}
			}
			return false;
		}

		void InOrder()//中序打印
		{
			_InOrder(_root);
		}//因为外部取不到_root
		//所以再套一层

	private:
		Node* _root = nullptr;

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

结语

现在再来写BSTree感觉也不是很困难,希望对你有帮助,我们下次见
(补充一下,代码是不全的,BSTree() 和~BSTree()都没写,用的默认的)

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

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

相关文章

JavaScript中的Math对象方法、Date对象方法

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;JavaScript 精粹 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 &#x1f31f;Math对象方法&#x1f344;1 Math静态属性&#x1f344;2 Math…

Vue阶段练习:组件拆分

页面开发思路 分析页面&#xff0c;按模块拆分组件&#xff0c;搭架子&#xff08;局部或全局注册&#xff09;根据设计图&#xff0c;编写html结构css样式拆分封装通用小组件&#xff08;局部或全局注册&#xff09;将来通过js动态渲染实现功能 BaseBrandItem.vue <templ…

A5资源网有哪些类型的资源可以下载?

A5资源网提供了广泛的资源下载&#xff0c;包括但不限于以下类型&#xff1a; 设计素材&#xff1a;包括各类图标、矢量图、背景素材、UI界面元素等&#xff0c;适用于网页设计、平面设计等领域。 图片素材&#xff1a;提供高质量的照片、插图、摄影作品等&#xff0c;可用于…

使用STM32F103C8T6与蓝牙模块HC-05连接实现手机蓝牙控制LED灯

导言: 在现代智能家居系统中,远程控制设备变得越来越普遍和重要。本文将介绍如何利用STM32F103C8T6单片机和蓝牙模块HC-05实现远程控制LED灯的功能。通过这个简单的项目,可以学会如何将嵌入式系统与蓝牙通信技术相结合,实现远程控制的应用。 目录 导言: 准备工作: 硬…

ue引擎游戏开发笔记(27)——解决角色移动及转动存在卡顿掉帧小技巧

1.需求分析&#xff1a; 随之游戏越来越大&#xff0c;难免出现部分时候移动出现卡顿&#xff0c;能否进行一定优化。 2.操作实现&#xff1a; 1.思路&#xff1a;采取捕获最后deltaseconds来逐帧进行旋转或移动&#xff0c;使动作显得不那么卡顿。 .2.首先在引擎中建立映射&a…

【深度学习】第一门课 神经网络和深度学习 Week 4 深层神经网络

&#x1f680;Write In Front&#x1f680; &#x1f4dd;个人主页&#xff1a;令夏二十三 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd; &#x1f4e3;系列专栏&#xff1a;深度学习 &#x1f4ac;总结&#xff1a;希望你看完之后&#xff0c;能对…

DRF解析器源码分析

DRF解析器源码分析 1 解析器 解析请求者发来的数据&#xff08;JSON&#xff09; 使用 request.data 获取请求体中的数据。 这个 reqeust.data 的数据怎么来的呢&#xff1f;其实在drf内部是由解析器&#xff0c;根据请求者传入的数据格式 请求头来进行处理。 drf默认的解…

连接一个 IP 不存在的主机时,会发生什么?(面试)

一、IP 不存在时 如果 IP 在局域网内&#xff0c;会发送 N 次 ARP 请求获得目的主机的 MAC 地址&#xff0c;同时不能发出 TCP 握手消息。 如果 IP 在局域网外&#xff0c;会将消息通过路由器发出&#xff0c;但因为最终找不到目的地&#xff0c;触发 TCP 重试流程。 二、IP…

✔ ★Java项目——设计一个消息队列(五)【虚拟主机设计】

虚拟主机设计 创建 VirtualHost实现构造⽅法和 getter创建交换机删除交换机创建队列删除队列创建绑定删除绑定发布消息 ★路由规则1) 实现 route ⽅法2) 实现 checkRoutingKeyValid3) 实现 checkBindingKeyValid4) 实现 routeTopic5) 匹配规则测试⽤例6) 测试 Router 订阅消息1…

酒水门店私域流量运营搭建执行规划方案

【干货资料持续更新&#xff0c;以防走丢】 酒水门店私域流量运营搭建执行规划方案 部分资料预览 资料部分是网络整理&#xff0c;仅供学习参考。 PPT可编辑&#xff08;完整资料包含以下内容&#xff09; 目录 精酿啤酒品牌私域执行运营的内容策划&#xff0c;涉及以下几个…

快讯! MySQL 8.4.0 LTS 发布(MySQL 第一个长期支持版本)

MySQL 第一个长期支持版本 8.4.0 LTS 发布&#xff0c;社区版下载地址&#xff1a; https://dev.mysql.com/downloads/mysql/ 功能变更 添加或更改的功能 组复制&#xff1a;与组复制相关的两个服务器系统变量的默认值已更改&#xff1a; 系统变量的默认值为 group_replication…

HashMap源码分析(jdk1.8,保证你能看懂)

现在的面试当中凡是那些大厂&#xff0c;基本上都会问到一些关于HashMap的问题了&#xff0c;而且这个集合在开发中也经常会使用到。于是花费了大量的时间去研究分析写了这篇文章。本文是基于jdk1.8来分析的。篇幅较长&#xff0c;但是都是循序渐进的。耐心读完相信你会有所收获…

【QT学习】12.UDP协议,广播,组播

一。Udp详细解释 UDP&#xff08;User Datagram Protocol&#xff09;是一种无连接的传输层协议&#xff0c;它提供了一种简单的、不可靠的数据传输服务。与TCP相比&#xff0c;UDP不提供可靠性、流量控制、拥塞控制和错误恢复等功能&#xff0c;但由于其简单性和低开销&#x…

for循环赋值

在for循环内将i赋值给j的问题 for(int i0,ji1;i<5;i){//此时j只会等于1cout<<"i-"<<i<<" j-"<<j<<endl; }如图&#xff1a; 将j放入循环体后没问题 for(int i0;i<5;i){int j i1; cout<<"i-"<<…

leetcode84柱状图中最大的矩形

题解&#xff1a; - 力扣&#xff08;LeetCode&#xff09; class Solution {public int largestRectangleArea(int[] heights) {Stack<Integer> stack new Stack<>();int maxArea Integer.MIN_VALUE;for(int i 0;i < heights.length;i){int curHeight hei…

|Python新手小白中级教程|第二十三章:列表拓展之——元组

文章目录 前言一、列表复习1.索引、切片2.列表操作字符3.数据结构实践——字典 二、探索元组1.使用索引、切片2.使用__add__((添加元素&#xff0c;添加元素))3.输出元组4.使用转化法删除元组指定元素5.for循环遍历元组 三、元组VS列表1.区别2.元组&#xff08;tuple&#xff0…

【书生·浦语大模型实战营第二期】Lagent AgentLego 智能体应用搭建——学习笔记6

文章目录 概述Lagent: 轻量级智能体框架Lagent Web Demo用Lagent自定义工具 AgentLego&#xff1a;组装智能体“乐高”直接使用AgentLego作为智能体工具使用AgentLego用AgentLego自定义工具 参考资料 概述 Lagent是什么 一个轻量级开源智能体框架&#xff0c;提供了一些典型工…

(ARM)ORACLE JDK 22 的下载安装及环境变量的配置

目录 获取JDK 安装JDK 配置JAVA环境变量 其他补充&#xff1a;JDK 22的新特征 1. 语法 2. 库 3. 性能 4. 工具 在今年的3月份&#xff0c;ORACLE 更新了的JDK 发行版 JDK 22&#xff0c;作为了一位ORACLE Primavera系列产品的研究者&#xff0c;其实对JDK的迭代完全不感…

Qt QImageReader类介绍

1.简介 QImageReader 是用于读取图像文件的类。它提供了读取不同图像格式的功能&#xff0c;包括但不限于 PNG、JPEG、BMP 等。QImageReader 可以用于文件&#xff0c;也可以用于任何 QIODevice&#xff0c;如 QByteArray &#xff0c;这使得它非常灵活。 QImageReader 是一个…

不坑盒子激活码免费领取

不坑盒子的一些新出来的大功能&#xff0c;都需要账号有Pro权限才能使用了。 关键是这些功能还很强大呢&#xff01;不用还不行&#xff01; 今天发现一个可以免费领不坑盒子Pro激活码的方法&#xff1a; 扫码进去后&#xff0c;就能看到激活码了&#xff1a; 复制激活码&…