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

在这里插入图片描述

🔥个人主页: 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模型,最后分析了二叉搜索树的性能,同时介绍了一些维持树平衡的解决方案。感谢大家的支持♥

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

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

相关文章

【中项】系统集成项目管理工程师-第2章 信息技术发展-2.1信息技术及其发展-2.1.1计算机软硬件与2.1.2计算机网络

前言&#xff1a;系统集成项目管理工程师专业&#xff0c;现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试&#xff0c;全称为“全国计算机与软件专业技术资格&#xff08;水平&#xff09;考试”&…

车载音视频App框架设计

简介 统一播放器提供媒体播放一致性的交互和视觉体验&#xff0c;减少各个媒体应用和场景独自开发的重复工作量&#xff0c;实现媒体播放链路的一致性&#xff0c;减少碎片化的Bug。本文面向应用开发者介绍如何快速接入媒体播放器。 主要功能&#xff1a; 新设计的统一播放U…

Windows FFmpeg 开发环境搭建

FFmpeg 开发环境搭建 FFmpeg命令行环境搭建使用FFmpeg官方编译的库Windows编译FFmpeg1. 下载[msys2](https://www.msys2.org/#installation)2. 安装完成之后,将安装⽬录下的msys2_shell.cmd中注释掉的 rem set3. 修改pacman 镜像源并安装依赖4. 下载并编译源码 FFmpeg命令行环境…

【C++】 string类的模拟实现

目录 一、我们先创建三个文件分别为 String.h&#xff08;声明&#xff09;、String.cpp&#xff08;定义&#xff09;、teat.cpp&#xff08;测试&#xff09; 二、成员函数 构造函数与析构函数 &#x1f31f;string() &#x1f31f;string(const char* str) &#x1f…

c++基础(类和对象中)(类的默认成员函数)

目录 一.构造函数&#xff08;类似初始化&#xff09; 1.概念 2.构造函数的特点 二.析构函数&#xff08;类似 销毁对象/空间&#xff09; 三.拷贝构造函数(类似复制粘贴的一种 初始化 ) 1.概念&#xff1a; 2.拷贝构造的特点&#xff1a; 四.赋值运算符重载&#xff08…

【MATLAB实战】基于UNet的肺结节的检测

数据&#xff1a; 训练过程图 算法简介&#xff1a; UNet网络是分割任务中的一个经典模型,因其整体形状与"U"相似而得名,"U"形结构有助于捕获多尺度信息,并促进了特征的精确重建&#xff0c;该网络整体由编码器,解码器以及跳跃连接三部分组成。 编码器由…

数据结构第一讲:复杂度

数据结构第一讲&#xff1a;复杂度 1.数据结构前言1.1什么是数据结构1.2算法 2.算法效率2.1复杂度的概念 3.时间复杂度3.1案例13.2案例23.3案例33.4案例43.5案例53.6案例63.7案例7 4.空间复杂度4.1案例14.2案例2 5.常见复杂度对比6.轮转数组题目分析6.1优化16.2优化2 博客简介&…

走进MongoDB--update和remove操作

操作任务 1.复制集合s&#xff0c;新集合名称为sbak。在sbak中完成以下操作 2.修改学号为1001同学的年龄为20岁 3.所有男同学的文档增加addr属性&#xff0c;值为“hebei” 4.删除wangwu同学的年龄属性 5.修改zhangsi同学的年龄属性为20&#xff0c;如果没有满足条件的则插…

DNS域名解析轮询过程图解

Q1: 先从本地浏览器/系统DNS缓存查找是否有解析记录 Q2:从本地hosts文件查找解析记录 Q3:如果前2步没有找到本地解析记录 hosts也无记录就从本地域名服务器查找 Q4:本地域名服务器区域缓存记录查找 Q5:查找转发器是否有 Q6--Q7:查找根com顶级域名服务器 Q8: 查找test.com…

力扣 字符串章节 344反转字符串

编写一个函数&#xff0c;将输入字符翻转过来,原地修改 思路 字符串用数组的形式存储&#xff0c;数组长度分奇数和偶数两种 如果长度是奇数&#xff0c;循环到str.size()/2&#xff0c;中间值不动 如果长度是偶数&#xff0c;循环到str.size()/2&#xff0c;全部参与反转 …

【简单介绍Gitea】

🎥博主:程序员不想YY啊 💫CSDN优质创作者,CSDN实力新星,CSDN博客专家 🤗点赞🎈收藏⭐再看💫养成习惯 ✨希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进步! Gitea.🍀 Gitea是一个开源的,轻量级的代码托管解决方案,它是…

收到赵健老师的限量签名书,开心

收到赵老师的亲笔签名&#x1f4d6;&#xff0c;开心一下下[愉快]&#xff0c;由外而内&#xff0c;首先是我喜欢的线装书&#xff0c;展开阅读舒适&#xff0c;手感友好&#xff0c;纸张更是很讲究&#xff0c;密度很高也很温润&#xff0c;应该是进口纸&#xff0c;每个对页的…

Linux主机添加ipv6地址

一、添加网卡ipv6地址 通过命令行添加 ip add add 2001:db8:0:1::102/64 dev ens160 通过编辑/etc/sysconfig/network-scripts/目录下的ifcfg-配置文件添加 TYPEEthernet BOOTPROTOdhcp # 或者指定为 "static" 如果你想要静态配置 DEFROUTEyes IPV4_FAILURE_FAT…

Uniapp基础篇(持续更新)

1. Uni-app常用内置组件 view 视图容器 scroll-view 可滚动视图区域&#xff0c;用于区域滚动。需注意在webview渲染的页面中&#xff0c;区域滚动的性能不及页面滚动。 swiper 滑块视图容器。一般用于左右滑动或上下滑动&#xff0c;比如banner轮播图。 image uniapp官方iam…

萝卜快跑无人出租车是有人远程代驾? 客服:没有人操控

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 更多资源欢迎关注 近期“萝卜快跑”无人驾驶网约车相关话题引发网友热议。 有网传图片显示&#xff0c;萝卜快跑机器人智控中心&#xff0c;有真人坐在带有方向盘的屏幕前&#xff1b; 有网友认为所谓的无人网约车&am…

Unty 崩溃问题(Burst 1.8.2)

错误代码&#xff1a; Assertion failed on expression: exception SCRIPTING_NULL UnityEngine.StackTraceUtility:ExtractStackTrace () Unity.Burst.BurstCompiler:SendRawCommandToCompiler (string Unity版本&#xff1a;2021.3.17F1&#xff0c;Burst 1.8.2 表现&…

黑马程序员瑞吉外卖Day6小程序空白无显示

做项目时出现问题之druid连接池报错 报错discard long time none received connection. , jdbcUrl : jdbc:mysql://localhost:3306/sky_take_out?serverTimezoneAsia/Shanghai&useUnicodetrue&characterEncodingutf-8&zeroDateTimeBehaviorconvertToNull&use…

Typescript 实现倒计时功能 useCountdown

效果图 代码块 useCountdown.ts import {onUnmounted, reactive, ref, watch} from "vue";type union = days | hours | minutes | seconds | millisecondsexport type Remains = Record<union, number>;/*** 创建一个倒计时** 用法*/ export const useCount…

“七夕之夜”:艺术家刘兰芳、陶玉玲等相约临沂蒙山沂水大剧院

人海信息网山东讯&#xff08;冯爱云、徐婉桦&#xff09; 近日&#xff0c;记者从北京御龙古今艺术剧院山东分院获悉&#xff0c;由该院承办、中国文艺研究会指导、北京御龙古今艺术剧院主办的2024御龙“七夕之夜”演唱会&#xff0c;将在浪漫的七夕前夜——8月9日晚&#xf…

视频压縮大小不影响画质,视频压缩大小不影响画质的软件

在数字化浪潮推动下&#xff0c;视频制作和分享已成为我们生活的一部分。然而&#xff0c;视频文件体积过大常常让分享和存储变得头疼。今天&#xff0c;我们就来聊聊如何在苹果电脑上压缩视频文件大小&#xff0c;让你的视频瞬间瘦身&#xff01; 方法一、 1.下载并安装视频压…