【C++】搜索二叉树

1. 二叉搜索树

a. 二叉搜索树的概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  1. 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  2. 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  3. 它的左右子树也分别为二叉搜索树
  4. 它的中序遍历是得到的结果是升序

b. 二叉搜索树的实现

1. 搜索二叉树的构建

代码

template<class K>
struct BinNode
{
	BinNode(K key)
		:_key(key)
	{}
	K  _key;
	BinNode* left = nullptr;
	BinNode* right = nullptr;
};
template<class K>
class BinNodeTree
{
public:
	typedef BinNode<K> node;
private:
	node* _root = nullptr;
};

2. 二叉树的插入 

循环实现思路

返回类型是 bool ,判断能否插入传入的值(二叉搜索树不存相同的值)

跟节点比较,如果 插入的值 key > 节点的值,则走节点的右子树 ; 如果插入的值 key < 节点的值,则走左子树 ; 如果等于,返回 false ;如果结点为空 ,结束循环 ,new 一个新的结点,需要空节点的父节点去链接新结点,所以我们一定要定义一个父节点

注意:

  1. 由于不知道新结点应该是父节点的左子树还是右子树,这里链接就需要判断一下,如果空结点是父结点的左子树,那么就链接左边,反之亦然
  2. 一开始的根节点为空,所以这里需要特殊处理一下,让根节点成为插入的第一个值构造的新节点

代码

bool insert(const K &key)
{
	if (_root == nullptr)
	{
		_root = new node(key);
		return true;
	}
	node* prev = _root;
	node* cur = _root;
	while (cur)
	{
		if (key > cur->_key)
		{
			prev = cur;
			cur = cur->right;
		}
		else if (key < cur->_key)
		{
			prev = cur;
			cur = cur->left;
		}
		else
		{
			return false;
		}
	}
	cur = new node(key);
	if (key > prev->_key)
	{
		prev->right = cur;
	}
	else
	{
		prev->left = cur;
	}
	return true;
}

 递归实现思路

大体思路和循环的思路差不多,但是略有不同

  1. 由于递归函数一定需要结点的地址(可以走左右子树)和 需要插入的值,给这个递归函数传入的结点的地址一定是根节点,但是根节点在类里面不能访问,所以这里我们就弄一层包装,一个函数是传根节点的地址的,另一个函数用来递归实现(都在类里面)
  2. 这里我们同样需要判断插入的值 key 和 结点的值的关系,但是这里可以不需要父节点,我们在递归函数的形参上面跟之前有所不同,这里的形参用了引用,使得可以直接让空结点存新结点的地址

代码

bool Insert(const K& key)
{
	return _Insert(_root, key);
}
bool _Insert(node*& root, const K& key)
{
	if (root == nullptr)
	{
		root = new node(key);
		return true;
	}
	if (key == root->_key)
	{
		return false;
	}
	else if (key > root->_key)
	{
		_Insert(root->right, key);
	}
	else
	{
		_Insert(root->left, key);
	}
}

   3. 二叉树的查找

循环实现思路

跟节点比较,如果 插入的值 key > 节点的值,则走节点的右子树 ; 如果插入的值 key < 节点的值,则走左子树 ; 如果结点为空 return false;

代码

bool find(const K& key)
{
	node* cur = _root;
	if (cur == nullptr)
	{
		return false;
	}
	while (cur)
	{
		if (key > cur->_key)
		{
			cur = cur->right;
		}
		else if(key < cur->_key)
		{
			cur = cur->left;
		}
		else
		{
			return true;
		}
	}
	return false;
}

 

递归实现思路

和插入的递归思路有一点相同,都要封装一层

剩下的思路和循环是一样的

代码

bool Find(const K& key)
{
	return _Find(_root, key);
}
bool _Find(node* root, const K& key)
{
	if (root == nullptr)
	{
		return false;
	}
	if (key == root->_key)
	{
		return true;
	}
	else if (key > root->_key)
	{
		_Find(root->right, key);
	}
	else
	{
		_Find(root->left, key);
	}
}

4. 二叉树的删除

循环实现思路

大体上遇到的情况分三种情况:

  1. 删除的结点左右子树为空
  2. 删除的结点左子树或者右子树为空
  3. 删除的结点左右子树都不为空

第一种情况:

实际上,第一种情况的操作可以归到第二种里面

第二种情况:

如果删除的结点左子树为空,那么我们需要这个结点的 父节点的左子树或者右子树(根据删除结点是父节点的左子树还是右子树进行判断) 是删除结点的右子树

如果删除结点是父节点的左子树,那么父节点左边链接,反之则相反

如图:

如果删除的结点右子树为空,那么我们需要这个结点的 父节点的 左子树或者右子树 (根据删除结点是父节点的左子树还是右子树进行判断)是删除结点的左子树

如果删除结点是父节点的左子树,那么父节点左边链接,反之则相反

如图:

前者我们说了,如果删除节点两边都为空,则也归到第二种,此时只要判断删除节点是父节点的左子树还是右子树就好了,无需管链接删除节点的左子树还是右子树

如图:


 

第三种情况:

由于两边都不为空,我们不好直接删除,这时候,我们需要把删除节点当根节点(同样构成搜索二叉树),找这个搜索二叉树的某个节点的值,既可以比左边节点的值大(除根节点外),也可以比右边节点的值小,有两个答案:这个搜索二叉树的左子树的最大值和右子树的最小值

如图:

如何找右子树的最小值呢?先得到右子树的地址,再一直往左走,直到节点为空,则它的父节点就是我们要找的,所以我们需要定义一个父节点,让删除节点存父节点的值,由于父节点的左子树为空,那么删除节点要链接父节点的右子树,跟之前第二种情况一样,要知道父节点是上一个父节点的左子树还是右子树(避免删除节点就是根节点的情况导致的错误),再删除父节点

找左子树的最大值相同道理

注意事项:

  1. 第二种情况有特例,可能删除的是根节点,并且左子树或者右子树为空

如果左子树为空,这个时候我们只需要直接让根节点存右子树的地址,释放原来的节点

反之则相反(如果同样为左右子树都为空,同样适用)

如图:

  1. 第三种情况一定要注意本来定义的两个指针(一前一后),最开始初始化时,都指向删除节点的地址,不要其中一个置空(防止删除的节点是父节点,而导致置空节点不能进入循环发生的一系列错误)

代码

bool erase(const K &key)
{
	node* parent = _root;
	node* cur = _root;
	while (cur)
	{
		if (key > cur->_key)
		{
			parent = cur;
			cur = cur->right;
		}
		else if (key < cur->_key)
		{
			parent = cur;
			cur = cur->left;
		}
		else
		{
			if (cur->left == nullptr)
			{
				if (cur == _root)
				{
					_root = cur->right;
				}
				if (parent->left == cur)
				{
					parent->left = cur->right;
				}
				else
				{
					parent->right = cur->right;
				}
				delete cur;
			}
			else if (cur->right == nullptr)
			{
				if (cur == _root)
				{
					_root = cur->left;
				}
				if (parent->left == cur)
				{
					parent->left = cur->left;
				}
				else
				{
					parent->right = cur->left;
				}
				delete cur;
			}
			else
			{
				node* MinRight = cur->right;
				node* pMinRight = cur;
				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;
			}
			
			return true;
		}
	}
	return false;
}

 递归实现思路

和插入的递归思路有些一样,都需要包装,都需要传指针的引用

第一种情况和第二种情况和循环思路很像,由于是引用,这里我们不需要判断是左子树还是右子树,直接赋值即可

第三种情况前面还是一样,找到左子树的最大值或者右子树的最小值

如果找左子树的最大值:

最大值我们可以在通过循环来找,找到之后,可以选择交换删除节点的值和最大值,再次进行递归,删除的值key不变

或者是只让删除节点的值换成最大值,其它不变,递归时,传的根节点就是删除节点的左子树,删除的值key就是最大值

注意:

第三种情况递归时,不可以直接传存最大值的节点(那个最大值节点是局部变量,而引用接收局部变量会出很大问题)

代码

bool Erase(const K& key)
{
	return _Erase(_root,key);
}
bool _Erase(node*& root,const K& key)
{
	if (root == nullptr)
	{
		return false;
	}
	if (key > root->_key)
	{
		_Erase(root->right, key);
	}
	else if(key < root->_key)
	{
		_Erase(root->left, key);
	}
	else
	{
		node* cur = root;
		if (root->left == nullptr)
		{
			root = root->right;
			delete cur;
		}
		else if (root->right == nullptr)
		{
			root = root->left;
			delete cur;
		}
		else
		{
			cur = cur->left;
			while (cur->right)
			{
				cur = cur->right;
			}
			int k = root->_key = cur->_key;
			_Erase(root->left, k);
		}
		return true;
	}
}

5.  二叉树的销毁

代码

~BinNodeTree()
{
	_BinNodeTree(_root);
}
void _BinNodeTree(node* root)
{
	if (root == nullptr)
	{
		return;
	}
	_BinNodeTree(root->left);
	_BinNodeTree(root->right);
	delete root;
}

后序遍历即可 

6.  二叉树的拷贝构造

代码

BinNodeTree(const BinNodeTree<K>& t)
{
	_root = copy(t._root,_root);
}
node* copy(node* t1, node* t2)
{
	if (t1 == nullptr)
	{
		return nullptr;
	}
	t2 = new node(t1->_key);
	t2->left = copy(t1->left, t2->left);
	t2->right = copy(t1->right, t2->right);
	return t2;
}

2. 二叉搜索树的应用

  1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到

的值

如:查找一个单词是否拼写正确

    2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对,该种方

式在现实生活中非常常见

如:单词中英文查找 , 统计单词出现次数

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

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

相关文章

解决方案:如何安装neo4j软件

文章目录 一、安装JDK二、安装neo4j 一、安装JDK 第一步先安装JDK&#xff0c;因为neo4j环境需要JDK&#xff0c;过程比较多&#xff0c;截图如下&#xff1a; 安装JDK网址 https://www.oracle.com/java/technologies/downloads winR&#xff0c;输入cmd&#xff0c;再输入j…

【Faster Bing】Bing 搜索结果取消跳转至 bing.com/ck

更快的 Bing (Faster Bing) 1. 介绍 项目地址&#xff1a; GitHub: https://github.com/jiang-taibai/faster-bingGitee: https://gitee.com/jiang-taibai/faster-bingGreasyFork: https://greasyfork.org/en/scripts/490999-faster-bing 在使用 Bing 搜索时&#xff0c;Bin…

如何遍历整个DOM树

原文链接&#xff1a;[如何遍历整个DOM树(外网原文链接)](https://chrisdeo.github.io/2019/07/20/%E5%A6%82%E4%BD%95%E9%81%8D%E5%8E%86%E6%95%B4%E4%B8%AADOM%E6%A0%91/) 作为前端开发工程师&#xff0c;我们大部分工作内容其实还是围绕着DOM在进行Javascript的编写&#xf…

AMD本月发布的成本优化型Spartan UltraScale+ FPGA系列

随着 FPGA 在更多应用中的使用&#xff0c;AMD 推出了最新的成本、功耗与性能平衡的系列产品。为了扩展其可编程逻辑产品组合&#xff0c;AMD最近推出了最新的成本优化型 Spartan FPGA 系列。随着 FPGA 应用于越来越多的产品和设备&#xff0c;设计人员可能经常发现自己正在寻找…

前端实现浏览器自定义滚动条

前言&#xff1a; 最近有个项目&#xff0c;产品觉得浏览器默认滚动条太丑了。想美化一下&#xff0c;比如自定义颜色&#xff0c;加上圆角&#xff0c;宽高都要更改一下。我查了资料和文档总结了一下 写法&#xff0c;特此记录以便之后使用。 浏览器滚动条api 总结&#xff…

git基础-tagging

tagging 与大多数版本控制系统一样&#xff0c;Git具有将存储库历史中的特定点标记为重要tag的能力。通常&#xff0c;人们使用此功能来标记发布点&#xff08;例如v1.0&#xff0c;v2.0等&#xff09;。在本节中&#xff0c;将学习如何列出现有的标签&#xff0c;如何创建和删…

智慧公厕的技术融合策略

智慧公厕是迎合现代城市发展需要的一项重要基础设施&#xff0c;其设计的技术融合策略在实现公共厕所泛在感知、互通互联、协同构筑智慧城市等方面起到了关键作用。本文将以智慧公厕源头实力厂家广州中期科技有限公司&#xff0c;大量精品案例现场实景实图实例&#xff0c;从物…

【MybatisPlus-updateById】| 更新字段失效 | 很难受的一个BUG

目录 一. &#x1f981; 写在前面二. &#x1f981; 探索过程三. &#x1f981; 原理解释四. &#x1f981; 最后 一. &#x1f981; 写在前面 如题所言&#xff0c;很难受&#xff01;&#xff01;&#xff01; 原因是 &#x1f981; 在写项目的时候&#xff0c;使用 Mybatis…

Docker大全

Docker大全 Docker安装准备工作开启虚拟机系统卸载Docker在线安装Docker离线安装Docker Docker服务基本操作启动docker服务查看docker状态设置docker开机自启禁用docker开机自启重新启动docker服务查看docker信息查看docker info中具体key的信息停止docker服务docker镜像加速 D…

高效沟通:总裁口才提升之道

高效沟通&#xff1a;总裁口才提升之道 在当今这个信息爆炸的时代&#xff0c;沟通已经成为了企业与个人之间不可或缺的一部分。而对于企业的总裁来说&#xff0c;良好的口才更是其领导力的体现和成功的保障。因此&#xff0c;如何提升总裁的口才&#xff0c;实现高效沟通&…

2024运维堡垒机品牌排名看这里!

2024运维堡垒机品牌排名看这里&#xff01; 1、行云管家 2、天磊卫士 3、阿里云 4、华为云 5、安恒 6、JumpServer 7、山石网科 8、齐治 9、启明星辰 10、奇安信 11、迪普科技 12、腾讯云 13、中远麒麟 备注&#xff1a;以上排名不分先后 运维堡垒机定义 运维堡…

Chrome 插件打包发布

插件打包发布 一、打包成 zip 包 最简单方便的一种其实就是打包成 zip 包&#xff0c;通过下载链接进行下载&#xff0c;在包里面通过设置版本号和数据库的版本号对比来提醒用户进行新包的下载。 二、发布到 Chrome 应用商店 1. 注册成为开发者 在发布到 chrome 应用商店之…

Linux系统使用Docker部署MongoDB数据库并实现无公网IP远程访问

文章目录 前言1. 安装Docker2. 使用Docker拉取MongoDB镜像3. 创建并启动MongoDB容器4. 本地连接测试5. 公网远程访问本地MongoDB容器5.1 内网穿透工具安装5.2 创建远程连接公网地址5.3 使用固定TCP地址远程访问 前言 本文主要介绍如何在Linux Ubuntu系统使用Docker快速部署Mon…

【Java程序设计】【C00387】基于(JavaWeb)Springboot的校园食堂订餐系统(有论文)

基于&#xff08;JavaWeb&#xff09;Springboot的校园食堂订餐系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过…

企业如何申请邓白氏编码(DUNS)呢?

尤其是食品企业&#xff0c;药品企业在申请美国FDA认证的时候&#xff0c;经常会听到一个名词——“邓白氏编码”&#xff0c;申请邓白氏编码是企业顺利完成FDA注册认证的必要前提&#xff0c;因此都需要提供邓白氏编码。 今天&#xff0c;小编就来为大家详细介绍下邓白氏编码…

前端大文件分片上传

1.分片上传整体流程 开始上传&#xff1a;前端启动文件分片上传。后端返回唯一标识。分片上传&#xff1a;获取到上传的文件&#xff0c;然后设置一个固定的分片大小&#xff0c;将文件切成多个小片&#xff0c;计算出每一个分片的MD5值&#xff08;32位&#xff09;。将每个分…

证书(公钥):网络安全的关键

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

【算法刷题】链表笔试题解析(1)

一、链表分割 题目描述&#xff1a; 链接&#xff1a;链表分割 题目分析&#xff1a; 这题直接处理并不好做&#xff0c;我们可以构建前后两个链表&#xff0c;将小于x值的结点放在链表a内&#xff0c;将其它结点放在链表b内&#xff0c;这样将原链表遍历完后&#xff0c;原链…

OSCP靶场--image

OSCP靶场–image 考点(CVE-2023-34152 suid strace提权) 1.nmap扫描 ## ┌──(root㉿kali)-[~/Desktop] └─# nmap -Pn -sC -sV 192.168.178.178 --min-rate 2500 Starting Nmap 7.92 ( https://nmap.org ) at 2024-03-27 23:43 EDT Nmap scan report for 192.168.178.17…

手机照片恢复:两种方法轻松找回您的珍贵照片!

我们的日常生活中&#xff0c;苹果手机已经成为了记录珍贵时刻的得力工具&#xff0c;而其中最重要的要数照片了。然而&#xff0c;有时候不可避免地会出现误删照片的情况&#xff0c;可能是因为手误、设备故障或其他原因。 当您发现重要的照片不见了&#xff0c;往往会感到焦…