二叉搜索树(二叉排序树,二叉查找树)(附图详解+代码实现+应用分析)

news2024/11/23 18:29:43

最近学习了有关搜索二叉树的相关知识,在此特意将该知识进行总结分享,希望对大家有所帮助。

文章目录

  • 一.二叉搜索树
    • 1.1二叉搜索树的概念
    • 1.2二叉搜索树的操作(含思路分析+代码实现)
        • 1.2.1二叉搜索树的查找(递归实现看最后总代码)
        • 1.2.2二叉搜索树的插入(递归实现看最后总代码)
        • 1.2.3二叉搜索树的删除(递归实现看最后总代码)
        • 总代码(含递归实现):
  • 二. 二叉搜索树的应用
  • 三.二叉搜索树的性能分析

一.二叉搜索树

1.1二叉搜索树的概念

二叉搜索树又叫二叉排序树,二叉查找树,可以为空,也可以不为空,具体有以下的特性:

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

在这里插入图片描述

1.2二叉搜索树的操作(含思路分析+代码实现)

一个基本的二叉搜索树需要有哪些函数(接口)呢?

1.2.1二叉搜索树的查找(递归实现看最后总代码)

基于二叉搜索树的结构的特点:左节点小于根,右节点大于根
查找的思路分析:

a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
b、最多查找高度次,走到到空,还没找到,这个值不存在。

图解:
在这里插入图片描述

代码实现:

	bool Find(const K& key)       //key为寻找关键字
	{
		if (_root == nullptr)    //如果为空,直接返回
		{
			return false;
		}
		else                      
		{
			Node* cur = _root;         //用cur迭代寻找
			while (cur)
			{
				if (key > cur->_key)      //如果key比根节点的值大,则去右子树寻找
				{
					cur = cur->_right;
				}
				else if(key < cur->_key)   //如果key比根节点的值小,则去左子树寻找
				{
					cur = cur->_left;
				}
				else
				{
					return true;       //相等,返回true
				}
			} 
			return false;          //出循环,还没找到,返回false
		}
	}
1.2.2二叉搜索树的插入(递归实现看最后总代码)

插入思路分析:

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

图解:
在这里插入图片描述

代码实现:

	bool insert(const K& key)
	{
		if (_root == nullptr)    //如果为空树,直接插入
		{
			Node* newNode = new Node(key);    //new一个节点
			_root = newNode;                   //链接

			return true;      //插入成功,返回true
		}
		else
		{
			Node* parent = nullptr;     //因为要链接,所以需要记录父亲
			Node* cur = _root;   
			while (cur)                   //while循环和find函数类似,目的找到合适位置插入
			{
				if (key > cur->_key)
				{
					parent = cur;    //记录父节点
					cur = cur->_right;
				}
				else if(key < cur->_key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false;    //搜索二叉树里面的值不能重复,如果已经存在,则返回false
				}
			}
			//cur空,结束循环,代表找到位置了
			Node* newNode = new Node(key);      //new节点
			if (key < parent->_key)            
				parent->_left = newNode;
			if (key > parent->_key)          //判断是链接到父亲的左还是右
				parent->_right = newNode;

			return true;
		}
	}
1.2.3二叉搜索树的删除(递归实现看最后总代码)

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

a. 要删除的结点无孩子结点 //直接删除
b. 要删除的结点只有左孩子结点 //托孤
c. 要删除的结点只有右孩子结点 //托孤
d. 要删除的结点有左、右孩子结点 //替换法删除

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

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

托孤发删除的原理:
假设我们要删除【图一】的22,它只有一个孩子,就可以使用托孤的方法,直接让父节点(15)指向它的孩子(17);
该方法也可以在删除没有孩子的节点上使用(将它看作有一个为NULL的孩子即可),例如下【图二】,假设删除17;
在这里插入图片描述

在这里插入图片描述

替换法删除的原理:
方法作用于有两个孩子的节点:

a,找到该节点左子树的最右节点(即左子树最大的节点)或则右子树的最左节点(即右子树最小值);
b,然后将待删除节点的值与找到的最大(或最小)节点的值进行交换,转化为删除找到的这个节点;
c,最后用上面的托孤的方法删除该节点

在这里插入图片描述

代码实现:

	bool Erase(const K& key)
	{
		if (_root == nullptr)     //如果树为空,直接返回
			return false;
		else                     //树不为空
		{
			Node* parent = _root;
			Node* cur = _root;       
			while (cur)                 //while循环,先找到待删除节点以及记录它父节点
			{                           //该循环体与find和insert类似
				if (key > cur->_key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (key < cur->_key) 
				{
					parent = cur;
					cur = cur->_left;
				}
				else                             //找到该节点了,开始删除
				{
				    //1.只有一个孩子或则没有孩子
					Node* del = cur;                    //保存要删除的这个节点,防止等下托孤后该节点找不到
					if (!(cur->_left && cur->_right))    //两个孩子都存在的逻反就是有一个孩子或则没有孩子
					{
						if (cur == _root)       //这里,判断如果该节点是头节点,直接将头节点置空,返回
						{
							_root = nullptr;         
						}
						if (parent->_left == cur)     //该删除节点为父节点的左子树,则将它的孩子连接到它父节点的左子树上
						{
							if (cur->_left)           //判断孩子是待删除节点的哪个孩子
							{
								parent->_left = cur->_left;
							}
							else
							{
								parent->_left = cur->_right;
							}
							return true;
						}
						else                        //和上面一样
						{
							if (cur->_left)
							{
								parent->_right = cur->_left;
							}
							else
							{
								parent->_right = cur->_right;
							}
							return true;
						}
					}
					//2.待删除节点有两个孩子
					else                            //这里实现的是替换法中找右子树中最小的节点
					{
						Node* rightMinparent = cur;        //记录最小节点的父亲,因为等下需要托孤
						Node* rightMin = cur->_right;      //记录最小节点
						while (rightMin->_left)
						{
							rightMinparent = rightMin;
							rightMin = rightMin->_left;
						}
						//到这里说明中找到了右树的最小节点
						swap(rightMin->_key, cur->_key);    //交换最小节点与待删除节点的值,转换为删除这个最小节点
						if (rightMinparent->_left==rightMin)     //下面的逻辑与上面的托孤一样
						{
							rightMinparent->_left = rightMin->_right;
						}
						else
						{
							rightMinparent->_right = rightMin->_right;
						}
						delete rightMin;                      //最后删除
					}
					return true;
				}
			}
			return false;
		}
	}
总代码(含递归实现):
#pragma once
#include<iostream>

using namespace std;

template<class K>
struct Binary_Serach_TreeNode
{
	typedef Binary_Serach_TreeNode<K> Node;
	Node* _left;
	Node* _right;
	K _key;

	Binary_Serach_TreeNode(const K& key)
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
	{}

};

template<class K>
class B_S_Tree
{
public:
	typedef Binary_Serach_TreeNode<K> Node;
	//B_S_Tree() = default;    //强制生成默认构造
	B_S_Tree()
		:_root(nullptr)
	{}

	B_S_Tree(B_S_Tree<K>& bst)
	{
		_root = copy(bst._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;
	}
	B_S_Tree<K>& operator=(B_S_Tree<K> bst)
	{
		std::swap(bst._root, _root);
		return *this;
	}
	bool insert(const K& key)
	{
		if (_root == nullptr)    //如果为空树,直接插入
		{
			Node* newNode = new Node(key);    //new一个节点
			_root = newNode;                   //链接

			return true;      //插入成功,返回true
		}
		else
		{
			Node* parent = nullptr;     //因为要链接,所以需要记录父亲
			Node* cur = _root;   
			while (cur)                   //while循环和find函数类似,目的找到合适位置插入
			{
				if (key > cur->_key)
				{
					parent = cur;    //记录父节点
					cur = cur->_right;
				}
				else if(key < cur->_key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false;    //搜索二叉树里面的值不能重复,如果已经存在,则返回false
				}
			}
			//cur空,结束循环,代表找到位置了
			Node* newNode = new Node(key);      //new节点
			if (key < parent->_key)            
				parent->_left = newNode;
			if (key > parent->_key)          //判断是链接到父亲的左还是右
				parent->_right = newNode;

			return true;
		}
	}

	bool Erase(const K& key)
	{
		if (_root == nullptr)     //如果树为空,直接返回
			return false;
		else                     //树不为空
		{
			Node* parent = _root;
			Node* cur = _root;       
			while (cur)                 //while循环,先找到待删除节点以及记录它父节点
			{                           //该循环体与find和insert类似
				if (key > cur->_key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (key < cur->_key) 
				{
					parent = cur;
					cur = cur->_left;
				}
				else                             //找到该节点了,开始删除
				{
				    //1.只有一个孩子或则没有孩子
					Node* del = cur;                    //保存要删除的这个节点,防止等下托孤后该节点找不到
					if (!(cur->_left && cur->_right))    //两个孩子都存在的逻反就是有一个孩子或则没有孩子
					{
						if (cur == _root)       //这里,判断如果该节点是头节点,直接将头节点置空,返回
						{
							_root = nullptr;         
						}
						if (parent->_left == cur)     //该删除节点为父节点的左子树,则将它的孩子连接到它父节点的左子树上
						{
							if (cur->_left)           //判断孩子是待删除节点的哪个孩子
							{
								parent->_left = cur->_left;
							}
							else
							{
								parent->_left = cur->_right;
							}
							return true;
						}
						else                        //和上面一样
						{
							if (cur->_left)
							{
								parent->_right = cur->_left;
							}
							else
							{
								parent->_right = cur->_right;
							}
							return true;
						}
					}
					//2.待删除节点有两个孩子
					else                            //这里实现的是替换法中找右子树中最小的节点
					{
						Node* rightMinparent = cur;        //记录最小节点的父亲,因为等下需要托孤
						Node* rightMin = cur->_right;      //记录最小节点
						while (rightMin->_left)
						{
							rightMinparent = rightMin;
							rightMin = rightMin->_left;
						}
						//到这里说明中找到了右树的最小节点
						swap(rightMin->_key, cur->_key);    //交换最小节点与待删除节点的值,转换为删除这个最小节点
						if (rightMinparent->_left==rightMin)     //下面的逻辑与上面的托孤一样
						{
							rightMinparent->_left = rightMin->_right;
						}
						else
						{
							rightMinparent->_right = rightMin->_right;
						}
						delete rightMin;                      //最后删除
					}
					return true;
				}
			}
			return false;
		}
	}

	bool Find(const K& key)       //key为寻找关键字
	{
		if (_root == nullptr)    //如果为空,直接返回
		{
			return false;
		}
		else                      
		{
			Node* cur = _root;         //用cur迭代寻找
			while (cur)
			{
				if (key > cur->_key)      //如果key比根节点的值大,则去右子树寻找
				{
					cur = cur->_right;
				}
				else if(key < cur->_key)   //如果key比根节点的值小,则去左子树寻找
				{
					cur = cur->_left;
				}
				else
				{
					return true;       //相等,返回true
				}
			} 
			return false;          //出循环,还没找到,返回false
		}
	}
	bool insertR(const K& key)
	{
		return _inserR(_root, key);
	}
	bool  FindR(const K& key)
	{
		return _FindR(_root,key);
	}
	bool  EraseR(const K& key)
	{
		return _EraseR(_root, key);
	}

	void OrdPrint()
	{
		_OrdPrint(_root);
		cout << endl;
	}

	~B_S_Tree()
	{
		//_Destory(_root);
	}
private:
	void _OrdPrint(Node* root)
	{
		if (root == nullptr)
			return;

		_OrdPrint(root->_left);
		cout << root->_key <<' ';
		_OrdPrint(root->_right);
	}
	bool _FindR(Node* root, const K& key)
	{
		if (root == nullptr)
			return false;
		if (key < root->_key)
			return _FindR(root->_left, key);
		else if (key < root->_key)
			return _FindR(root->_right, key);
		else
			return true;
	}
	bool _inserR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}
		if (key < root->_key)
			return _inserR(root->_left, key);
		else if (key > root->_key)
			return _inserR(root->_right, key);
		else
			return false;
	}
	bool _EraseR(Node*& root,const K&  key)
	{
		if (root == nullptr)
			return false;
		if (key < root->_key)
			_FindR(root->_left, key);
		else if (key < root->_key)
			_FindR(root->_right, key);
		else
		{
			Node* del = root;
			if (root->_left==nullptr)
				root = root->_right;
			else if(root->_right==nullptr)
				root = root->_left;
			else
			{
				Node* rightMinparent = root;
				Node* rightMin = root->_right;
				while (rightMin->_left)
				{
					rightMinparent = rightMin;
					rightMin = rightMin->_left;
				}
				swap(root->_key, rightMin->_key);
				return _EraseR(root->_right, key);
			}
			delete del;
			return true;

		}
	}
	void _destory(Node* root)
	{
		if (root == nullptr)
			return;
		else
		{
			_destory(root->_left);
			_destory(root->_right);
			delete root;
		}
	}

private:
	Node* _root;
};

二. 二叉搜索树的应用

应用一:
K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

应用二:
KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。

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

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树)【如图左】,其平均比较次数为:log_2 N
最差情况下,二叉搜索树退化为单支树(或者类似单支)【如图右】,其平均比较次数为:N

在这里插入图片描述
本章完~看完觉得对你有用就点个赞吧!

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

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

相关文章

RocketMQ学习笔记:零拷贝

这是本人学习的总结&#xff0c;主要学习资料如下 马士兵教育rocketMq官方文档 目录 1、零拷贝技术1.1、什么是零拷贝1.2、mmap()1.3、Java中的零拷贝 1、零拷贝技术 1.1、什么是零拷贝 使用传统的IO&#xff0c;从硬盘读取数据然后发送到网络需要经过四个步骤。 通过DMA复…

外包干了5年,技术退步明显.......

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入杭州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…

基于Scapy国内城市空气质量数据采集系统设计与实现

代码和完整的报告在文章最后 城市空气质量数据采集系统设计与实现 &#x1f3d9;️ 研究背景 &#x1f32c;️ 城市化与环境挑战&#xff1a;随着城市化进程的加快&#xff0c;环境污染问题&#xff0c;尤其是空气质量问题&#xff0c;已成为公众关注的焦点。数据监测的重要性…

canvas跟随鼠标移动画带透明度的线(画涂鸦)

提示&#xff1a;canvas画线 文章目录 前言一、带透明度的线二、试错&#xff0c;只有lineTo的时候画&#xff0c;只有最后地方是透明度的三、试错&#xff0c;只存上一次的点&#xff0c;线会出现断裂的情况总结 前言 一、带透明度的线 test.html <!DOCTYPE html> &l…

【日常linux操作命令】

文章目录 1、查看服务器信息1.1、查看内存1.2、查看磁盘1.3、查看CPU信息 2、清理内存缓存2.1、清理PageCache&#xff1a;2.2、清理Dentries和Inodes&#xff1a;2.3、同时清理PageCache、Dentries和Inodes&#xff1a;2.4、清理日志文件2.5、清理临时文件 3、查找文件3.1、查…

在Ubuntu 22.04上源码安装python3.7及setuptools及pip

背景 随着时代发展&#xff0c;ubuntu的版本也在飞速迭代&#xff0c;现在ubuntu中默认带的python3已经到python3.11了&#xff0c;并且python2也已经在新的版本中被废弃了。 但是还有一些场景下会用到python3.7&#xff0c;这里给一些说明&#xff0c;方便大家使用。 操作 …

K3 计划订单投放时,将“关联物料”传递到采购和生产订单的“组部件”字段

参考K/3 WISE 中MRP计算投放过程中 销售订单自定义字段怎么携带到任务单这篇文章&#xff0c;进行优化。 在表ICMrpDestBills下增加触发器&#xff0c;代码如下 CREATE TRIGGER [dbo].[ICMrpDestBills_update]ON [dbo].[ICMrpDestBills]AFTER INSERT,UPDATE AS BEGINSET NO…

【SpringCloud】探索Eureka注册中心

&#x1f3e1;浩泽学编程&#xff1a;个人主页 &#x1f525; 推荐专栏&#xff1a;《深入浅出SpringBoot》《java对AI的调用开发》 《RabbitMQ》《Spring》《SpringMVC》《项目实战》 &#x1f6f8;学无止境&#xff0c;不骄不躁&#xff0c;知行合一 文章目录 …

AISD智能安全配电装置--智能监测、远程监控

安科瑞薛瑶瑶18701709087 AISD100单相、AISD300三相智能安全配电装置是安科瑞专为低压配电侧开发的一款智能安全配电产品。主要针对低压配电系统人身触电、线路老化、短路、漏电等原因引起电气安全问题而设计。 产品主要应用于学校、加油站、医院、银行、疗养院、康复中心、敬…

Douyin视频详情数据API接口(视频详情,评论)

抖音官方并没有直接提供公开的视频详情数据采集API接口给普通用户或第三方开发者。抖音的数据采集通常受到严格的限制&#xff0c;以保护用户隐私和平台安全。 请求示例&#xff0c;API接口接入Anzexi58 如果您需要获取抖音视频详情数据&#xff0c;包括评论、点赞等&#xff…

Java中 List 集合,通过 Stream 流进行排序总结

一、数据准备 public class OrderTest {private String channelCode;private BigDecimal rate;// 省略 getter、setter、toString()、constructor }List<OrderTest> orderTestList new ArrayList<>();OrderTest z09 new OrderTest("Z09", new BigDeci…

EasyCVR在银河麒麟V10系统中启动异常及解决方法

安防监控视频平台EasyCVR具备较强的兼容性&#xff0c;它可以支持国标GB28181、RTSP/Onvif、RTMP&#xff0c;以及厂家的私有协议与SDK&#xff0c;如&#xff1a;海康ehome、海康sdk、大华sdk、宇视sdk、华为sdk、萤石云sdk、乐橙sdk等。平台兼容性强&#xff0c;支持Windows系…

抖音视频关键词爬虫批量采集软件|视频提取下载工具

视频关键词批量采集软件 — 助力您快速获取所需视频 主要功能&#xff1a; 关键词批量提取视频和单独视频提取&#xff0c;提取后下载功能。 功能解析&#xff1a; 1. 关键词批量提取视频的解析 通过输入关键词进行视频搜索和提取。例如&#xff0c;输入“汽车配件”&#x…

抓取京东/淘宝类数据#Javascript#商品详情图片

提出问题 如何在京东商城爬取出各个商品的相关信息(价格、名称、评价、店铺名等等)&#xff0c;比如&#xff0c;打开web京东网站&#xff0c;那么商品展示列表的所有商品的信息&#xff0c;怎么爬下来&#xff0c;怎么保存到表格中&#xff1f; 我们来看看怎么实现这个功能。…

嵌入式系统调研报告

嵌入式系统调研报告 一、发展历程与趋势二、行业现状三、嵌入式工程师对职业生涯的看法 一、发展历程与趋势 嵌入式系统的发展历程可以分为四段&#xff1a; 20世纪60年代。当时&#xff0c;计算机技术还处于早期阶段&#xff0c;主要应用于科研和军事领域&#xff0c;比如&am…

【快速解决】解决谷歌自动更新的问题,禁止谷歌自动更新,如何防止chrome自动升级 chrome浏览器禁止自动升级设置方法

目录 问题描述 解决方法 1、搜索栏搜索控制面板 2、搜索&#xff1a;服务 ​编辑 3、点击Windows工具 4、点击服务 ​5、禁止谷歌更新 问题描述 由于我现在需要装一个谷歌的驱动系统&#xff0c;但是目前的谷歌驱动系统的版本都太旧了&#xff0c;谷歌自身的版本又太新了…

【scala】使用gradle和scala构建springboot程序

零、版本说明: springboot: 2.7.18 使用log4j2&#xff0c;不使用springboot自带的logback scala版本&#xff1a;2.11 jackson版本&#xff1a;2.16.0 一、依赖&#xff1a; buildscript {dependencies {// using spring-boot-maven-plugin as package toolclasspath("…

Docker实战指南:编辑Dockerfile、编译镜像、启动容器,一网打尽

万能dockerfile编写模板文件 FROM openjdk:11.0 as builder WORKDIR application ARG JAR_FILEtarget/*.jar COPY ${JAR_FILE} application.jar RUN java -Djarmodelayertools -jar application.jar extractFROM openjdk:11.0 WORKDIR application COPY --frombuilder applica…

【浏览器渲染-输入Url到页面渲染全流程】

概述 在面试题中有一道经典面试题就是浏览器输入url之后发送了什么&#xff0c;看了下网上的大多数文章都感觉不太全&#xff0c;所以这里梳理了比较全的流程&#xff0c;如果有误&#xff0c;欢迎评论指正。本文大致是从以下内容概览入手&#xff0c;有需要的可以跳转到感兴趣…

【C语言】函数atoi的详解与实现~

一、atoi函数的讲解 函数声明&#xff1a;int atoi( const char *string );头 文 件 &#xff1a;<stdlib.h>函数功能&#xff1a;对指针string所指向的字符串&#xff0c;将其中的一段连续的(0~9)数字按照( int )返回&#xff1b;函数特点&#xff1a;&#xff08;这里…