【二叉树进阶】搜索二叉树的性能分析及其应用

news2024/11/16 9:56:48

文章目录

  • 前言
  • 1. 二叉搜索树的性能分析
  • 2. 二叉搜索树的应用
    • 2.1 K模型
    • 2.2 KV模型
      • 英汉互译
      • 统计次数
  • 3. 源码展示
    • 3.1 KV结构改造
    • 3.2 测试

前言

上一篇文章我们学习了搜索二叉树的实现,这篇文章我们来对搜索二叉树进行一个性能分析,并来讲解一下它的应用。

1. 二叉搜索树的性能分析

插入和删除操作都必须先查找,所以查找效率就代表了二叉搜索树中各个操作的性能

那大家思考一下,搜索二叉树的查找的时间复杂度是多少?
那这个其实在不同情况下是不一样的:
如果二叉搜索树处于比较平衡的情况(接近完全二叉树),比如这样的
在这里插入图片描述
这种情况最坏的查找无非也就查找高度次(那如果结点数量为N,它的高度通常保持在logN的水平),所以这样它的时间复杂度就是O(logN)
但是,避免不了出现这样的情况
在这里插入图片描述
二叉搜索树退化为单支树(或者接近单支),那这时查找的时间复杂度就应该是O(N)

所以,二叉搜索树的查找的时间复杂度

最优情况下,二叉搜索树趋于平衡,其平均比较次数为: l o g 2 N log_2 N log2N,时间复杂度为O(logN)
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N,时间按复杂度为O(N)

那么问题来了:

如果退化成单支树,二叉搜索树的性能就失去了。
那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?那么我们后续章节学习的AVL树和红黑树就可以上
场了。
这个到时候讲到再说…

2. 二叉搜索树的应用

2.1 K模型

搜索二叉树的第一个应用是K模型,什么是K模型呢,介绍一下:

其实呢就是一个在不在的问题。
K模型:K模型即只有key作为关键码,结构中只存储Key,关键码即为需要搜索的值。

比如:

给一个单词word,判断该单词是否拼写正确,具体方式如下:
以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误

那我们上一篇实现的搜索二叉树其实就是按照K模型搞的,所以这里就不举具体的例子了。

2.2 KV模型

KV模型即每一个关键码key,都有与之对应的值Value,即结构中存储<Key, Value>的键值对。

该种方式在现实生活中非常常见:

比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。

那接下来我们就来改造一下我们上一篇文章实现的搜索二叉树,改造成KV模型来简单解决一下上面提到的两个问题。

英汉互译

怎么改造呢,也很简单:

第一步:

我们可以把上一篇文章写的那个放到一个Key的命名空间,然后拷贝一份放到KV的命名空间里进行修改。
在这里插入图片描述

然后,大部分地方都不用变,把这几个地方改一下就行了

在这里插入图片描述
其实就是增加一个模板参数,结点里面增加一个成员_val;
然后涉及到的模板的地方和相关的成员函数改一下就行了

首先插入的函数肯定得改一下在这里插入图片描述
ps:代码比较长,就不全部放出来了,最后会给大家源码,大家不清楚的地方可以看

然后查找我们要简单改一下

因为现在是KV模型,完成英汉互译,不像之前那样查到了返回true,查不到返回false
应该是查到返回对应的结点,通过结点我们可以拿到对应的val,查不到返回个nullptr。

在这里插入图片描述

然后删除其实就不用改了,因为删除还是用key去找对应的结点删除就行了。
至于剩下的,大家有兴趣可以自己修改一下,我们这里就直接把剩下的其它一些接口删掉了。

然后我们来写个程序测试一下:

在这里插入图片描述
运行看看
在这里插入图片描述
然后关于这里这个循环如何结束,我们上面提到可以输入Ctrl+Z,然后回车,这就是正常让它结束。
当然还可以输入Ctrl+c,直接就结束了,不过Ctrl+c是直接让进程终止。
在这里插入图片描述

统计次数

接下来我们再来搞一个,还是用KV模型:

在这里插入图片描述
现在有一些水果,我们想统计每种水果出现的次数。

可以怎么搞呢?

🆗,我们还是用KV来存储键值对,key是水果的名字,val是出现的次数。
在这里插入图片描述
然后:
遍历存储水果的string数组,判断每一个水果在不在countTree里面,不在,就添加进去,把val置成1,在的话,就只让val++就行了。
这样遍历一遍,就统计出次数了。
在这里插入图片描述
运行一下
在这里插入图片描述
就统计出来了。

3. 源码展示

3.1 KV结构改造

namespace kv
{
	template <class K, class V>
	struct BSTreeNode
	{
		BSTreeNode(const K& key, const V& val)
			:_left(nullptr)
			, _right(nullptr)
			, _key(key)
			, _val(val)
		{}

		BSTreeNode<K,V>* _left;
		BSTreeNode<K,V>* _right;
		K _key;
		V _val;
	};

	template <class K, class V>
	class BSTree
	{
		typedef BSTreeNode<K,V> Node;
	public:
		bool Insert(const K& key, const V& val)
		{
			if (_root == nullptr)
			{
				_root = new Node(key, val);
				return true;
			}
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur)
			{
				if (key < cur->_key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (key > cur->_key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					return false;
				}
			}
			//走到这里cur为空,就是key应该插入的位置
			cur = new Node(key, val);
			//链接
			if (key < parent->_key)
				parent->_left = cur;
			if (key > parent->_key)
				parent->_right = cur;
			return true;
		}
		Node* Find(const K& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (key < cur->_key)
				{
					cur = cur->_left;
				}
				else if (key > cur->_key)
				{
					cur = cur->_right;
				}
				else
				{
					return cur;
				}
			}
			return nullptr;
		}
		bool Erase(const K& key)
		{
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur)
			{
				if (key < cur->_key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (key > cur->_key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					//key == cur->_key就是找到了,进行删除
					//1.左为空
					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;
					}
					//2.右为空
					else if (cur->_right == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							if (cur == parent->_left)
								parent->_left = cur->_left;
							else
								parent->_right = cur->_left;
						}
						delete cur;
						cur = nullptr;
					}
					else//左右都不为空
					{
						//这里选择右树的最小结点(最左边)替换
						Node* pminRight = cur;
						Node* minRight = cur->_left;
						while (minRight->_left)
						{
							pminRight = minRight;
							minRight = minRight->_left;
						}
						cur->_key = minRight->_key;
						//删除替换结点
						if (pminRight->_left = minRight)
						{
							pminRight->_left = minRight->_right;
						}
						else
						{
							pminRight->_right = minRight->_right;
						}
						delete minRight;
						minRight = nullptr;
					}
					return true;
				}
			}
			//找不到直接返回false
			return false;
		}
		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;
			_InOrder(root->_left);
			cout << root->_key << root->_val << " ";
			_InOrder(root->_right);
		}
	private:
		Node* _root = nullptr;
	};
}

3.2 测试

void TestBSTree5()
{
	kv::BSTree<string, string> dict;
	dict.Insert("sort", "排序");
	dict.Insert("left", "左边");
	dict.Insert("right", "右边");
	dict.Insert("string", "字符串");
	dict.Insert("insert", "插入");
	dict.Insert("erase", "删除");

	string str;
	while (cin >> str)
	{
		auto ret = dict.Find(str);
		if (ret)
		{
			cout << ":" << ret->_val << endl;
		}
		else
		{
			cout << "无此单词" << endl;
		}
	}
}
void TestBSTree6()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };

	kv::BSTree<string, int> countTree;
	for (auto fruit : arr)
	{
		//kv::BSTreeNode<string, int>* ret = conutTree.Find(e);
		auto ret = countTree.Find(fruit);
		if (ret == nullptr)
		{
			countTree.Insert(fruit, 1);
		}
		else
		{
			++ret->_val;
		}
	}
	countTree.InOrder();
}

在这里插入图片描述

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

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

相关文章

OpenCV4.3 Java 编程入门:透明度与抠图

1. 基础知识 JPG 格式图片有损压缩和不支持半透明&#xff0c;如果想在图片上添加透明通道&#xff0c;一定不要用 JPG 格式的图片&#xff1b;PNG&#xff1a;既支持3通道RGB图像&#xff0c;也支持4通道RGBA图像&#xff08;红色、绿色、蓝色和透明度&#xff09;&#xff1…

一篇文章帮你弄懂邻接矩阵,邻接表和链式前向星的区别

前言&#xff1a; 在学C的时候&#xff0c;面对各种各样的存图方式&#xff0c;脑子都大了不少&#xff0c;各种算法还在向我冲来&#xff0c;结果一个邻接矩阵/邻接表/链表轻松给了我一下暴击就直接让我KO了&#xff0c;趁着脑子还算清楚&#xff0c;详细的介绍下这三种存图方…

探索工程机械远程控制新纪元:Intewell-Hyper II震撼发布!

在当前的工程技术领域&#xff0c;远程控制技术以其卓越的效率和方便性&#xff0c;正受到越来越多的关注和运用。而在这个过程中&#xff0c;某机械集团以Intewell-HyperII操作系统为基础&#xff0c;打造出了具有前瞻性的工程机械远程控制器&#xff0c;为行业的发展提供了新…

树莓派-搭建wireguard服务

一、简介 官网&#xff1a;https://www.wireguard.com/ WireGuard is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headac…

SpringBoot原理分析 | 安全框架:Shiro

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Shiro Shiro是一个安全框架&#xff0c;用于认证、授权和管理应用程序的安全性。它提供了一组易于使用的API和工具&#xff0c;可以帮助您轻松地添加安全性到您的应用…

apple pencil到底值不值得买?好用的iPad电容笔

随着ipad平板型号版本的不断更新&#xff0c;其的功能越来越多&#xff0c;现在它的性能已经可以和笔记本电脑相媲美了。而现在&#xff0c;随着技术的进步&#xff0c;IPAD已经不再是单纯的娱乐&#xff0c;而是一种功能强大的学习、绘画、工作等等。要增加生产效率&#xff0…

按键消抖(有/无状态机)

一&#xff0c;理论概念 按键抖动 按键抖动&#xff1a;按键抖动通常的按键所用开关为机械弹性开关&#xff0c;当机械触点断开、闭合时&#xff0c;由于机械触点的弹性作用&#xff0c;一个按键开关在闭合时不会马上稳定地接通&#xff0c;在断开时也不会一下子断开。因而在闭…

<MyBatis>MyBatis把空字符串转换成了0的问题处理方案

先看问题: Postman入参: MyBatis采用map循环插入: // Mapper接口层void addPar(Param(value "question") Map<String, Object> paramMap);<!-- 新增&#xff1a;参数 --><insert id"addPar" parameterType"map">INSERT IGNO…

高忆管理:集合竞价可以卖股票吗?

集合竞价是证券买卖所开盘前的一种买卖方式&#xff0c;其意图是为了在买卖所开盘之前构成有用的商场价格。因为商场处于初始状况&#xff0c;买卖量较小&#xff0c;因而集合竞价价格或许与买卖日中的实践买卖价格有所不同。那么&#xff0c;集合竞价是否能够卖股票呢&#xf…

Python3,1行代码,批量把图片转换成PDF文档,女神终于同意跟我吃夜宵了。

批量图片转换成PDF文档 1、引言2、代码示例2.1 安装2.2 单张转换2.3 批量转换 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c; 求助&#xff0c;求助。 小鱼&#xff1a;有啥事&#xff0c;这大惊小怪的。 小屌丝&#xff1a;我女神跟我说&#xff0c; 如果我把她的照片…

Spark 5:Spark Core 内核调度

DAG Spark的核心是根据RDD来实现的&#xff0c;Spark Scheduler则为Spark核心实现的重要一环&#xff0c;其作用就是任务调度。Spark的任务调度就是如何组织任务去处理RDD中每个分区的数据&#xff0c;根据RDD的依赖关系构建DAG&#xff0c;基于DAG划分Stage&#xff0c;将每个…

跨境电商ERP源码选型指南,如何找到最适合你的?

在跨境电商行业&#xff0c;一个高效的ERP系统是保证业务顺利进行和管理的关键。选择适合自己的跨境电商ERP源码至关重要。本指南将帮助你了解如何找到最适合你的跨境电商ERP源码。 跨境电商ERP源码的重要性 跨境电商ERP源码在现代电商营运中起着至关重要的作用。它提供了一套…

音频开发-小程序和H5

微信录音 1、引入sdk 2、录音操作 浏览器录音 参考文献&#xff1a;前端H5实现调用麦克风&#xff0c;录音功能_h5 录音_Darker丨峰神的博客-CSDN博客 function record() { window.navigator.mediaDevices.getUserMedia({ audio: { sampleRate: 44100, // 采样率 channelCount…

游戏APP开发:创新设计的秘诀

在游戏 APP开发中&#xff0c;创新设计是游戏开发公司的一大追求&#xff0c;为了可以为用户带来更好的游戏体验&#xff0c;这就需要对游戏 APP开发进行创新设计。那么&#xff0c;游戏 APP开发中的创新设计是什么呢&#xff1f;接下来&#xff0c;我们就一起来看看吧。 想要…

一起学算法(递推篇)

前言&#xff1a;递推最通俗的理解就是数列&#xff0c;递推和数列的关系就好比算法和数据结构的关系&#xff0c;数列有点像数据结构中的顺序表&#xff0c;而递推就是一个循环或者迭代的过程的枚举过程 1.斐波那契数列 斐波那契数形成的序列称为斐波那契数列&#xff0c;该…

【Java|golang】143. 重排链表---快慢指针

给定一个单链表 L 的头节点 head &#xff0c;单链表 L 表示为&#xff1a; L0 → L1 → … → Ln - 1 → Ln 请将其重新排列后变为&#xff1a; L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → … 不能只是单纯的改变节点内部的值&#xff0c;而是需要实际的进行节点交换。 …

python中有哪些异常,怎么处理

目录 python报的错误怎么处理 1. 使用 try-except 语句块 2. 使用 finally 语句块 3. 主动引发异常 python中有哪些异常 不知道是什么异常时怎么操作 总结 python报的错误怎么处理 在Python中&#xff0c;当程序执行时遇到错误&#xff0c;Python会抛出异常。要处理Pyt…

孩子近视有必要用全光谱灯吗?全光谱led灯推荐

当然&#xff0c;有必要!全光谱LED灯的光源分布更加均匀&#xff0c;使空间更加美观舒适&#xff0c;而普通灯的光源分布可能会在一定范围内分布不均匀。全光谱它的使用寿命长达20-30万小时&#xff0c;而普通灯的使用寿命仅为1000-2000小时&#xff0c;因此在长期使用上&#…

list模拟

之前模拟了string,vector&#xff0c;再到现在的list&#xff0c;list的迭代器封装最让我影响深刻。本次模拟的list是双向带头节点的循环链表&#xff0c;该结构虽然看起来比较复杂&#xff0c;但是却非常有利于我们做删除节点的操作&#xff0c;结构图如下。 由于其节点结构特…

收发存和进销存有什么区别?

一、什么是收发存和进销存 1、收发存 收发存是供应链管理中的关键概念&#xff0c;用于描述企业在供应链中的物流和库存管理过程。 收发存代表了企业在采购、生产和销售过程中的物流活动和库存水平。 收&#xff08;Receiving&#xff09; 企业接收供应商送达的物料或产品…