二叉搜索树(二叉排序树、二叉查找树)

二叉搜索树(二叉排序树、二叉查找树)

  • 一、定义
  • 二、操作
    • (一)中序遍历
    • (二)查找
    • (三)插入
    • (四)删除
  • 三、二叉搜索树的应用
  • 四、二叉搜索树操作的性能分析
  • 五、总结

一、定义

二叉搜索树(BST:Binary Search Tree)也叫二叉排序树,也叫二叉查找树。
二叉搜索树要么是一颗空树,要么是具有以下性质的树:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树
从定义可以看出:二叉搜索树中不能有两个值相等的结点
比如:
在这里插入图片描述

二、操作

下面是二叉搜索树的定义(为了满足多种情况,定义成了一个模板):

template<class T>
class BSTreeNode //二叉搜索树结点
{
public:
	BSTreeNode(T val = T()) :
		_val(val), _left(nullptr), _right(nullptr)
	{
	}

	BSTreeNode* _left;
	BSTreeNode* _right;
	T _val;
};

template<class T>
class BSTree //二叉搜索树
{
	typedef BSTreeNode<T> Node;
	
public:
	Node* Find(const T& key);
	bool Insert(const T& key);
	bool Erase(const T& key);
	void InOrder();

private:
	Node* _root = nullptr;
};

(一)中序遍历

同二叉树的中序遍历

void _InOrder(Node* root)
{
	if (root == nullptr)
		return;
	_InOrder(root->_left); //中序遍历左子树
	cout << root->_val << " ";
	_InOrder(root->_right); //中序遍历右子树
}
void InOrder()
{
	_InOrder(_root); //递归
	cout << "\n";
}

(二)查找

从根开始比较查找,比根小则去左子树中查找,比根大则去右子树中查找,在左右子树中以同样的方式继续查找,如果走到空还没找到,这个值就不存在。最多查找树的高度次。
下面是递归和非递归两种实现方法:

Node* _Find(Node* root, const T& key)
{
	if (root == nullptr) //没找到返回nullptr
		return nullptr;
		
	if (root->_val > key) //在右子树中查找
		return _Find(root->_left, key);
	else if (root->_val < key) //在左子树中查找
		return _Find(root->_right, key);
	else //找到了
		return root;
}
Node* Find(const T& key)
{
	//递归
	//return _Find(_root, key);
	
	//非递归
	Node* root = _root;
	while (root)
	{
		if (root->_val == key)
			return root;
		else if (root->_val > key)
			root = root->_left;
		else if (root->_val < key)
			root = root->_right;
	}
	return root;
}

(三)插入

创建一个二叉搜索树的过程,也就是不断的插入。插入的具体过程如下:
1、树为空,则直接新增节点,赋值给root指针
2、树不空,按二叉搜索树性质查找插入位置,插入新节点
3、每次插入的结点都是叶子结点
比如:
在这里插入图片描述
下面是递归和非递归两种实现方法:

bool _Insert(Node*& root, const T& key)
{
	if (root == nullptr)
	{
		root = new Node(key);
		return true;
	}
	if (root->_val > key)
		return _Insert(root->_left, key);
	else if (root->_val < key)
		return _Insert(root->_right, key);
	else
		return false;
}
bool Insert(const T& key)
{
	//递归
	//return _Insert(_root, key);
	
	//非递归
	Node* newnode = new Node(key); //创建一个新结点
	if (_root == nullptr) //空树特殊处理
	{
		_root = newnode;
		return true;
	}
	Node* root = _root, *pre = nullptr; //pre指向root的双亲
	while (root) //寻找插入的位置
	{
		if (root->_val == key) //已经存在,无需插入
			return false;
		else if (root->_val > key) //在左子树中查找插入位置
		{
			pre = root;
			root = root->_left;
		}
		else if (root->_val < key) //在右子树中查找插入位置
		{
			pre = root;
			root = root->_right;
		}
	}
	//判断插入到左孩子还是右孩子
	if (pre->_val > key)
		pre->_left = newnode;
	else
		pre->_right = newnode;
	return true;
}

(四)删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回,否则要删除的结点可能分下面四种情况:
1、要删除的结点无孩子结点
2、要删除的结点只有左孩子结点
3、要删除的结点只有右孩子结点
4、要删除的结点有左、右孩子结点
情况1:直接删除该结点–直接删除
情况2:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点–直接删除
情况3:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点–直接删除
情况4:在它的右子树中寻找中序遍历下的第一个结点(右子树中值最小,也就是右子树中最左边的结点,并且该结点一定没有左子树),用它的值填补到被删除结点中,再来处理该结点的删除问题(该结点删除问题下面注意中会说明)–替换法删除。或者在它的左子树中寻找中序遍历下的最后一个结点(左子树中值最大,也就是左子树中最右边的结点,并且该结点一定没有右子树),用它的值填补到被删除结点中,再来处理该结点的删除问题–替换法删除。
注意:
a:其中可以发现,情况1可以当作情况2或情况3处理,因为情况1删除之后,它的双亲指向空,当作情况2处理的话,虽然它没有左孩子,但是可以看作是null左孩子,然后将null赋值给它的双亲,他的双亲仍然是指向空,情况3也是同理。这样的做的好处是归纳整理可以更好的编程。
b:情况4中用替换法删除,替换的那个结点删除问题该怎么处理,如果用右子树中最小关键字结点替换,该结点一定没有左子树,那么删除该结点就变成了情况3。如果是用左子树中最大关键字结点替换,该结点一定没有右子树,那么删除该结点就变成了情况2。这样一来删除任何一个结点都很容易,要么直接删除,要么替换删除后再使用一次直接删除。
c:对于b中那种处理方法之外还有一种方法,一直用替换法删除替换的那个结点,直到最后一次用叶子结点替换,然后将叶结点删除。如果树的高度很高并且树的性状不理想的情况下,可能需要使用很多次替换法,所有这种方法没有b快。
比如:
在这里插入图片描述

下面是递归和非递归两种实现方法:

bool _Erase(Node*& root, const T& key)
{
	if (root == nullptr) //没找到
		return false;
		
	if (root->_val > key)
		return _Erase(root->_left, key);
	else if (root->_val < key)
		return _Erase(root->_right, key);
	else
	{
		Node* del = root;
		//分三种情况(思想同非递归)
		if (root->_left == nullptr)
			root = root->_right;
		else if (root->_right == nullptr)
			root = root->_left;
		else if (root->_left != nullptr && root->_right != nullptr)
		{
			//用右子树中最左边结点替换
			Node* tmp = root->_right;
			while (tmp->_left)
				tmp = tmp->_left;
			swap(root->_val, tmp->_val); //库函数,交换两个结点的值
			return _Erase(root->_right, key); //递归删除替换的那个结点(这一步也可不使用递归,非递归也行)
			//(注意这里并不是从根重新开始查找删除值为key的结点,因为我们上一步交换了待删除结点root和替换结点tmp的值。要是从根结点开始查找,是找不到值为key的结点。而是从结点root的右子树开始删除值为key的结点)
		}
		delete del;
		return true;
	}
}
bool Erase(const T& key)
{
	//递归
	//return _Erase(_root, key);
	
	//非递归
	//一、查找
	Node* root = _root, *pre = nullptr; //pre指向root的双亲
	while (root)
	{
		if (root->_val == key)
			break;
		else if (root->_val > key)
		{
			pre = root;
			root = root->_left;
		}
		else if (root->_val < key)
		{
			pre = root;
			root = root->_right;
		}
	}
	
	//二、删除
	if (root == nullptr) //没找到
		return false;
	else //找到
	{
		//找到后,删除的结点分三种情况
		if (root->_left == nullptr) //情况2
		{
			if (root == _root) //根节点特殊处理(主要是因为根结点的时候pre为nullptr,不能用pre进行操作)
				_root = _root->_right;
			else
			{
				//判断当前结点时双亲的左孩子还是右孩子
				if (pre->_left == root) 
					pre->_left = root->_right;
				else if (pre->_right == root)
					pre->_right = root->_right;
			}
			delete root;
		}
		else if (root->_right == nullptr) //情况3
		{
			if (root = _root) //根节点特殊处理
				_root = _root->_left;
			else
			{	
				//判断当前结点时双亲的左孩子还是右孩子
				if (pre->_left == root)
					pre->_left = root->_left;
				else if (pre->_right == root)
					pre->_right = root->_left;
			}
			delete root;
		}
		else if (root->_left != nullptr && root->_right != nullptr) //情况4
		{
			//用右子树中最左边的结点替换
			//1、找右子树中最左边结点
			Node* tmp = root->_right, *father = root; //tmp指向右子树中最左边结点,father是指向tmp的双亲结点
			while (tmp->_left)
			{
				father = tmp;
				tmp = tmp->_left;
			}
			//2、替换(仅赋值)
			root->_val = tmp->_val;
			//3、删除替换结点(有可能root的右孩子就是右子树中最左边结点,有可能不是,所有需要判断赋值)
			if (father->_left == tmp)
				father->_left = tmp->_right;
			else if (father->_right == tmp)
				father->_right = tmp->_right;
			delete tmp;
		}
		return true;
	}
}

三、二叉搜索树的应用

K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
比如:私人小区停车场,只允许该小区的车辆进入。每次只需要识别进入车辆的车牌是否在小区的系统中,所有只需要将车牌信息存入系统即可,这里的车牌就是key。
KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。
比如:商场停车场,所有外来车辆都可以进入,并且按停车时长收费。每次车辆进入时记录车辆的车牌号和入场时间,将车牌作为key,入场时间作为value。每次车辆离场时,只需要在系统中查找到该车牌号,然后就能找到车牌对应的入场时间,就可以算出停车费用。KV模型的实现在K的基础上增加一个value就可以。

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

插入和删除操作都必须先查找,因此查找效率代表了二叉搜索树中各个操作的性能。查找的效率取决于结点在树中的深度。
对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树。
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N

五、总结

插入和删除这两个操作多注意一些细节和特殊情况,无论是插入还是删除,都需要知道插入位置和待删除结点的双亲结点,并且删除的时候,删除根节点的时候要特殊处理一下。

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

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

相关文章

CCF-B类SGP‘24 4月10日截稿!速速行动!

会议之眼 快讯 第22届SGP(Eurographics Symposium on Geometry Processing)即欧洲图形学几何处理专题讨论会将于 2024 年 6月24 -日至26日在美国麻省理工学院举行&#xff01;SGP是传播几何处理新研究想法和尖端成果的首要学术会议。作为该领域的重要学术盛事&#xff0c;SGP会…

模型转换案例学习:等效替换不支持算子

文章介绍 Qualcomm Neural Processing SDK &#xff08;以下简称SNPE&#xff09;支持Caffe、ONNX、PyTorch和TensorFlow等不同ML框架的算子。对于某些特定的不支持的算子&#xff0c;我们介绍一种算子等效替换的方法来完成模型转换。本案例来源于https://github.com/quic/qidk…

从零开始手写mmo游戏从框架到爆炸(十六)— 客户端指定回调路由与登录

导航&#xff1a;从零开始手写mmo游戏从框架到爆炸&#xff08;零&#xff09;—— 导航-CSDN博客 我们这次来把注册、登录、选择英雄&#xff0c;进入主页-选择地图的功能完善。 在这之前&#xff0c;我们还要解决一个问题&#xff0c;就是服务端往客户端发消息的路由问题…

CSS 不同颜色的小圆角方块组成的旋转加载动画

<template><!-- 创建一个装载自定义旋转加载动画的容器 --><view class="spinner"><!-- 定义外部包裹容器,用于实现整体旋转动画 --><view class="outer"><!-- 定义四个内部小方块以形成十字形结构 --><view clas…

vtk.js加载dicom,获取世界点的坐标、两点之间的距离

通过点击vtk的renderWindow&#xff0c;获取坐标点。 获取点的坐标有vtkCellPicker和vtkPointPicker两个方法&#xff0c;区别在于vtkCellPicker可以区分是否点击在模型上&#xff0c;推荐使用vtkCellPicker。 获取两点之间距离使用vtkMath的方法&#xff0c;vtkMath.distance…

阿里云k8s容器部署consul集群的高可用方案

一、背景 原本consul集群是由三个server节点搭建的&#xff0c;购买的是三个ecs服务器&#xff0c; java服务在注册到consul的时候&#xff0c;随便选择其中一个节点。 从上图可以看出&#xff0c; consul-01有28个服务注册&#xff0c;而consul-02有94个服务&#xff0c;co…

一凸包----------12,分而治之(2)

在上节中&#xff0c;两部分子凸包有重合的部分&#xff0c;不简洁。这一节是沿着某个方向&#xff0c;子凸包不重叠&#xff0c;如下图 根据以前的方法&#xff0c;很可能认为是两个子凸包上顶点与上顶点相连&#xff0c;下顶点与下顶点相连&#xff0c;形成两条支撑线&#…

算法沉淀——二叉树中的深搜(leetcode真题剖析)

算法沉淀——二叉树中的深搜 01.计算布尔二叉树的值02.求根节点到叶节点数字之和03.二叉树剪枝04.验证二叉搜索树05.二叉搜索树中第K小的元素06.二叉树的所有路径 二叉树的深度优先搜索是一种遍历二叉树的方法&#xff0c;它通过深度递归的方式探索树的结构。有两种主要形式&am…

ubuntu 22.04 图文安装

ubuntu 22.04.3 live server图文安装 一、在Vmware里安装ubuntu 22.04.3 live server操作系统 选择第一个选项开始安装 选择English语言 选择中间选项不更新安装&#xff0c;这是因为后续通过更换源之后再更新会比较快 键盘设计继续选择英文&#xff0c;可以通过语言选择…

【微服务生态】Dubbo

文章目录 一、概述二、Dubbo环境搭建-docker版三、Dubbo配置四、高可用4.1 zookeeper宕机与dubbo直连4.2 负载均衡 五、服务限流、服务降级、服务容错六、Dubbo 对比 OpenFeign 一、概述 Dubbo 是一款高性能、轻量级的开源Java RPC框架&#xff0c;它提供了三大核心能力&#…

zabbix5.0利用percona监控MySQL

具体来说包括: Percona Monitoring Plugins 这是一组用于收集MySQL实例各种性能指标和状态的插件脚本,包括: mysqld_stats.pl - 收集服务器状态计数器mysqld_statement_replay.pl - 进行负载模拟测试pt-status - 收集InnoDB资源使用情况等 Percona Templates 基于这些插件收集…

陪诊小程序|陪诊系统解决就医繁忙问题

陪诊现在是个新兴行业&#xff0c;具有比较大的市场前景&#xff0c;陪诊小程序更是行业蓝海&#xff0c;不仅为用户解决了无人陪同看病、医院科室流程繁杂的问题&#xff0c;更为陪诊师提供了线上直接获客的渠道&#xff0c;可谓发展前景不可估量。陪诊服务提供者及需求者来说…

C++之Easyx——图形库的基本功能(2):来点色彩

一、setbkcolor 函数定义 void EGEAPI setbkcolor(color_t color, PIMAGE pimg NULL); // 设置当前绘图背景色&#xff08;设置并做背景色像素替换&#xff09; 使用说明 void EGEAPI setbkcolor(颜色RGB, PIMAGE pimg NULL); // 设置当前绘图背景色&#xff08;…

关于在Windows上socket组播通信的一些问题

一、Windows上的组播通信 基本和linux上的socket编程一致&#xff0c;稍微有点区别 以下是我测验可以使用的代码&#xff0c; 客户端 // 广播处理回复消息回调 typedef void(*handMulticastRsp)(char* rspstr, int len); // 发送组播消息 // buff 发送内容 // len 发送内容…

Linux操作系统应用软件编程

今天是学习嵌入式相关内容的第二十四天 b -- block -- 块设备文件 --- 硬盘&#xff08;存储设备&#xff09; c -- character -- 字符设备文件 --- 鼠标 &#xff08;输入输出设备&#xff09; d -- directory -- 目录文件 - -- regular -- 普通文件 ---…

十、计算机视觉-腐蚀操作

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、什么是腐蚀二、如何实现腐蚀三、腐蚀的原理 一、什么是腐蚀 在我们生活中常会见到腐蚀&#xff0c;比如金属表面受到氧化或其他化学物质的侵蚀&#xff0c;导致…

【办公类-16-07-04】合并版“2023下学期 中班户外游戏(有场地和无场地版,一周一次)”(python 排班表系列)

背景需求&#xff1a; 把 无场地版&#xff08;贴周计划用&#xff09; 和 有场地版&#xff08;贴教室墙壁上用&#xff09; 组合在一起&#xff0c;一个代码生成两套。 【办公类-16-07-02】“2023下学期 周计划-户外游戏 每班1周五天相同场地&#xff0c;6周一次循环”&…

基于RHEL8部署Zabbix6.0,监控不再困难!

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

Linux是什么

文章目录 Linux是什么Linux之前Unix发展史MulticsUnicsUnixUNIX分支--BSDUNIX分支--System VMinixGUN计划GPLXFree86Linux 开源软件和闭源软件开源软件闭源软件/专利软件(copyright) Linux的内核版本Linux发行版 Linux是什么 Linux到底是操作系统还是应用程序呢&#xff1f;Li…

利用nbsp设置空格

想要实现上面效果&#xff0c;一开始直接<el-col :span"8" >{{ item.name }} </el-col> 或者<el-col :span"8" >{{ item.name }}</el-col>或者<el-col :span"8" >{{ item.name }}</el-col> 都无…