二叉搜索树讲解

news2024/12/1 10:24:12

二叉搜索树概念和定义

二叉搜索树是一个二叉树,其中每个节点的值都满足以下条件:

  • 节点的左子树只包含小于当前节点值的节点。
  • 节点的右子树只包含大于当前节点值的节点。
  • 左右子树也必须是二叉搜索树。

二叉树搜索树性质

从上面的二叉搜索树定义中可以了解到, 二叉搜索树是有序的.

通过中序遍历, 会发现这是个有序的序列, 升序或降序 (自己决定)

二叉搜索树只支持增删查, 不支持该.
因为如果随意修改二叉搜索树节点的值, 那么就有可能会导致, 这棵树不再满足二叉搜索树的条件.

二叉搜索树的查找的时间复杂度: 
最好情况: O(log n)
最坏情况: O(N)

当二叉搜索树是以上的情况时, 就变成了链表, 那么时间复杂度就是 O(N).

二叉搜索树中是不能出现相同元素的.

二叉搜索树模拟实现

创建一个二叉搜索树节点类

template<class T>
struct SearchTreeNode
{
	T _data; // 存储数据
	SearchTreeNode<T>* _leftchile; // 指向左孩子
	SearchTreeNode<T>* _rightchild; // 指向右孩子

	SearchTreeNode(const T& data)
		:_data(data),
		_leftchile(nullptr),
		_rightchild(nullptr)
	{}
};

二叉搜索树类创建

template<class T>
class SearchTree
{
	typedef SearchTreeNode<T> SearchTreeNode;
public:
    bool insert(const T& data)
    {}
    bool erase(const T& data)
    {}
    bool find(const T& data)
    {}
private:
	SearchTreeNode* _root;
};

二叉搜索树的插入

二叉搜索树的插入非常简单.
从根节点开始, 如果插入的值小于节点值, 那么向左走,
如果插入的值大于节点值, 那么向右走,
如果值已存在, 那么直接返回 false.

bool insert(const T& data)
{
	SearchTreeNode* node = new SearchTreeNode(data);
	if (_root == nullptr) // 如果 _root 为空, 那么也就需要更新 _root 节点
	{
		_root = node;
		return true;
	}
	else
	{
		SearchTreeNode* prev = nullptr; // 记录插入位置的父节点
		SearchTreeNode* cur = _root; // 查找插入位置
		while (cur != nullptr)
		{
			if (cur->_data == data) // 如果要插入的数据已存在, 直接返回
			{
				return false;
			}
			if (data < cur->_data) // 要插入的数据小于节点数据, 向左走
			{
				prev = cur;
				cur = cur->_leftchile;
			}
			else // 要插入的数据大于节点数据, 向右走
			{
				prev = cur;
				cur = cur->_rightchild;
			}
		}
		if (data < prev->_data) // 判断要插入的位置是左边还是右边
		{
			prev->_leftchile = node;
		}
		else
		{
			prev->_rightchild = node;
		}
		return true;
	}
}

二叉搜索树的删除

1. 被删除的节点最多只有一个孩子

1) 被删除的节点是叶子节点, 没有孩子

这种情况下, 直接将这个节点删除即可

2) 被删除的节点只有左孩子

将本节点删除后, 将节点的左孩子连接到本节点的父节点上

3) 被删除的节点只有右孩子

和只有左孩子相同, 删除本节点, 右孩子连接到本节点的父节点上

void erase(const T& data)
{
	SearchTreeNode* prev = nullptr;
	SearchTreeNode* cur = _root;
	while (cur != nullptr) // 先查找是否存在这个值
	{
		if (data == cur->_data)
		{
			break;
		}
		prev = cur;
		if (data < cur->_data)
		{
			cur = cur->_leftchile;
		}
		else
		{
			cur = cur->_rightchild;
		}
	}
	if (cur == nullptr) // 不存在这个值, 直接返回
	{
		return;
	}
	if (cur->_leftchile == nullptr || cur->_rightchild == nullptr) // 存在这个节点, 这个节点没有孩子, 或者只有一个孩子
	{
		if (cur->_leftchile == nullptr) // 只有右孩子
		{
			if (_root == cur) // 如果要删除的节点就是 _root, 更新 _root
			{
				_root = cur->_rightchild;
			}
			else
			{
				if (prev->_leftchile = cur) // 判断要连接再父节点的哪边
				{
					prev->_leftchile = cur->_rightchild;
				}
				else
				{
					prev->_rightchild = cur->_rightchild;
				}
			}
		}
		else
		{
			if (_root == cur)
			{
				_root = cur->_leftchile;
			}
			else
			{
				if (prev->_leftchile = cur)
				{
					prev->_leftchile = cur->_leftchile;
				}
				else
				{
					prev->_rightchild = cur->_leftchile;
				}
			}
		}
		delete cur;
	}
}

2. 被删除的节点有两个孩子

我们不直接删除这个节点, 使用和 堆 删除节点差不多的方法.

我们将要删除的这个节点和一个最多只有一个孩子的节点进行交换

然后删除那个交换后的值.

那么这个交换的节点怎么选择.

1. 本节点右子树中的最小值的那个节点 (即右子树的最左节点)

2. 本节点左子树中的最大值的那个节点 (左子树的最右节点)

这两个选择方法, 选择出来的要交换的节点的值, 都是最接近 本节点值的 节点.

 找到这个节点之后, 交换 cur 和 child 的值, 然后删除 child 节点即可.

void erase(const T& data)
{
	SearchTreeNode* prev = nullptr;
	SearchTreeNode* cur = _root;
	while (cur != nullptr) // 先查找是否存在这个值
	{
		if (data == cur->_data)
		{
			break;
		}
		prev = cur;
		if (data < cur->_data)
		{
			cur = cur->_leftchile;
		}
		else
		{
			cur = cur->_rightchild;
		}
	}
	if (cur == nullptr) // 不存在这个值, 直接返回
	{
		return;
	}
	if (cur->_leftchile == nullptr || cur->_rightchild == nullptr) // 存在这个节点, 这个节点没有孩子, 或者只有一个孩子
	{
		if (cur->_leftchile == nullptr)
		{
			if (_root == cur)
			{
				_root = cur->_rightchild;
			}
			else
			{
				if (prev->_leftchile = cur)
				{
					prev->_leftchile = cur->_rightchild;
				}
				else
				{
					prev->_rightchild = cur->_rightchild;
				}
			}
		}
		else
		{
			if (_root == cur)
			{
				_root = cur->_leftchile;
			}
			else
			{
				if (prev->_leftchile = cur)
				{
					prev->_leftchile = cur->_leftchile;
				}
				else
				{
					prev->_rightchild = cur->_leftchile;
				}
			}
		}
		delete cur;
	}
	else // 这个节点有两个孩子, 上半部分代码 和 文章上面的代码是一样的
	{
		prev = cur;
		SearchTreeNode* child = cur->_rightchild; // 要删除的节点
		while (child->_leftchile != nullptr) // 查找符合要求的节点
		{
			prev = child;
			child = child->_leftchile;
		}
		cur->_data = child->_data; // 交换数据
		if (child->_rightchild == nullptr) // child 是叶子节点
		{
			if (prev == cur) // 可以看下图演示
			{
				prev->_rightchild = nullptr;
			}
			else
			{
				prev->_leftchile = nullptr;

			}
		}
		else // child 有右子树, 这不可能有左子树, 因为这里找的就是最左节点
		{
			if (prev == cur)
			{
				prev->_rightchild = cur->_rightchild;
			}
			else
			{
				prev->_leftchile = child->_rightchild;

			}
		}
		delete child;
	}
}

那么另一种方法, 和这种方法差不多, 会一种就会另一种. 
去本节点左子树中, 查找最右的节点.

至于查找功能, 无论是再插入还是删除中, 都有这部分操作.

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

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

相关文章

FinalShell工具数据备份升级、密码解密方法

前言 FinalShell 作为国产的服务器管理工具和远程终端软件。一个一体化的运维工具&#xff0c;在国内运维人员中还是比较受欢迎。它整合了多个常用功能&#xff0c;界面友好&#xff0c;使用方便。不过它是一个闭源的商业软件&#xff0c;虽然提供免费版本&#xff0c;但部分高…

241130_昇思MindSpore函数式自动微分

241130_昇思MindSpore函数式自动微分 函数式自动微分是Mindspore学习框架所特有的&#xff0c;更偏向于数学计算的习惯。这里也是和pytorch差距最大的部分&#xff0c;具体体现在训练部分的代码&#xff0c;MindSpore是把各个梯度计算、损失函数计算 在这幅图中&#xff0c;右…

菱形打印(Python)

“以块组合块”&#xff0c;以行凝结循环打印。 (笔记模板由python脚本于2024年11月30日 19:55:22创建&#xff0c;本篇笔记适合正在学习python循环的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”…

【QT入门到晋级】QT项目打生产环境包--(Linux和window)

前言 使用QTcreator完成正常编译后&#xff0c;在构建目录中有可执行程序生成&#xff0c;如果直接把可执行程序拷贝到干净的生产环境上是无法运行成功的&#xff0c;使用ldd&#xff08;查看程序依赖包&#xff09;会发现缺失很多QT的特性包&#xff0c;以及将介绍国产Linux桌…

Super Vlan与Mux Vlan

SuperVlan VLAN Aggregation&#xff0c; 也称 Super-VLAN : 指 在一个物理网络内&#xff0c;用多个 VLAN &#xff08;称为 Sub-VLAN &#xff09;隔离 广播域&#xff0c;并将这些 Sub-VLAN 聚合成一个逻辑的 VLAN &#xff08;称为 Super-VLAN &#xff09;&#xff0c;这…

蓝牙定位的MATLAB程序,四个锚点、三维空间

这段代码通过RSSI信号强度实现了在三维空间中的蓝牙定位&#xff0c;展示了如何使用锚点位置和测量的信号强度来估计未知点的位置。代码涉及信号衰减模型、距离计算和最小二乘法估计等基本概念&#xff0c;并通过三维可视化展示了真实位置与估计位置的关系。 目录 程序描述 运…

Hutool 秒速实现 2FA 两步验证

前言 随着网络安全威胁的日益复杂&#xff0c;传统的用户名和密码认证方式已不足以提供足够的安全保障。为了增强用户账户的安全性&#xff0c;越来越多的应用和服务开始采用多因素认证&#xff08;MFA&#xff09;。基于时间的一次性密码&#xff08;TOTP, Time-based One-Ti…

【继承】—— 我与C++的不解之缘(十九)

前言&#xff1a; 面向对象编程语言的三大特性&#xff1a;封装、继承和多态 本篇博客来学习C中的继承&#xff0c;加油&#xff01; 一、什么是继承&#xff1f; ​ 继承(inheritance)机制是⾯向对象程序设计使代码可以复⽤的最重要的⼿段&#xff0c;它允许我们在保持原有类…

【目标跟踪】Anti-UAV数据集详细介绍

Anti-UAV数据集是在2021年公开的专用于无人机跟踪的数据集&#xff0c;该数据集采用RGB-T图像对的形式来克服单个类型视频的缺点&#xff0c;包含了318个视频对&#xff0c;并提出了相应的评估标准&#xff08;the state accurancy, SA)。 文章链接&#xff1a;https://arxiv.…

偏差-方差权衡(Bias–Variance Tradeoff):理解监督学习中的核心问题

偏差-方差权衡&#xff08;Bias–Variance Tradeoff&#xff09;&#xff1a;理解监督学习中的核心问题 在机器学习中&#xff0c;我们希望构建一个能够在训练数据上表现良好&#xff0c;同时对未见数据也具有强大泛化能力的模型。然而&#xff0c;模型的误差&#xff08;尤其…

Figma入门-原型交互

Figma入门-原型交互 前言 在之前的工作中&#xff0c;大家的原型图都是使用 Axure 制作的&#xff0c;印象中 Figma 一直是个专业设计软件。 最近&#xff0c;很多产品朋友告诉我&#xff0c;很多原型图都开始用Figma制作了&#xff0c;并且很多组件都是内置的&#xff0c;对…

Windows系统怎么把日历添加在桌面上用来记事?

在众多电脑操作系统中&#xff0c;Windows系统以其广泛的用户基础和强大的功能&#xff0c;成为许多人的首选。对于习惯于在电脑前工作和学习的用户来说&#xff0c;能够直接在桌面上查看和记录日历事项&#xff0c;无疑会大大提高工作效率和生活便利性。今天&#xff0c;就为大…

蓝桥杯备赛笔记(一)

这里的笔记是关于蓝桥杯关键知识点的记录&#xff0c;有别于基础语法&#xff0c;很多内容只要求会用就行&#xff0c;无需深入掌握。 文章目录 前言一、编程基础1.1 C基础格式和版本选择1.2 输入输出cin和cout&#xff1a; 1.3 string以下是字符串的一些简介&#xff1a;字符串…

大数据新视界 -- 大数据大厂之 Hive 数据压缩:优化存储与传输的关键(上)(19/ 30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

RNN And CNN通识

CNN And RNN RNN And CNN通识一、卷积神经网络&#xff08;Convolutional Neural Networks&#xff0c;CNN&#xff09;1. 诞生背景2. 核心思想和原理&#xff08;1&#xff09;基本结构&#xff1a;&#xff08;2&#xff09;核心公式&#xff1a;&#xff08;3&#xff09;关…

求整数的和与均值

求整数的和与均值 C语言代码C 代码Java代码Python代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 读入n&#xff08;1 < n < 10000&#xff09;个整数&#xff0c;求它们的和与均值。 输入 输入第一行是一个整数n&#xff0c;…

配置idea环境进行scala编程

这里用的jdk是jdk-8u161,scala版本是2.12.0 在d盘新建一个本地仓库用来存放下载的maven包&#xff0c;在里面创建如下两个文件 更改settings文件为下面的样子 点击左下角的设置&#xff0c;更改maven本地仓库的位置&#xff08;默认在c盘用户目录下的.m2文件中&#xff0c;更改…

WSL简介与安装流程(Windows 下的 Linux 子系统)

目录 1.wsl安装 1.1 WSL简介 1.1.1 WSL 的主要功能 1.1.2 WSL 的版本 1.1.3 为什么使用 WSL&#xff1f; 1.1.4 WSL 的工作原理 1.1.5 WSL 的常见使用场景 1.1.6 与虚拟机的区别 1.1.7 适合使用 WSL 的人群 1.2 启用 WSL 1.2.1 打开 PowerShell&#xff08;管理员模…

【Java树】二叉树遍历的简单实现

二叉树的遍历 二叉树的遍历是值按照一定顺序访问二叉树中所有结点的过程&#xff0c;确保每个结点被访问且仅被访问一次。遍历操作是对二叉树的基础操作&#xff0c;用于后续的查找、排序和路径计算等功能。 二叉树的遍历有以下几种常见方式&#xff1a;深度遍历&#xff08;…

STL算法之set相关算法

STL一共提供了四种与set(集合)相关的算法&#xff0c;分别是并集(union)、交集(intersection)、差集(difference)、对称差集(symmetric difference)。 目录 set_union set_itersection set_difference set_symmetric_difference 所谓set&#xff0c;可细分为数学上定义的和…