二叉搜索树的实现

本文旨在讲解如何编写一颗二叉搜索树,包括基本的增删查改的操作。

目录

一、二叉搜索树的概念

 ​编辑二、二叉搜索树的编写

2.1节点的编写

2.2节点的插入

2.3节点的查找

2.4节点的删除

三、二叉搜索树的应用

四、 二叉搜索树的性能分析

五、完整代码 


一、二叉搜索树的概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
1.若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
2.若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
3.它的左右子树也分别为二叉搜索树

 二、二叉搜索树的编写

2.1节点的编写

作为一颗树他的节点应该包括储存的内容和找到其他节点的方式,而因为它是一棵二叉树,所以这里我采用左右孩子法去定义它的孩子。

template <class K, class V>
struct BSTreeNode
{
	BSTreeNode<K,V>* _left;
	BSTreeNode<K,V>* _right;
	K _key;
	V _val;
	BSTreeNode(const K& key,const V& val)//可能存自定义类型,所以用引用节省空间提升效率
		:_left(nullptr),
		_right(nullptr),
		_key(key),
		_val(val)
	{}
};

这里我预计储存一个k_val两个类型的数据,但是其实写多少个类型的数据都可以(>=1)。但是要明确的是,只有一个数据参与了对应节点在二叉树中位置相关的代码。

2.2节点的插入

当节点的插入实现后,我们就可以将这颗树建立起来。 根据二叉树的特性,比当前节点小的在左边,大的在右边。

插入的具体过程如下:
a. 树为空,则直接新增节点,赋值给root指针
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点

 代码如下

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 (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,val);
		if (parent->_key < key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

		return true;
	}

2.3节点的查找

这里我希望当节点找到后返回它对应的地址,根据二叉搜索树代码如下:

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 FindR(const K& key)
	{
		return _FindR(_root, key);
	}
    bool _FindR(Node* root,const K& key)//带R表示是递归实现
	{
		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;
		}
	}

2.4节点的删除

节点的删除是二叉搜索树最困难的部分。首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:

a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点

 看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程 如下

情况 b :删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点 -- 直接删除
情况 c :删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点 -- 直接删除
情况 d :在它的右子树中寻找中序下的第一个结点 ( 关键码最小 ) ,用它的值填补到被删除节点
中,再来处理该结点的删除问题 -- 替换法删除

其中替换法删除就是用要删除节点的左边最大的节点或着右边最小的节点的值来替换当前节点,然后情况d就转换成了情况b或c。

此外,我们还应该考虑的时如果要删除的节点时根节点的情况。 

    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->_left == nullptr)
				{
					if (cur == _root)_root = _root->_right;
					else
					{
						if (parent->_right == cur)parent->_right = cur->_right;
						else parent->_left = cur->_right;
					}
				}
				if (cur->_right == nullptr)
				{
					if (cur == _root)_root = _root->_left;
					else
					{
						if (parent->_right == cur)parent->_right = cur->_left;
						else parent->_left = cur->_left;
					}
				}
				else
				{
					//找替换节点,左面最大或者右面最小
					parent = cur;
					Node* leftMax = cur->_left;
					while (leftMax->_right)
					{
						parent = leftMax;
						leftMax = leftMax->_right;
					}

					std::swap(cur->_key, leftMax->_key);
					std::swap(cur->_val, leftMax->_val);
					if (parent->_left == leftMax)parent->_left = leftMax->_left;
					else parent->_right= leftMax->_left;
				}
				delete cur;
				return true;
			}
		}
		return false;
	}

最终我们就将整颗二叉搜索树的基本功能实现了

三、二叉搜索树的应用

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

四、 二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。 对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二 叉搜索树的深度的函数,即结点越深,则比较次数越多。 但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树: 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:$log_2 N 最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:N/2问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插 入关键码,二叉搜索树的性能都能达到最优?那么我们后续章节学习的AVL树和红黑树就可以上 场了。

五、完整代码 

#pragma once
#include<iostream>
using namespace std;

template <class K, class V>
struct BSTreeNode
{
	BSTreeNode<K,V>* _left;
	BSTreeNode<K,V>* _right;
	K _key;
	V _val;
	BSTreeNode(const K& key,const V& val)//可能存自定义类型,所以用引用节省空间提升效率
		:_left(nullptr),
		_right(nullptr),
		_key(key),
		_val(val)
	{}
};
template<class K,class V>
class BSTree
{
	typedef BSTreeNode<K,V> Node;
public:
	BSTree()
		:_root(nullptr)
	{}
	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 (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,val);
		if (parent->_key < key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = 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 FindR(const K& key)
	{
		return _FindR(_root, key);
	}
	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->_left == nullptr)
				{
					if (cur == _root)_root = _root->_right;
					else
					{
						if (parent->_right == cur)parent->_right = cur->_right;
						else parent->_left = cur->_right;
					}
				}
				if (cur->_right == nullptr)
				{
					if (cur == _root)_root = _root->_left;
					else
					{
						if (parent->_right == cur)parent->_right = cur->_left;
						else parent->_left = cur->_left;
					}
				}
				else
				{
					//找替换节点,左面最大或者右面最小
					parent = cur;
					Node* leftMax = cur->_left;
					while (leftMax->_right)
					{
						parent = leftMax;
						leftMax = leftMax->_right;
					}

					std::swap(cur->_key, leftMax->_key);
					std::swap(cur->_val, leftMax->_val);
					if (parent->_left == leftMax)parent->_left = leftMax->_left;
					else parent->_right= leftMax->_left;
				}
				delete cur;
				return true;
			}
		}
		return false;
	}
	
	void InOrder()//中序遍历
	{
		_InOrder(_root);
		cout << endl;
	}
private:
	bool _FindR(Node* root,const K& key)//带R表示是递归实现
	{
		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;
		}
	}
	void _InOrder(Node* root)
	{
		if (root == NULL)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_key << " :"<<root->_val;
		_InOrder(root->_right);
	}
private:
	Node* _root;
};

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

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

相关文章

CD8+T细胞通过NKG2D-NKG2DL轴维持对MHC-I阴性肿瘤细胞的杀伤

今天给同学们分享一篇实验文章“CD8 T cells maintain killing of MHC-I-negative tumor cells through the NKG2D-NKG2DL axis”&#xff0c;这篇文章发表在Nat Cancer期刊上&#xff0c;影响因子为22.7。 结果解读&#xff1a; MHC-I阴性肿瘤的免疫疗法需要CD8 T细胞 作者先…

现代雷达车载应用——第2章 汽车雷达系统原理 2.2节 汽车雷达架构

经典著作&#xff0c;值得一读&#xff0c;英文原版下载链接【免费】ModernRadarforAutomotiveApplications资源-CSDN文库。 2.2 汽车雷达架构 从顶层来看&#xff0c;基本的汽车雷达由发射器&#xff0c;接收器和天线组成。图2.2给出了一种简化的单通道连续波雷达结构[2]。这…

什么是网络丢包以及如何解决

丢包的概念一直是网络行业争论的话题&#xff0c;在设计和实现网络时&#xff0c;它始终是考虑的关键因素&#xff0c;其重要性在于它对网络和网络系统的效率和整体性能的直接影响&#xff0c;即使是单个故障设备或配置错误的设置也会导致数据包丢失&#xff0c;也会严重影响整…

2 Mycat2 安装与启动

1、制作安装包 Mycat2不提供安装包,只提供核心JAR包,JAR包可以独立运行,安装包是使用Java Service Wrapper做壳的,如果需要安装包&#xff0c;需要自己制作。JAR可以作为Java库引入自己业务项目中使用Mycat2中的各个组件的设计都是可以独立使用的。 步骤如下&#xff1a; 1.…

【C++干货铺】继承后的多态 | 抽象类

个人主页点击直达&#xff1a;小白不是程序媛 C系列专栏&#xff1a;C干货铺 代码仓库&#xff1a;Gitee 目录 多态的概念 多态的定义和实现 多态的定义条件 虚函数 虚函数的重写 特殊情况 协变&#xff08;基类和派生类的虚函数返回值不同&#xff09; 析构函数的重…

ffmpeg踩坑之手动编译报错Unrecognized option ‘preset‘及rtsp/rtmp推流

本文解决的问题记录&#xff1a; 报错1&#xff1a;Unrecognized option preset. Error splitting the argument list: Option not found 报错2&#xff1a;ERROR: x264 not found using pkg-config 报错3&#xff1a;ffmpeg: error while loading shared libraries: libavd…

【linux】Debian不能运行sudo的解决

一、问题&#xff1a; sudo: 没有找到有效的 sudoers 资源&#xff0c;退出 sudo: 初始化审计插件 sudoers_audit 出错 二、可用的方法&#xff1a; 出现 "sudo: 没有找到有效的 sudoers 资源&#xff0c;退出" 和 "sudo: 初始化审计插件 sudoers_audit 出错&q…

spring面试:一、面试题分类总览+bean线程安全问题+AOP相关问题(定义、使用步骤、编程式事务管理和声明式事务管理和声明式事务管理失效)

面试题分类总览 bean线程安全问题 单例/多例 单例&#xff08;singleton&#xff09;&#xff1a;在每个spring ioc容器中都只有一个实例。 多例&#xff08;prototype&#xff09;&#xff1a;在每个spring ioc容器中有多个实例。 默认情况下spring中的bean都是单例的。但是…

基于Java SSM框架实现智能停车场系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现智能停车场系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个智能停车场管理系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述…

call 和 apply:改变对象行为的秘密武器(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

TSINGSEE视频智能解决方案边缘AI智能与后端智能分析的区别与应用

视频监控与AI人工智能的结合是当今社会安全领域的重要发展趋势。随着科技的不断进步&#xff0c;视频监控系统已经不再局限于简单的录像和监视功能&#xff0c;而是开始融入人工智能技术&#xff0c;实现更加智能化的监控和安全管理。传统的监控系统往往需要人工操作来进行监控…

内网渗透测试基础——Windows PowerShell篇

内网渗透测试基础——Windows PowerShell篇 1. Windows PowerShell基础 Windows PowerShell是一种命令行外壳程序和脚本环境&#xff0c;它内置在每个受支持的Windows版本中&#xff08;Windows7、Windows Server 2008 R2及更高版本&#xff09;&#xff0c;为Windows命令行使…

讨好型人格最适合从事什么职业?

讨好型人格&#xff0c;其言行不是考虑个人&#xff0c;而是以满足对方为主&#xff0c;只要是他人的想法&#xff0c;都会尽力去满足&#xff0c;特别害怕自己做了什么事情&#xff0c;让对方产生不满的想法。遇到事情&#xff0c;也很难主动请求别人&#xff0c;总是依靠自己…

计算机组成原理-函数调用的汇编表示(call和ret指令 访问栈帧 切换栈帧 传递参数和返回值)

文章目录 call指令和ret指令高级语言的函数调用x86汇编语言的函数调用call ret指令小结其他问题 如何访问栈帧函数调用栈在内存中的位置标记栈帧范围&#xff1a;EBP ESP寄存器访问栈帧数据&#xff1a;push pop指令访问栈帧数据&#xff1a;mov指令小结 如何切换栈帧函数返回时…

APP安全测试填坑

在实习过程中&#xff0c;我接触到了一些SDL安全提测的工作。原来我是学web端渗透比较多的&#xff0c;移动端这块基本没怎么试过手&#xff0c;结果刚开始一直踩坑&#xff0c;连抓包都抓不到(&#xff34;▽&#xff34;)。 下面记录下我遇到的部分问题和解决方法&#xff0…

Python基础04-数据容器

零、文章目录 Python基础04-数据容器 1、了解字符串 &#xff08;1&#xff09;字符串的定义 字符串是 Python 中最常用的数据类型。我们一般使用引号来创建字符串。创建字符串很简单&#xff0c;只要为变量分配一个值即可。<class ‘str’>即为字符串类型。一对引号…

【C++干货铺】会搜索的二叉树(BSTree)

个人主页点击直达&#xff1a;小白不是程序媛 C系列专栏&#xff1a;C干货铺 代码仓库&#xff1a;Gitee 目录 前言&#xff1a; 二叉搜索树 二叉搜索树概念 二叉搜索树操作 二叉搜索树的查找 二叉搜索树的插入 二叉搜索树元素的删除 ​二叉搜索树的实现 BSTree结点 …

人工智能导论习题集(4)

第六章&#xff1a;机器学习 题1题2题3 题1 题2 题3

大型网站架构演进过程

架构演进 大型网站的技术挑战主要来自于庞大的用户&#xff0c;高并发的访问和海量的数据&#xff0c;任何简单的业务一旦需要处理数以P计的数据和面对数以亿计的用户&#xff0c;问题就会变得很棘手。大型网站架构主要就是解决这类问题。 架构选型是根据当前业务需要来的&…

系列六、Springboot整合Spring Session

一、概述 在互联网发展的起始阶段&#xff0c;一般使用的是单服务架构&#xff0c;由于只有一台服务器&#xff08;Tomcat&#xff09;&#xff0c;所有的请求和响应都是基于这台服务器实现的&#xff0c;那么就不存在session共享的问题&#xff0c;但是在互联网发展的今天&…