二叉搜索树比起二叉树又有什么不一样呢?

news2025/1/16 19:00:22

二叉搜索树比起二叉树又有什么不一样呢?

  • 🏐什么是二叉搜索树
  • 🏐二叉搜索树的实现
    • 🏀节点类:
    • 🏀构造函数
    • 🏀析构函数
    • 🏀插入insert
      • ⚽非递归版本
      • ⚽递归版本
    • 🏀查找find
      • ⚽非递归版本
      • ⚽递归版本
    • 🏀删除erase
      • ⚽非递归版本
      • ⚽递归版本
    • 🏀拷贝问题
    • 🏀赋值
  • 🏐二叉搜索树的应用及分析
    • 🏀性能分析
  • 💬一些小点

👀先看这里👈
😀作者:江不平
📖博客:江不平的博客
📕学如逆水行舟,不进则退
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
❀本人水平有限,如果发现有错误的地方希望可以告诉我,共同进步👍

在这里插入图片描述

数据对于我们来说除了存储之外最重要的就是查找了,怎么才能快速的进行查找呢?有序的就好查找,二分查找对于我们来说效率并不高,搜索树和哈希表是更好的方式。

🏐什么是二叉搜索树

二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树:

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉排序树。

在这里插入图片描述

二叉搜索树作为一种经典的数据结构,它既有链表的快速插入与删除操作的特点,又有数组快速查找的优势;所以应用十分广泛。

给个缺省值,连构造函数都不用写了

🏐二叉搜索树的实现

二叉搜索树(BST)又称二叉查找树或二叉排序树。一棵二叉搜索树是以二叉树来组织的,可以使用一个链表数据结构来表示,其中每一个结点就是一个对象。
一般地,除了key和位置数据之外,每个结点还包含属性lchild、rchild和parent,分别指向结点的左孩子、右孩子和双亲(父结点)。如果某个孩子结点或父结点不存在,则相应属性的值为空(NULL)。根结点是树中唯一父指针为NULL的结点,而叶子结点的孩子结点指针也为NULL。

🏀节点类:

要实现二叉搜索树先构造个节点类出来

template<class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;

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

🏀构造函数

构造函数构造一个空树即可,也可以调用默认的构造函数

第一种:
BSTree()
{}
第二种
BSTree(){
	:root(nullptr)
}
第三种:C++的用法,使用default关键字强制编译器生成默认的构造
BSTree() = default;

🏀析构函数

析构函数要完成对每个结点的释放,注意释放时是后序顺序,当结点都被释放完后,别忘了把最后一个节点置空。

void _Destory(Node*& root)
{
	if (root == nullptr)//空树无需释放
	{
		return;
	}

	_Destory(root->_left);//释放左子树
	_Destory(root->_right);//释放右子树
	delete root;//释放根节点
	root = nullptr;//置空
}
~BSTree()
{
	_Destory(_root);
}

🏀插入insert

插入分两种情况,空树和不是空树,所以要先进行判断
不是空树则按照二叉搜索树的性质来进行插入,若存在与插入相等的值就插入失败,根据这一逻辑,来写insert函数的实现。

⚽非递归版本

我们需要定义一个parent指针,该指针用于标记待插入结点的父结点。
这样一来,当我们找到待插入结点的插入位置时,才能很好的将待插入结点与其父结点连接起来。

template<class K>
	class BSTree
	{
		typedef BSTreeNode<K> Node;
	public:
		bool Insert(const K& key)
		{
			if (_root == nullptr)
			{
				_root = new Node(key);
				return true;
			}

			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//如果存在与插入相等的值,则插入失败
				{
					return false;
				}
			}

			cur = new Node(key);//把key值插入节点完成,还需要完成连接,才算是插入,所以还需要parent节点
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}

			return true;
		}
	}

⚽递归版本

注意这里的参数root,给了引用,不给引用的话就需要记录下parent,需要连接起来,不然出了函数之后就没有插入成功,因为它不加引用就是个局部变量。这个函数参数在所有函数中都加了引用。加了引用就可以很好的把节点都连接起来

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;
}

🏀查找find

与插入insert结构基本一致

⚽非递归版本

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;
		}
	}
}

⚽递归版本

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;
}

🏀删除erase

删除操作就要麻烦的多了,根据有没有子树可以分为这么几种情况:

  1. 待删除结点是是叶子结点,没有子树
  2. 待删除结点有左子树或右子树
  3. 待删除结点左右子树都有

对于没有子树的情况我们也可以归到没有只有一个子树中去
对于一个子树的,如果是只有左子树,那么就父亲结点连接到左子树上,如果是只有右子树的,那么父亲结点连接到右子树上。
左右子树都存在的情况我们采用替换法,寻找待删除结点左子树当中最大的结点;或寻找或是待删除结点右子树当中值最小的结点去进行替换,替换后就会出现前面的情况,按照前面方法删除即可
注意只能是待删除结点左子树当中值最大的结点,或是待删除结点右子树当中值最小的结点代替待删除结点被删除,因为只有这样才能使得进行删除操作后的二叉树仍保持二叉搜索树的特性。

⚽非递归版本

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
		{
			// 开始删除
			// 1、左为空
			// 2、右为空
			// 3、左右都不为空
			if (cur->_left == nullptr)
			{
				if (cur == _root)
				{
					_root = cur->_right;
				}
				else
				{
					if (cur == parent->_left)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
				}

				delete cur;
				cur = nullptr;
			}
			else if (cur->_right == nullptr)
			{
				if (_root == cur)
				{
					_root = cur->_left;
				}
				else
				{
					if (cur == parent->_left)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
				}

				delete cur;
				cur = nullptr;
			}
			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;
}

⚽递归版本

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->_right;//两个函数作用域中同名的参数是不一样的,引用在这个地方也很关键,这样就建立起联系了,以前写的那一堆因为引用就变成了一句
		}
		else if (root->_right == nullptr)
		{
			root = root->_left;
		}
		else
		{
			// 找右树的最左节点替换删除
			Node* min = root->_right;
			while (min->_left)
			{
				min = min->_left;
			}
			swap(root->_key, min->_key);
			//return EraseR(key);  错的
			return _EraseR(root->_right, key);
		}

		delete del;
		return true;
	}
}

🏀拷贝问题

遇到一个节点拷贝一个节点,这里是深拷贝,然后再左子树再右子树

Node* _Copy(Node* root)
{
	if (root == nullptr)
	{
		return nullptr;
	}

	Node* copyRoot = new Node(root->_key);
	copyRoot->_left = _Copy(root->_left);
	copyRoot->_right = _Copy(root->_right);
	return copyRoot;
}

🏀赋值

一般都是复用copy函数啦
一般写法

//传统写法
const BSTree<K>& operator=(const BSTree<K>& t)
{
	if (this != &t) //防止自己给自己赋值
	{
		_root = _Copy(t._root); //拷贝t对象的二叉搜索树
	}
	return *this; //支持连续赋值
}

还有一种赋值运算符重载函数的写法非常巧妙,函数在接收右值时并没有使用引用进行接收,因为这样可以间接调用BSTree的拷贝构造函数完成拷贝构造。我们只需将这个拷贝构造出来的对象的二叉搜索树与this对象的二叉搜索树进行交换,就相当于完成了赋值操作,而拷贝构造出来的对象t会在该赋值运算符重载函数调用结束时自动析构。

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

🏐二叉搜索树的应用及分析

key模型(用来查找,判断key值在不在)
例如:用来判断一篇文章中是否出现错误单词,将其与单词库中的进行比对,若不存在就说明出现了错误单词。
key_value模型(通过key去找value,即<key, value>的键值对)
例如:翻译问题,英译汉或者汉译英(不能互译)

🏀性能分析

查找find是最能反应性能的了,因为插入和删除几乎很多操作都要用到查找。增删查的时间复杂度为O(h),h是树的高度,最坏的情况为O(N),没有修改是因为二叉搜索树的特性,修改了就不是二叉搜索树了
实际上,二叉搜索树在极端情况下是没办法保证效率的,因此由二叉搜索树又衍生出来了AVL树、红黑树等,它们对二叉搜索树的高度进行了优化,使得二叉搜索树非常接近完全二叉树,因此对于这些树来说,它们的效率是可以达O(logN)的。

💬一些小点

  • 二叉搜索树排序是天然的去重
  • 类里面调用递归不好调,因为默认成员函数一般不传参,但是递归调用一般要传参,可是我们外面调用必须要获取私有成员root,解决办法:写一个get_root或者友元,第三种:套一层,用无参的调用有参的
  • 从实际用途的角度来说写非递归好一点,因为深了会溢出的可能,思想上偏向递归的话就写递归,不然还是循环好一点
    求高度什么的相关实现和二叉树基本一致,详情看:你知道有一种树叫二叉树吗?
  • 平衡树和搜索树只是效率上的差别

在这里插入图片描述

觉得还不错的铁汁点赞关注一下吧😀

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

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

相关文章

spring boot 八:SpringBoot响应返回xml数据

spring boot 八&#xff1a;SpringBoot响应返回xml数据 1 前言 根据DispatcherServlet源码分析&#xff0c;研究SpringBoot的Controller返回xml数据的一些方法&#xff0c;包含单独配置和全局配置返回xml数据两种方式。 依赖的SpringBoot版本&#xff1a; <parent>&l…

u盘有病毒怎么办?修复U盘,3个方法解决

U盘和外部的驱动器相比&#xff0c;它的体积更小&#xff0c;携带更加方便&#xff0c;可以轻松地与他人分享文件。虽然U盘使用很方便&#xff0c;但是有时会出现中病毒的情况。u盘有病毒怎么办&#xff1f;如果您也受到此问题的影响&#xff0c;我们可以提供一种有效的方法来修…

物联网架构实例—Ubuntu 安装Redis

1.准备更新apt-get源sudo apt-get update2.安装执行Redis 安装命令sudo apt-get install redis-server3.检查安装状态sudo /etc/init.d/redis-server status查看Redis运行进程ps -aux|grep redis4.将Redis添加到服务器启动项修改/etc/rc.localvim /etc/rc.local将下面的命令加到…

阿里云办公安全产品专家高传贵:零信任,让全球办公安全更简单

2022 年 8 月 30 日&#xff0c;阿里云用户组&#xff08;AUG&#xff09;第 9 期活动在北京举办。活动现场&#xff0c;阿里云办公安全产品专家高传贵&#xff0c;向参会企业代表分享了零信任&#xff0c;让全球办公安全更简单。本文根据演讲内容整理而成。 大家下午好。我今天…

内部类导致的内存泄漏

前两天刷文章偶然翻到一篇因使用非静态内部类时导致内存泄漏的问题,出于好奇自己也动手一试 什么叫内存泄漏 内存泄漏&#xff08;Memory Leak&#xff09;是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放&#xff0c;造成系统内存的浪费&#xff0c;导致程序…

WuThreat首个发布全球领先的身份安全云产品ITDR Cloud

随着数字化、人工智能&#xff0c;公有/私有云&#xff0c;物联网络及5G等技术的全面普及和迭代更新&#xff0c;身份管理建设作为企业重要的基础设施。然而现在黑客攻击手段复杂多样&#xff0c;在历年的实战攻防演习中有大量的应用系统与基础设施的的身份入口被攻破&#xff…

【从零开始学习深度学习】38. Pytorch实战案例:梯度下降、随机梯度下降、小批量随机梯度下降3种优化算法对比【含数据集与源码】

本文将使用一个来自NASA测试不同飞机机翼噪音的数据集&#xff0c;通过梯度下降、随机梯度下降、小批量随机梯度下降这3种优化算法进行模型训练&#xff0c;比较3种训练结果的差异。 目录1. 梯度下降、随机梯度下降、小批量随机梯度下降区别2. 读取训练数据3. 从零实现3种梯度算…

多线程与高并发(16)——线程池原理(ThreadPoolExecutor源码)

本文从ThreadPoolExecutor源码来理解线程池原理。 ThreadPoolExecutor使用了AQS、位操作、CAS操作等。在看这篇文章之前&#xff0c;需要具备以下知识&#xff1a; 多线程与高并发&#xff08;6&#xff09;——CAS详解&#xff08;包含ABA问题&#xff09; 多线程与高并发&…

腾讯三面:进程写文件过程中,进程崩溃了,文件数据会丢吗?

进程写文件&#xff08;使用缓冲 IO&#xff09;过程中&#xff0c;写一半的时候&#xff0c;进程发生了崩溃&#xff0c;会丢失数据吗&#xff1f; 答案&#xff0c;是不会的。 因为进程在执行 write &#xff08;使用缓冲 IO&#xff09;系统调用的时候&#xff0c;实际上是…

企业宣传片制作配音,我们该从哪里找?

优秀的品质的配音是制作优质企业视频必不可少的硬件条件。因此&#xff0c;许多公司视频配音或旁白声音是由专门从事配音行业的人员配音的。 首先是在宣传视频中配音的作用 1.宣传视频的配音为您建立企业形象 2.宣传视频的配音将为您打开市场 3.宣传视频的配音将使您的宣传…

深入理解Synchronized

Synchronized 底层原理 Synchronized的语义底层是通过一个 Monitor 的对象来完成&#xff0c;其实wait/notify等方法也依赖于 Monitor 对象&#xff0c;这就是为什么只有在同步的块中&#xff0c;拿到锁之后&#xff0c;才能调用wait/notify等方法&#xff0c;否则会抛出java.…

AI助力产品质量检验,基于YOLO实现瓷砖缺陷问题检测识别

在我之前的文章中也写过很多关于生产质检相关的实践文章&#xff0c;一直觉得这块是比较有意思的应用方向&#xff0c;做出来的模型能够以一种更加直观贴切的形式展现出来&#xff0c;瓷砖缺陷问题检测识别也是一个比较老的话题了&#xff0c;今天还是想拿出来具体实践做一下&a…

Golang.org/x库初探1——image库

Golang有一个很有意思的官方库&#xff0c;叫golang.org/x&#xff0c;x可能是extends&#xff0c;experimental&#xff0c;总之是一些在官方库中没有&#xff0c;但是又很有用的库。最近花点时间把这里有用的介绍一下。 Image库 提供更多的图像格式 golang.org/x/image库整…

Linux 网络驱动

1. linux 里面驱动三巨头&#xff1a;字符设备驱动、块设备驱动、网络设备驱动。2.嵌入式网络硬件分为两部分&#xff1a; MAC 和 PHY。如果一款芯片数据手册说自己支持网络&#xff0c;一般都是说的这款 SOC 内置 MAC&#xff0c; MAC 类似 I2C 控制器、SPI 控制器一样的外设。…

Java三大技术平台是什么?

为了使软件开发人员、服务提供商和设备生产商可以针对特定的市场进行开发&#xff0c;SUN公司将Java划分为三个技术平台&#xff0c;它们分别是 JavaSE、 JavaEE和 JavaME。Java SE( Java Platform Standard Edition)标准版&#xff0c;是为开发普通桌面和商务应用程序提供的解…

零宽断言正则表达式替换方案

一、背景 safari浏览器不支持零宽断言正则表达式 二、解决方案 使用其他正则替换零宽断言正则&#xff08;包含&#xff1a;(?<)正向肯定预查、(?<!)正向否定预查、(?)反向肯定预查、(?!)反向否定预查&#xff09; 三、涉及场景 1、仅校验&#xff0c;不取值 如表…

首汽约车驶向极速统一之路!出行平台如何基于StarRocks构建实时数仓?

作者&#xff1a;王满&#xff0c;高级数据架构工程师首汽约车&#xff08;以下简称 “首约”&#xff09;是首汽集团为响应交通运输部号召&#xff0c;积极拥抱互联网&#xff0c;推动传统出租车行业转型升级&#xff0c;加强建设交通强国而打造的网约车出行平台。 在用车服务…

KernelSU: 内核 ROOT 方案, KernelSU KernelSU KernelSU 新的隐藏root防止检测 封号方案

大约一年多以前&#xff0c;我在一篇讲Android 上 ROOT 的过去、现在和未来https://mp.weixin.qq.com/s?__bizMjM5Njg5ODU2NA&mid2257499009&idx1&sn3cfce1ea7deb6e0e4f2ac170cffd7cc1&scene21#wechat_redirect 的文章中提到&#xff1a; 我认为&#xff0c;随…

三菱FX5U 多个表格运行指令 DRVTBL

简述该指令可以用GX Works3预先在表格数据中设定的控制方式的动作&#xff0c;&#xff08;连续或步进&#xff09; 执行多行。 本文演示了步进执行多行。指令解释2.1梯形图中的指令第一个参数&#xff1a;输出脉冲的轴编号 &#xff0c;K1,K2,K3,K4... 第二个参数&#xff1a;…

ESP8266 Windows开发环境搭建(IDE1.5)好用不骗人

最近一个项目需要用ESP8266&#xff0c;找了很多文章进行环境搭建编译都很问题&#xff0c;不是make Menuconfig 不出来&#xff0c;就是编译报错&#xff0c;现总结如下。 我在自己电脑上没弄出来&#xff0c;就安装了一个虚拟机很干净的环境没有其它开发环境影响。 提前去官…