【数据结构进阶】二叉搜索树

news2024/9/23 5:35:28

在这里插入图片描述

🔥个人主页: Forcible Bug Maker
🔥专栏: C++ || 数据结构

目录

  • 🌈前言
  • 🔥二叉搜索树
  • 🔥 二叉搜索树的实现
    • ==Insert(插入)==
    • ==find(查找)==
    • ==erase(删除)==
    • ==destroy(析构)==
    • ==InOrder(中序遍历)==
    • ==拷贝构造==
  • 🔥二叉搜索树的应用
  • 🔥二叉搜索树的性能
  • 🌈结语

🌈前言

本篇博客主要内容:二叉搜索树的介绍及自实现

基础的二叉树在前面的C数据结构阶段已经讲过(初阶数据结构之—二叉树链式结构)。之前因为用C语言的话,实现更高级数据结构比较困难,所以并没有往后展开。到了现在,已经有了一定的C++功底,就可以开启我们数据结构进阶部分的内容了。对于二叉搜索树的特性了解,有助于后续更好的理解map和set的特性。本节课作为学习更高阶数据结构的基础,对后续学习来说至关重要。

🔥二叉搜索树

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

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

二叉搜索树的中序遍历是有序的。

在这里插入图片描述

🔥 二叉搜索树的实现

在这里插入图片描述
以下是需要实现的二叉搜索树的头文件内容

#pragma once
#include<iostream>

namespace ForcibleBugMaker
{
	template<class K>
	struct BSTreeNode
	{
		BSTreeNode<K>(const K& k = K())
			:_key(k)
			, _left(nullptr)
			, _right(nullptr)
		{}
		K _key;
		BSTreeNode<K>* _left;
		BSTreeNode<K>* _right;
	};

	template<class K>
	class BSTree
	{
		typedef BSTreeNode<K> Node;
	public:
		BSTree() = default;

		BSTree(const BSTree<K>& t);

		bool Insert(const K& key);
		
		Node* Find(const K& key);
		
		bool Erase(const K& key);
		
		~BSTree();
		
		void InOrder();
		
	private:
		Node* _root = nullptr;
	};
}

二叉搜索树的结点中有三个成员变量,分别是
_key:存储数据;_left:指向左子树;_right:指向右子树。将其在BSTree中typedef成Node方便后续使用。

Insert(插入)

二叉树的插入,主要考虑两种情况:

  1. 树为空,则直接新增结点,赋给root指针。
  2. 树不为空,按二叉搜索树性质查找插入位置,插入新结点,如key结点值存在,则插入失败。
    在这里插入图片描述
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;
	}
	if (parent->_key < key) parent->_right = new Node(key);
	else parent->_left = new Node(key);
	return true;
}

find(查找)

二叉搜索树的查找:

  1. 从根开始比较,比跟大则往右边走查找,比根小则往左边走查找。
  2. 最多查找高度次,走到空,还没找到,这个值不存在。
Node* 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 cur;
	}
	return nullptr;
}

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 {
			if (cur == _root && cur->_left == nullptr) {
				_root = cur->_right;
				delete cur;
				return true;
			}
			else if (cur == _root && cur->_right == nullptr) {
				_root = cur->_left;
				delete cur;
				return true;
			}
			if (cur->_left == nullptr) {
				if (parent->_right == cur)
					parent->_right = cur->_right;
				else
					parent->_left = cur->_right;
				delete cur;
			}
			else if (cur->_right == nullptr) {
				if (parent->_right == cur)
					parent->_right = cur->_left;
				else
					parent->_left = cur->_left;
				delete cur;
			}
			else {
				Node* rightMinP = cur;
				Node* rightMin = cur->_right;
				while (rightMin->_left) {
					rightMinP = rightMin;
					rightMin = rightMin->_left;
				}
				cur->_key = rightMin->_key;
				cur->_value = rightMin->_value;
				if (rightMinP == cur)
					rightMinP->_right = rightMin->_right;
				else
					rightMinP->_left = rightMin->_right;
				delete rightMin;
			}
			return true;
		}
	}
	return false;
}

destroy(析构)

二叉树的析构需要传入根结点,通过后序遍历递归实现,但是从外界无法访问对象内部的私有成员_root。所以咱们可以实现一个工具函数,用来帮助完成二叉搜索树的析构。

~BSTree()
{
	Destroy(_root);
	_root = nullptr;
}

void Destroy(Node* root)
{
	if (root == nullptr)return;
	Destroy(root->_left);
	Destroy(root->_right);
	delete root;
}

InOrder(中序遍历)

逻辑跟析构一样。中序遍历下来的key是有序的。

void InOrder()
{
	_InOrder(_root);
	std::cout << std::endl;
}

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

拷贝构造

本质上就是实现一次二叉树的深拷贝,也是嵌套了一个递归。

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

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

🔥二叉搜索树的应用

像我们刚刚实现的,只存一个数据,是典型的K模型;如果存两个数据,那就是KV模型。

  1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
    比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
  • 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
  1. **KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。**该种方式在现实生活中非常常见:
  • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
  • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。

在以上实现K模型的基础上,实现KV模型无非就是让结点多存储一个元素,给模板增添一个类型,具体实现代码如下:

#pragma once
#include<iostream>

namespace ForcibleBugMaker
{
	template<class K, class V>
	struct BSTreeNode
	{
		BSTreeNode<K, V>(const K& k = K(), const V& v = V())
			:_key(k)
			, _value(v)
			, _left(nullptr)
			, _right(nullptr)
		{}
		K _key;
		V _value;
		BSTreeNode<K, V>* _left;
		BSTreeNode<K, V>* _right;
	};

	template<class K, class V>
	class BSTree
	{
		typedef BSTreeNode<K, V> Node;
	public:
		BSTree() = default;

		BSTree(const BSTree<K, V>& t)
		{
			_root = _Copy(t._root);
		}

		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){
				if (cur->_key < key) {
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key) {
					parent = cur;
					cur = cur->_left;
				}
				else return false;
			}
			if (parent->_key < key) parent->_right = new Node(key, value);
			else parent->_left = new Node(key, value);
			return true;
		}

		Node* 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 cur;
			}
			return nullptr;
		}

		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 == _root && cur->_left == nullptr) {
						_root = cur->_right;
						delete cur;
						return true;
					}
					else if (cur == _root && cur->_right == nullptr) {
						_root = cur->_left;
						delete cur;
						return true;
					}
					if (cur->_left == nullptr) {
						if (parent->_right == cur)
							parent->_right = cur->_right;
						else
							parent->_left = cur->_right;
						delete cur;
					}
					else if (cur->_right == nullptr) {
						if (parent->_right == cur)
							parent->_right = cur->_left;
						else
							parent->_left = cur->_left;
						delete cur;
					}
					else {
						Node* rightMinP = cur;
						Node* rightMin = cur->_right;
						while (rightMin->_left) {
							rightMinP = rightMin;
							rightMin = rightMin->_left;
						}
						cur->_key = rightMin->_key;
						cur->_value = rightMin->_value;
						if (rightMinP == cur)
							rightMinP->_right = rightMin->_right;
						else
							rightMinP->_left = rightMin->_right;
						delete rightMin;
					}
					return true;
				}
			}
			return false;
		}

		~BSTree()
		{
			Destroy(_root);
			_root = nullptr;
		}
		void InOrder()
		{
			_InOrder(_root);
			std::cout << std::endl;
		}
	private:
		Node* _Copy(Node* root)
		{
			if (root == nullptr)return nullptr;
			Node* newRoot = new Node(root->_key, root->_value);
			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;
		}

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

🔥二叉搜索树的性能

二叉搜索树(Binary Search Tree, BST)的性能主要取决于其结构。理想情况下,二叉搜索树是一个平衡树,其中每个节点的左子树只包含小于节点值的元素,右子树只包含大于节点值的元素,且左、右子树的高度大致相等。然而,在实际应用中,由于插入和删除操作的随机性,二叉搜索树可能会退化为链表状结构(即所有节点都偏向一侧),这会导致其性能急剧下降
在这里插入图片描述
时间复杂度:

  • 搜索(Search):在平衡的二叉搜索树中,搜索操作的时间复杂度为O(log n),其中n是树中节点的数量。这是因为每次递归或迭代都排除了一半的搜索空间。但在最坏的情况下(树退化为链表),时间复杂度会退化为O(n)
  • 插入(Insert)和删除(Delete):同样,在平衡的二叉搜索树中,插入和删除操作的时间复杂度也是O(log n)。但在最坏的情况下,时间复杂度会退化为O(n)

空间复杂度:

  • 二叉搜索树的空间复杂度主要由树中存储的节点数量决定,为O(n)

优化:
为了保持二叉搜索树的平衡,避免性能退化,可以使用各种自平衡二叉搜索树的数据结构,如:

  • AVL树:任何节点的两个子树高度最大差的绝对值小于二,通过旋转操作来维持平衡。
  • 红黑树:一种自平衡二叉查找树,通过特定的操作和性质(如节点着色和树的高度限制)来保持树的平衡。
  • B树和B+树:这些树不仅用于保持数据的排序,还优化了磁盘读写操作,常用于数据库和文件系统中。

这些平衡二叉树也将会是我们未来在数据结构进阶中主要展开的内容。

🌈结语

本篇博客主要讲了二叉搜索树及其实现,K模型和KV模型,最后分析了二叉搜索树的性能,同时介绍了一些维持树平衡的解决方案。感谢大家的支持♥

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

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

相关文章

毕业/期刊论文发表必备:YOLOv5 / v7 / v8 /v10算法网络结构图【文末提供原型文件下载地址】

前言:Hello大家好,我是小哥谈。同学们在写YOLO算法相关毕业论文/期刊论文的时候,不可避免的会用到相关版本的网络结构图,曾有很多小伙伴私信我索要原型文件,本文就给大家提供YOLOv5/v7/v8/v10版本算法网络结构图及原型文件下载地址。🌈 目录 🚀1.网络结构图 �…

Fiddler 导出请求为curl格式

来自:https://www.cnblogs.com/yudongdong/p/15418181.html Fiddler 下载地址: https://downloads.getfiddler.com/fiddler-classic/FiddlerSetup.5.0.20243.10853-latest.exe 这段代码加到类中 public static RulesOption("关闭请求体转代码", "生成代码&qu…

简单页表和多级页表

地址转换(Address Translation) 内存需要被分成固定大小的页(Page)然后再通过虚拟内存地址(Virtual Address) 到物理内存地址(Physical Address) 的地址转换(Address Translation)才能到达实际存放数据的物理内存位置 简单页表 页表的概念 想要把虚拟内存地址&#xff0c;映…

ip地址是电脑还是网线决定的

在数字化时代的浪潮中&#xff0c;网络已经成为了我们日常生活和工作不可或缺的一部分。当我们谈论网络时&#xff0c;IP地址无疑是一个核心的概念。然而&#xff0c;关于IP地址的分配和决定因素&#xff0c;很多人可能存在误解。有些人认为IP地址是由电脑决定的&#xff0c;而…

pytorch 46 将ASpanFormer模型导出onnx运行

ASpanFormer是一个2022年8月份发布的算法,其主要步骤与LoFTR模型类似,因此无法导出为onnx模型。根据ASpanFormer论文中的数据与效果图,可以确定AsPanFormer是可以作为一个比SP+SG更为有效的方案,其在标准数据集上的效果优于SP+SG,在速度上远超SP+SG,与LoFTR接近;在预测点…

C语言:静态库和动态(共享)库

相关阅读 C语言https://blog.csdn.net/weixin_45791458/category_12423166.html?spm1001.2014.3001.5482 在软件开发中&#xff0c;库&#xff08;Library&#xff09;是一个至关重要的概念。它们是由函数和数据的集合构成&#xff0c;用于实现特定的功能&#xff0c;供其他程…

测试——性能测试

内容大纲: 常见的性能问题 性能测试是什么 性能测试和功能测试之间的区别 为什么要进行性能测试 常见的性能指标及性能测试专业术语 性能测试分类 1. 常见的性能问题 系统内部以及软件的代码实现: 资源泄漏&#xff0c;包括内存泄漏。CPU使用率达到100%&#xff0c;系统被锁定…

Vue3组件通信

1、props 1.1 父传子 父组件&#xff1a;通过属性在子组件标签传递 子组件&#xff1a;通过defineProps接收 1.2 子传父 1.父组件先给子组件传递一个函数 2.子组件接收此参数&#xff08;函数&#xff09;&#xff0c;并在合适的时机调用此函数&#xff0c;通过函数的参数&…

SEO之网站结构优化(四)

初创企业搭建网站的朋友看1号文章&#xff1b;想学习云计算&#xff0c;怎么入门看2号文章谢谢支持&#xff1a; 1、我给不会敲代码又想搭建网站的人建议 2、新手上云 4、清晰导航 清晰的导航系统是网站设计的重要目标&#xff0c;对网站信息架构、用户体验影响重大。SEO也越来…

Hadoop高可用集群搭建及API调用

NameNode HA 背景 在Hadoop1中NameNode存在一个单点故障问题,如果NameNode所在的机器发生故障,整个集群就将不可用(Hadoop1中虽然有个SecorndaryNameNode,但是它并不是NameNode的备份,它只是NameNode的一个助理,协助NameNode工作,SecorndaryNameNode会对fsimage和edits文…

【BUG】已解决:OSError: [Errno 22] Invalid argument

已解决&#xff1a;OSError: [Errno 22] Invalid argument 目录 已解决&#xff1a;OSError: [Errno 22] Invalid argument 【常见模块错误】 错误原因&#xff1a; 解决方法如下&#xff1a; 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&…

基于luckysheet实现在线电子表格和Excel在线预览

概述 本文基于luckysheet实现在线的电子表格&#xff0c;并基于luckyexcel实现excel文件的导入和在线预览。 效果 实现 1. luckysheet介绍 Luckysheet &#xff0c;一款纯前端类似excel的在线表格&#xff0c;功能强大、配置简单、完全开源。 官方文档在线Demo 2. 实现 …

配置单区域OSPF

目录 引言 一、搭建基础网络 1.1 配置网络拓扑图如下 1.2 IP地址表 二、测试每个网段都能单独连通 2.1 PC0 ping通Router1所有接口 2.2 PC1 ping通Router1所有接口 2.3 PC2 ping通Router2所有接口 2.4 PC3 ping通Router2所有接口 2.5 PC4 ping通Router3所有接口 2.…

力扣 28找到字符串中第一个匹配项的下标 KMP算法

思路&#xff1a; 朴素匹配有很多步骤是多余的 KMP算法能够避免重复匹配 KMP算法主要是根据子串生成的next数组作为回退的依据&#xff0c;它记录了模式串与主串(文本串)不匹配的时候&#xff0c;模式串应该从哪里开始重新匹配。 这里讲一下为什么用模式串的最大公共前后缀…

基于python的当当二手书数据分析与可视化系统设计与实现

1.1 研究背景及现状 1.1.1 研究背景 生态文明建设是我国的基本国情之一&#xff0c;资源利用作为应该重要的环节[1]。然而随着大学校园内掀起倡导的低碳环保热潮&#xff0c;高校学生教材及其他书籍的目前的处理方式已被大多人所关注[2]。从循环利用资源的角度出发[3]&…

封装MAVSDK为JAR包并导出给其它Android工程用完整示例

效果: 未解锁状态 已执行解锁指令 已执行起飞指令 飞行中 已执行降落指令 已执行返航指令 实现步骤: 1.准备PX4容器并启动:

Java项目中整合多个pdf合并为一个pdf

一、Java项目中整合多个pdf合并为一个pdf gitee笔记路径&#xff1a;https://gitee.com/happy_sad/drools一、依赖导入 <dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.6</version> …

AtCoder Beginner Contest 362

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;陈童学哦&#xff0c;彩笔ACMer一枚。 &#x1f3c0;所属专栏&#xff1a;Codeforces 本文用于记录回顾总结本彩笔的解题思路便于加深理解。 比赛题目地址&#xff1a;AtCoder Beginner Contest 362 …

【保卫花果山】游戏

游戏介绍 拯救花果山是一款玩家能够进行趣味闯关的休闲类游戏。拯救花果山中玩家需要保护花果山的猴子&#xff0c;利用各种道具来防御妖魔鬼怪的入侵&#xff0c;游戏中玩家需要面对的场景非常的多样&#xff0c;要找到各种应对敌人的方法。拯救花果山里玩家可以不断的进行闯…

vue基于Cookies实现记住密码自动登录功能

vue基于Cookies实现记住密码自动登录功能 Cookies 和localStorage存储比对 实现记住密码功能时&#xff0c;使用 Cookies 和使用 localStorage 各有其优势和考虑因素&#xff0c;具体需要取决于需求和安全考量&#xff1a; 1、Cookies 的优势&#xff1a; 广泛支持&#x…