C++二叉搜索树剖析

news2024/9/22 15:33:11

在这里插入图片描述


目录

  • 🍇二叉搜索树概念
  • 🍈二叉搜索树查找
  • 🍉二叉搜索树的插入
  • 🍊二叉搜索树的删除
  • 🍍二叉搜索树的查找、插入、删除实现
  • 🍋二叉搜索树的应用
  • 🥭二叉搜索树的性能分析
  • 🍓总结


🍇二叉搜索树概念

二叉搜索树,又称为二叉排序树,是一种特殊的二叉树。它要么是一棵空树,要么具有以下性质:

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

二叉搜索树的特点是可以快速地查找、插入和删除节点,因为它的节点按照大小关系排列,形成了一种有序结构。它常被用于实现关联数组、集合等数据结构,也是许多其他算法的基础。

在这里插入图片描述

🍈二叉搜索树查找

二叉搜索树的查找可以通过以下步骤进行:

  • 从根节点开始比较,将待查找的值与根节点的值进行比较,如果相等,则返回该节点;如果待查找的值比根节点的值大,则往右子树走查找,反之则往左子树走查找。

  • 重复上述步骤,直到找到待查找的值或者走到空节点仍未找到。如果走到空节点仍未找到,则说明该值不存在于二叉搜索树中。

由于二叉搜索树的节点按照大小关系排列,因此查找的时间复杂度为O(log n),其中n为二叉搜索树中节点的个数。

在这里插入图片描述

在这里插入图片描述

🍉二叉搜索树的插入

二叉搜索树的插入可以通过以下步骤进行:

  1. 如果二叉搜索树为空,则直接将新节点作为根节点,赋值给root指针。

  2. 如果二叉搜索树不为空,则按照二叉搜索树的性质,从根节点开始比较待插入节点的值和当前节点的值的大小关系,如果待插入节点的值比当前节点的值小,则往左子树走查找,如果左子树为空,则将待插入节点作为当前节点的左子节点;

  3. 如果待插入节点的值比当前节点的值大,则往右子树走查找,如果右子树为空,则将待插入节点作为当前节点的右子节点。

  4. 如果待插入节点的值与当前节点值相等,表示已经存在这个值,插入失败。

  5. 重复上述步骤,直到找到合适的插入位置为止。

二叉搜索树的插入时间复杂度为O(log n),其中n为二叉搜索树中节点的个数。

在这里插入图片描述

🍊二叉搜索树的删除

二叉搜索树的删除可以通过以下步骤进行:

首先查找待删除的节点是否在二叉搜索树中,如果不存在,则直接返回;否则,进入下一步操作。

根据待删除节点的情况,进行以下处理:

  1. 如果待删除节点是叶子节点,则直接删除该节点即可。

  2. 如果待删除节点只有左子树或者右子树,则将待删除节点的父节点指向待删除节点的子节点,然后删除待删除节点。

  3. 如果待删除节点既有左子树又有右子树,则需要找到它的中序遍历下的后继节点(即右子树中的最小节点),将后继节点的值赋给待删除节点,然后将待删除节点的右指向后继节点的右,然后删除后继节点。

重复上述步骤,直到完成待删除节点的删除。
需要注意的是,二叉搜索树的删除操作可能会影响树的结构,因此需要对树进行平衡操作,以保证二叉搜索树的性质不被破坏。

二叉搜索树的删除时间复杂度为O(log n),其中n为二叉搜索树中节点的个数。

在这里插入图片描述

🍍二叉搜索树的查找、插入、删除实现

  1. 二叉搜索树节点的定义
template<class k>
class BStreeNode
{
public:
	BStreeNode(const k& key)
		:_key(key), _left(nullptr), _right(nullptr)
	{}

	k _key;
	BStreeNode* _left;
	BStreeNode* _right;
};

二叉搜索树节点的定义有以下成员:

  • key:表示节点的关键码,用于比较节点的大小关系,通常是一个模板类型,可以是整型、字符型、字符串、自定义类型等。

  • left:表示节点的左子节点,通常是一个指向BStreeNode类型的指针,如果节点没有左子节点,则该指针为空指针nullptr。

  • right:表示节点的右子节点,通常是一个指向BStreeNode类型的指针,如果节点没有右子节点,则该指针为空指针nullptr。

  1. 二叉搜索树的定义
template<class k>
class BStree
{
	typedef BStreeNode<k> Node;
public:
	Node* find(const k& key);
	bool insert(const k& key);
	bool erase(const k& key);	
	void InOrder();
private:
	Node* _root = nullptr;
	void _InOrder(const Node* root);
};

二叉搜索树的定义包括以下成员:

  • Node:表示二叉树的节点,通常是一个模板类,包括节点的关键码、左子节点、右子节点等成员。

  • find():用于在二叉树中查找指定关键码的节点,如果找到,则返回该节点的指针;否则返回空指针nullptr。

  • insert():用于向二叉树中插入一个新节点,如果插入成功,则返回true;否则返回false。

  • erase():用于从二叉树中删除指定关键码的节点,如果删除成功,则返回true;否则返回false。

  • InOrder():用于对二叉树进行中序遍历,按照节点的关键码从小到大输出节点的值。

  1. 二叉搜索树查找实现
	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;
	}

二叉搜索树的查找操作可以通过比较节点的键值来实现。从根节点开始,若当前节点的键值小于要查找的键值,则继续在右子树中查找;若当前节点的键值大于要查找的键值,则继续在左子树中查找;若当前节点的键值等于要查找的键值,则返回当前节点。

上述代码实现了二叉搜索树的查找操作。从根节点开始,通过循环遍历整棵树,比较节点的键值,根据大小关系移动到左子树或右子树中,直到找到要查找的节点或遍历到叶子节点为止。如果找到了要查找的节点,则返回该节点指针;否则返回空指针。

  1. 二叉搜索树插入实现
	bool insert(const k& key)
	{
		//空树
		if (_root == nullptr)
		{
			Node* newnode = new Node(key);
			_root = newnode;
			return true;
		}

		//不为空
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (cur->_key < key)
				cur = cur->_right;
			else if (cur->_key > key)
				cur = cur->_left;
			else
				return false;
		}
		Node* newnode = new Node(key);
		parent->_key > key ? parent->_left = newnode : parent->_right = newnode;
		return true;
	}

二叉搜索树的插入操作可以通过比较节点的键值来实现。从根节点开始,若要插入的键值小于当前节点的键值,则继续在左子树中插入;若要插入的键值大于当前节点的键值,则继续在右子树中插入;若要插入的键值等于当前节点的键值,则返回插入失败。

上述代码实现了二叉搜索树的插入操作。首先判断树是否为空,如果为空,则直接将新节点作为根节点;否则,从根节点开始循环遍历整棵树,比较节点的键值,根据大小关系移动到左子树或右子树中,直到找到合适的插入位置或遍历到叶子节点为止。如果找到了合适的插入位置,则创建新节点并插入到该位置;否则返回插入失败。

  1. 二叉搜索树删除的实现
	bool erase(const k& key)
	{
		//查找该节点及其父亲节点
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
				
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}				
			else
				break;
		}
		if (cur == nullptr)
			return false;
		//为叶子节点
		else if (cur->_left == nullptr && cur->_right == nullptr)
			delete cur;
		//只有一个孩子
		else if ((cur->_left == nullptr && cur->_right) || (cur->_left && cur->_right == nullptr))
		{
			if (parent->_left == cur)
			{
				cur->_left == nullptr ? parent->_left = cur->_right : parent->_left = cur->_left;
			}
			else
			{
				cur->_left == nullptr ? parent->_right = cur->_right : parent->_right = cur->_left;
			}
			delete cur;
		}
		//有两个孩子
		else if (cur->_left && cur->_right)
		{
			Node* del = cur->_right;
			//找右子树最小节点
			while (del->_left)
			{
				del = del->_left;
			}
			//赋值
			cur->_key = del->_key;
			//待删除节点的右指向最小节点右
			cur->_right = del->_right;
			//删除最小节点
			delete del;
		}
		return true;
	}

二叉搜索树的删除操作比较复杂,需要考虑删除节点的情况分为三种:

  • 待删除节点为叶子节点:直接删除该节点即可。

  • 待删除节点只有一个孩子:将该节点的孩子节点接到该节点的父节点上,然后删除该节点。

  • 待删除节点有两个孩子:找到该节点的右子树中的最小节点,将其键值赋值给待删除节点,然后删除最小节点。

上述代码实现了二叉搜索树的删除操作。首先从根节点开始循环遍历整棵树,查找待删除节点及其父节点。如果没有找到待删除节点,则返回删除失败;否则根据待删除节点的情况进行不同的操作。如果待删除节点为叶子节点,则直接删除该节点;如果待删除节点只有一个孩子,则将孩子节点接到父节点上,然后删除该节点;如果待删除节点有两个孩子,则找到右子树中的最小节点,将其键值赋值给待删除节点,然后删除最小节点。最后返回删除成功。

  1. 二叉搜索树中序遍历实现
	void _InOrder(const Node* root)
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_key << ' ';
		_InOrder(root->_right);
	}

二叉搜索树的中序遍历是指按照节点键值的大小关系,先遍历左子树,再遍历根节点,最后遍历右子树。因为二叉搜索树的中序遍历结果是一个有序序列,因此可以使用中序遍历来实现对二叉搜索树的排序操作。

上述代码实现了二叉搜索树的中序遍历操作。首先判断当前节点是否为空,如果为空则返回;否则按照左子树、根节点、右子树的顺序递归遍历整棵树,并输出节点的键值。

🍋二叉搜索树的应用

  • K模型:K模型是指只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索的值。例如,可以以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树,在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

  • KV模型:KV模型是指每一个关键码key都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见,例如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;再比如统计单词次数,统计成功后,给定单词就可以快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。二叉搜索树可以用来实现KV模型,以key作为二叉搜索树的节点,value作为节点的值,通过比较key的大小关系来实现快速查找、插入和删除节点的操作。

将上述二叉搜索树修改为KV模型:

  • 二叉树节点定义示例代码
template<class K, class V>
class BStreeNode
{
public:
    BStreeNode(const K& key, const V& value)
        :_key(key), _value(value), _left(nullptr), _right(nullptr)
    {}

    K _key;
    V _value;
    BStreeNode* _left;
    BStreeNode* _right;
};
  • 中序输出代码:
    void _InOrder(const Node* root)
    {
        if (root == nullptr)
            return;
        _InOrder(root->_left);
        cout << root->_key << ":" << root->_value << endl;
        _InOrder(root->_right);
    }
  • 测试代码:
void test1()
{
    BStree<string, string> tree;
    tree.insert("apple", "苹果");
    tree.insert("banana", "香蕉");
    tree.insert("orange", "橘子");
    tree.insert("peach", "桃子");
    tree.insert("pear", "梨");
    tree.InOrder();
}

运行结果:

在这里插入图片描述

🥭二叉搜索树的性能分析

插入和删除操作都需要先进行查找操作,因此二叉搜索树的查找效率代表了二叉搜索树各个操作的性能。对于有n个节点的二叉搜索树,如果每个元素查找的概率相等,那么二叉搜索树的平均查找长度是节点在二叉搜索树的深度的函数,即节点越深,则比较次数越多。

需要注意的是,对于同一个关键码集合,如果各关键码插入的次序不同,可能会得到不同结构的二叉搜索树。因为二叉搜索树的结构取决于插入顺序,如果插入的顺序不同,可能会导致树的结构不同,进而影响树的查找效率。因此,在实际应用中,需要根据实际情况选择合适的插入顺序,以获得更好的性能。

在这里插入图片描述
最优情况下,二叉搜索树的高度为 log2N,每一次查找可以将搜索范围缩小一半,因此平均比较次数为 log2N。

最差情况下,二叉搜索树的高度为 (N-1),每一次查找只能将搜索范围缩小一层,因此平均比较次数为 (1+2+…+N)/N = (N+1)/2,约等于 N/2。这种情况发生在插入的数据是有序的时候,导致二叉搜索树退化为链表。此时可以使用平衡二叉树来解决这个问题。

🍓总结

二叉搜索树是一种非常常见的数据结构,它是一棵二叉树,其中每个节点都包含一个键值,且左子树的所有节点的键值小于当前节点的键值,右子树的所有节点的键值大于当前节点的键值。这种特定的结构使得二叉搜索树能够快速地进行查找、插入、删除等操作。

在实际应用中,二叉搜索树被广泛使用,如在数据库索引、编译器符号表、路由表等领域。对于一个包含n个节点的二叉搜索树,其查找、插入、删除的时间复杂度均为O(logn),这使得它在处理大数据量时具有很高的效率。

但是,二叉搜索树也存在一些问题。当数据集合中的元素是随机分布的时,二叉搜索树的性能是非常好的。但是,当数据集合中的元素是有序的时,二叉搜索树的性能会退化为O(n),这就是所谓的“不平衡问题”。为了解决这个问题,我们可以使用平衡二叉树、红黑树等数据结构来优化二叉搜索树。

此外,二叉搜索树在插入重复元素时也存在问题。如果我们简单地将重复元素插入到二叉搜索树中,那么查找和删除操作就会变得非常麻烦。为了解决这个问题,我们可以使用多重集合、哈希表等数据结构来处理重复元素。

总之,二叉搜索树是一种非常重要的数据结构,它能够快速地进行查找、插入、删除等操作,但是在实际应用中需要注意避免和解决一些问题,如不平衡问题、重复元素问题等。

文章总结不易,如果觉得有所帮助的话就👍,文中所有代码均放在gitee上,关注博主,持续更新中…

在这里插入图片描述

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

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

相关文章

Mac 创建和删除 Automator 工作流程,设置 Terminal 快捷键

1. 创建 Automator 流程 本文以创建一个快捷键启动 Terminal 的自动操作为示例。 点击打开 自动操作&#xff1b; 点击 新建文稿 点击 快速操作 选择 运行 AppleScript 填入以下内容 保存名为 “Open Terminal” 打开 设置 > 键盘&#xff0c;选择 键盘快捷键 以此选择 服…

Python(六十九)为什么要将元组设计成不可变序列

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

git bash 安装sdkadmin

1.下载相关安装包,复制到git 安装目录 D:\software\Git\mingw64\bin 2. 运行 curl -s "https://get.sdkman.io" | bash

心跳跟随的心形灯(STM32(HAL)+WS2812+MAX30102)

文章目录 前言介绍系统框架原项目地址本项目开发开源地址硬件PCB软件功能 详细内容硬件外壳制作WS2812级联及控制MAX30102血氧传感器0.96OLEDFreeRTOS 效果视频总结 前言 在好几年前&#xff0c;我好像就看到了焊武帝 jiripraus在纪念结婚五周年时&#xff0c;制作的一个心跳跟…

10 日志系统(下)

10 日志系统&#xff08;下&#xff09; 本文内容 日志系统分为两部分&#xff0c;其一是单例模式与阻塞队列的定义&#xff0c;其二是日志类的定义与使用。 本篇将介绍日志类的定义与使用&#xff0c;具体的涉及到基础API&#xff0c;流程图与日志类定义&#xff0c;功能实现…

Android 刷新与显示

目录 屏幕显示原理&#xff1a; 显示刷新的过程 VSYNC机制具体实现 小结&#xff1a; 屏幕显示原理&#xff1a; 过程描述&#xff1a; 应用向系统服务申请buffer 系统服务返回一个buffer给应用 应用开始绘制&#xff0c;绘制完成就提交buffer&#xff0c;系统服务把buffer数据…

第三章 CUDA编译器环境配置篇

cuda教程目录 第一章 指针篇 第二章 CUDA原理篇 第三章 CUDA编译器环境配置篇 第四章 kernel函数基础篇 第五章 kernel索引(index)篇 第六章 kenel矩阵计算实战篇 第七章 kenel实战强化篇 第八章 CUDA内存应用与性能优化篇 第九章 CUDA原子(atomic)实战篇 第十章 CUDA流(strea…

Fatal error, can‘t open config file ‘/myredis/redis.conf‘: No such file or directory

在学习Redis到主从复制部分&#xff0c;进行相关练习&#xff0c;基本过程如下 1.首先将redis.conf文件cp到自建myredis文件夹中&#xff0c;并配置不同端口号的redis.conf redisXXXX.confd的配置内容如下 &#xff1a; include /myredis/redis.conf pidfile /var/run/redis_…

压力测试与测试工具jmeter的介绍

目录 一、性能指标 二、jmeter &#xff08;一&#xff09;JMeter 安装 &#xff08;二&#xff09;JMeter 压测示例 1、添加线程组 2、添加 HTTP 请求 3、添加监听器 4、启动压测&查看分析结果 &#xff08;三&#xff09;JMeter Address Already in use 错误解决 压力测…

Flutter运行app时向logcat输出当前打开的界面路径且点击可跳转

当一个项目大了目录文件多了&#xff0c;我们往往会为了找到一个文件花费大量的时间和精力&#xff0c;为了快捷方便的调试我们的项目&#xff0c;我们往往需要在打开app运行的时候需要知道当前打开的界面的文件在哪儿&#xff0c;我们这个代码就能快捷的知道我们app正在打开的…

《HeadFirst设计模式(第二版)》第五章代码——单例模式

代码文件目录&#xff1a; 初始版本&#xff1a; package Chapter5_SingletonPattern.origin;/*** Author 竹心* Date 2023/8/5**/public class Singleton {private static Singleton uniqueInstance;private Singleton(){}public static Singleton getInstance(){if(uniqueIn…

2023牛客暑期多校训练营6-A Tree

2023牛客暑期多校训练营6-A Tree https://ac.nowcoder.com/acm/contest/57360/A 文章目录 2023牛客暑期多校训练营6-A Tree题意解题思路代码 题意 解题思路 最大价值和这个数据范围&#xff0c;一眼 d p dp dp。 直接在树上并不好处理&#xff0c;问题是如何有效转化、处理…

黑马程序员SpringMVC练手项目

目录 1、需求 2、项目准备 pom.xml SQL jdbc.properties log4j.properties applicationContext.xml spring-mvc.xml web.xml 3、工作流程 4、难点 项目已经上传到gitee&#xff1a;https://gitee.com/xzl-it/my-projects 1、需求 SpringMVC项目练习&#xff1a;数…

每日一题——反转单链表

反转单链表 题目链接 下面主要介绍两种方法&#xff1a; 方法一&#xff1a; 利用三个指针变量进行反转 具体过程如图所示&#xff1a; 注意&#xff1a;循环的结束的条件为cur NULL而不是next NULL 实现代码&#xff1a; struct ListNode* reverseList(struct ListNode* …

STL容器适配器 -- stack和queue(使用+实现)(C++)

stack和queue stackstack的介绍stack的使用stack的实现 queuequeue的介绍queue的使用queue的实现 deque简单介绍deque&#xff08;双端队列&#xff09;双开口连续打引号的原因 deque底层结构deque的迭代器封装结构&#xff08;复杂&#xff09;deque的优缺点 栈和队列数据结构…

LLM reasoners 入门实验 24点游戏

LLM reasoners Ber666/llm-reasoners 实验过程 实验样例24games&#xff0c;examples/tot_game24&#xff0c;在inference.py中配置使用代理和open ai的api key。 首先安装依赖 git clone https://github.com/Ber666/llm-reasoners cd llm-reasoners pip install -e .然后…

JVM入门到精通

一、JVM概念 1.1、什么是JVM Java Virtual Machine&#xff1a;Java虚拟机&#xff0c;用来保证Java语言跨平台 Java虚拟机可以看做是一台抽象的计算机&#xff0c;如同真实的计算机那样&#xff0c;它有自己的指令集以及各种运行时内存区域 Java虚拟机与Java语言并没有必然…

Maven-搭建私有仓库

使用NEXUS REPOSITORY MANAGER 3在Windows上搭建私有仓库。 NEXUS REPOSITORY MANAGER 3 是一个仓库管理系统。 下载NEXUS3 官网上是无法下载的,所以网上搜nexus-3.18.1-01-win64就能搜到,下载即可。 安装NEXUS3 下载nexus-3.18.0-01-win64.zip至相应目录下(路径不要有中文)。 …

[Realtek sdk-3.4.14b]RTL8197FH-VG+RTL8812F WiFi开启访客网络之后无法扫描到SSID问题分析及解决方案

问题描述 realtek sdk-3.4.14b 开启访客网络之后,发现无法扫描到SSID,可以看到接口已经up,但是设备无法搜到WiFi热点 问题分析 查看网口状态 ifconfig查看wlan0-va0接口TX/RX的数据包都是0,表示没有发送或者接收到数据包,正常wifi启动之后,都会有Beacon包发出,也会接…