[C++][数据结构][二叉搜索树]详细讲解

news2025/2/24 22:00:59

目录

  • 1.概念
  • 2.二叉搜索树操作
    • 1.查找
    • 2.插入
    • 3.删除
  • 3.二叉搜索树的实现
  • 4.二叉搜索树的应用
    • 1.K模型
    • 2.KV模型
  • 5.二叉搜索树的性能分析


1.概念

  • 二叉搜索树又称二叉排序树,具有以下性质
    • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
    • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
    • 它的左右子树也分别为二叉搜索树
      请添加图片描述

2.二叉搜索树操作

1.查找

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

2.插入

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

3.删除

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

    • 要删除的节点无孩子节点
    • 要删除的节点只有左孩子节点
    • 要删除的节点只有右孩子节点
    • 要删除的节点有左、右孩子节点
  • 看起来看起来有待删除节点有4中情况,实际ab/bc可以合并起来,因此真正的删除过程如下:

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

3.二叉搜索树的实现

// Key模型
namespace Key
{
	template <class K>
	struct BSTreeNode // 为了能在类外访问,用struct
	{
		BSTreeNode<K>* _left;
		BSTreeNode<K>* _right;
		K _key;

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

	template <class K>
	class BSTree
	{
		typedef BSTreeNode<K> Node;
	public:
		//BSTree()
		//{}

		// C++11的用法:强制编译器生成默认的构造函数
		BSTree() = default;

		BSTree(const BSTree<K>& t)
		{
			_root = _Copy(t._root);
			return *this;
		}

		~BSTree()
		{
			_Destroy(_root);
		}

		BSTree<K> operator=(BSTree<K> t)
		{
			std::swap(_root, t._root);
			return *this;
		}

		// 二叉搜索树的非递归玩法
		bool Insert(const K& key)
		{
			if (_root == nullptr)
			{
				_root = new Node(key);
				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为空,则找到了插入位置
			cur = new Node(key);
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}

			return true;
		}

		bool Find(const K& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (cur->_key < key)
				{
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					cur = cur->_left;
				}
				else
				{
					return true;
				}
			}

			return false;
		}

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

						delete cur;
						cur = nullptr;
					}
					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* minParent = cur;
						Node* min = cur->_right;

						while (min->_left)
						{
							minParent = min;
							min = min->_left;
						}

						std::swap(min->_key, cur->_key);
						if (min == minParent->_left)
						{
							minParent->_left = min->_right; // 正常找,min肯定为左孩子
						}
						else
						{
							minParent->_right = min->_right; // min就是cur的右孩子
						}

						delete min;
						min = nullptr;
					} 

					return true;
				} // end of delete
			} // end of find key

			return false;
		}

		void InOrder()
		{
			_InOrder(_root);
			std::cout << std::endl;
		}
///
		// 二叉搜索树的递归玩法
		bool InsertR(const K& key)
		{
			return _InsertR(_root, key);
		}

		bool FindR(const K& key)
		{
			return _FindR(_root, key);
		}

		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}

	private:
		Node* _Copy(Node* root)
		{
			if (root == nullptr)
			{
				return nullptr;
			}

			Node* copyRoot = new Node(root->_key);
			copyRoot->_left = _Copy(root->_left);
			copyRoot->_right = _Copy(root->_right);

			return copyRoot;
		}

		void _Destroy(Node*& root)
		{
			if (root == nullptr)
			{
				return;
			}

			_Destroy(root->_left);
			_Destroy(root->_right);
			delete root;
			root = nullptr; // 传引用,置空有效防止野指针
		}

		void _InOrder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}

			_InOrder(root->_left);
			std::cout << root->_key << " ";
			_InOrder(root->_right);
		}

		bool _InsertR(Node*& root, const K& key) // 这里Node*&神之一手,传引用,可以修改root
		{
			if (root == nullptr)
			{
				root = new Node(key);
				return true;
			}

			if (root->_key < key)
			{
				return _InsertR(root->_right, key); // 传引用,可以修改下层root,且链接关系不断
			}
			else if (root->_key > key)
			{
				return _InsertR(root->_left, key);
			}
			else
			{
				return false;
			}
		}

		bool _FindR(Node* root, const K& key)
		{
			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;;
			}
		}

		bool _EraseR(Node*& root, const K& key)
		{
			if (root == nullptr)
			{
				return false;
			}

			if (root->_key < key)
			{
				return _EraseR(root->_right, key);
			}
			else if (root->_key > key)
			{
				return _EraseR(root->_left, key);
			}
			else
			{
				// 删除
				Node* del = root;
				if (root->_left == nullptr) // 左为空,只有右孩子
				{
					root = root->_right; // root就是上一层root->_right/_left的引用,修改root就是修改上一层的链接关系
				}
				else if (root->_right == nullptr) // 右为空,只有左孩子
				{
					root = root->_left;
				}
				else // 两个都不为空,有两个孩子
				{
					// 替换法删除,找右子树最小节点进行替换
					Node* min = root->_right;
					while (min->_left)
					{
						min = min->_left;
					}

					std::swap(min->_key, root->_key);
					return _EraseR(root->_right, key); // 将问题再此划归到只有一个孩子的/没孩子的情况
				}

				delete del;
				del = nullptr;
				return true;
			}
		}

	private:
		Node* _root = nullptr;
	};
}

4.二叉搜索树的应用

1.K模型

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

2.KV模型

  • 每一个关键码key,都有与之对应的值Value,即<Key, Value>键值对

  • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文就构成一种键值对

  • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是就构成一种键值对

    // KeyValue模型
    namespace KeyValue
    {
    	template <class K, class V>
    	struct BSTreeNode
    	{
    		BSTreeNode<K, V>* _left;
    		BSTreeNode<K, V>* _right;
    		K _key;
    		V _value;
    
    		BSTreeNode(const K& key, const V& value)
    			:_left(nullptr)
    			, _right(nullptr)
    			, _key(key)
    			, _value(value)
    	};
    
    	template<class K, class V>
    	class BSTree
    	{
    		typedef BSTreeNode<K, V> Node;
    	public:
    		bool Insert(const K& key, const V& value)
    		{
    			if (_root == nullptr)
    			{
    				_root = new Node(key, value);
    				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, value);
    			if (parent->_key < key)
    			{
    				parent->_right = cur;
    			}
    			else
    			{
    				parent->_left = cur;
    			}
    
    			return true;
    		}
    
    		Node* Find(const K& key)
    		{
    			Node* cur = _root;
    			while (cur)
    			{
    				if (cur->_key < key)
    				{
    					cur = cur->_right;
    				}
    				else if (cur->_key > key)
    				{
    					cur = cur->_left;
    				}
    				else
    				{
    					return cur;
    				}
    			}
    
    			return nullptr;
    		}
    
    		bool Erase(const K& key)
    		{
    			//...
    
    			return true;
    		}
    
    		void InOrder()
    		{
    			_InOrder(_root);
    			cout << endl;
    		}
    	private:
    
    		void _InOrder(Node* root)
    		{
    			if (root == nullptr)
    			{
    				return;
    			}
    
    			_InOrder(root->_left);
    			cout << root->_key << ":" << root->_value << endl;
    			_InOrder(root->_right);
    		}
    	private:
    		Node* _root = nullptr;
    	};
    }
    

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

  • 插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能
  • 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为logN
  • 最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为N
    请添加图片描述

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

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

相关文章

深入剖析云原生服务与微服务框架中服务发现机制的核心原理与实现细节

深入剖析云原生服务与微服务框架中服务发现机制的核心原理与实现细节 本文介绍单体架构微服务架构微服务的方法建议微服务的通信机制P2P的调用通信API网关统一门面 微服务服务发现DNS服务发现基于一致性原则的key-value数据库服务发现与负载均衡紧密相关 微服务发展到容器化云原…

如何警用root用户登录ssh

使用tail指令&#xff0c;可以动态查看日志信息。 &#xff08;tail -f /var/log/secure或messages&#xff09; 使用>符号&#xff0c;可以清空日志内容&#xff0c;不删除文件本身。 禁用root用户为以下步骤&#xff1a; 首先使用useradd创建用户&#xff08;可以修改为其…

PyTorch C++扩展用于AMD GPU

PyTorch C Extension on AMD GPU — ROCm Blogs 本文演示了如何使用PyTorch C扩展&#xff0c;并通过示例讨论了它相对于常规PyTorch模块的优势。实验在AMD GPU和ROCm 5.7.0软件上进行。有关支持的GPU和操作系统的更多信息&#xff0c;请参阅系统要求&#xff08;Linux&#xf…

Vue页面内容未保存时离开页面做弹框提示

一、背景 目标&#xff1a;如果当前页面中有正在编辑中的内容&#xff0c;那么此时切换组件、跳转路由、关闭标签页、刷新页面&#xff0c;都会有离开确认提示&#xff0c;防止用户因误触导致填写的内容白费。 后台管理系统中有许多需要填写的表单&#xff0c;弹窗方式的表单一…

Docker安装Nginx(各种错误版)

Docker安装-CSDN博客 安装启动Docker之后 docker run -d -p 81:81 --name nginx nginx 这样没有指定版本 docker run&#xff1a;启动一个新的容器。-d&#xff1a;以分离模式运行容器&#xff08;后台运行&#xff09;。-p 81:81&#xff1a;将主机的 81 端口映射到容器的 …

“论面向对象的建模及应用”必过范文,突击2024软考高项论文

论文真题 软件系统建模是软件开发中的重要环节&#xff0c;通过构建软件系统模型可以帮助系统开发人员理解系统&#xff0c;抽取业务过程和管理系统的复杂性&#xff0c;也可以方便各类人员之间的交流。软件系统建模是在系统需求分析和系统实现之间架起的一座桥梁&#xff0c;…

傲星一个在线工具箱源码附搭建教程

傲星工具箱源码是一款功能强大的在线工具箱程序&#xff0c;您可以通过安装扩展来增强其功能。同时&#xff0c;该程序还提供了插件模板的功能&#xff0c;让您可以将其作为网页导航使用。 1.PHP版本需不低于7.2.5。 2.Mysql版本需不低于5.7。 3.需要安装fileinfo扩展。 4.…

第〇篇:深入Docker的世界系列博客介绍

欢迎来到“深入Docker的世界”系列博客&#xff0c;这是一次旨在全面探索Docker容器化技术的冒险之旅。从基础原理到高级应用&#xff0c;再到实践案例分析&#xff0c;我们将深入挖掘Docker的每一个角落&#xff0c;帮助你不仅掌握这项技术的实用知识&#xff0c;还能了解其在…

从0到1:手动测试迈向自动化——手机web应用的自动化测试工具

引言&#xff1a; 在当今移动互联网时代&#xff0c;手机web应用已经成为人们生活中不可或缺的一部分。为了保证手机web应用的质量和稳定性&#xff0c;自动化测试工具变得十分重要。本文将介绍手机web应用自动化测试工具的选择和使用&#xff0c;提供一份超详细且规范的指南&a…

2024年6月15日 十二生肖 今日运势

小运播报&#xff1a;2024年6月15日&#xff0c;星期六&#xff0c;农历五月初十 &#xff08;甲辰年庚午月庚戌日&#xff09;&#xff0c;法定节假日。 红榜生肖&#xff1a;兔、马、虎 需要注意&#xff1a;牛、鸡、龙 喜神方位&#xff1a;西北方 财神方位&#xff1a;…

【国赛赛题详解】2024年数学建模国赛ABCDEF题(点个关注,后续会更新)

您的点赞收藏是我继续更新的最大动力&#xff01; 一定要点击如下的蓝色字体链接&#xff0c;那是获取资料的入口! 点击链接加入群聊【2024国赛资料合集】&#xff1a;http://qm.qq.com/cgi-bin/qm/qr?_wv1027&keQt5WRIvc5-fogZRrrahAhbqDa2nKfW8&authKey%2BqQfThTx…

博瓦科技产品亮相湖北安博会啦!!!

6月12日&#xff0c;第二十三届2024中国&#xff08;武汉&#xff09;社会公共安全产品暨数字城市产业展览会&#xff08;简称&#xff1a;湖北安博会&#xff09;在武汉国际会展中心隆重开幕。作为行业内最具影响力的展会之一&#xff0c;此次盛会将汇聚来自全球的顶尖企业、专…

《TCP/IP网络编程》(第十五章)套接字和标准I/O

之前数据通信时&#xff0c;使用的是read&write函数以及其他各种I/O函数&#xff0c;本章将使用标准I/O函数&#xff0c;例如C语言的fopen、fgetc、fputs等等&#xff1b;C语言的cout、cin等等 1.使用标准I/O函数的优点 ①跨平台兼容性&#xff1a; 标准I/O函数通常是跨平…

简单了解CPU的工作原理

目录 一、基本结构以及对应功能 &#xff08;1&#xff09;基本结构 &#xff08;2&#xff09;几个重要寄存器的详细介绍 操作码 (Opcode) 操作数 (Operands) 指令表 (Instruction Table) 第一个&#xff1a;程序计数器 (PC) 第二个&#xff1a;指令寄存器 (IR&#x…

STM32学习笔记(三)--EXTI外部中断详解

&#xff08;1&#xff09;配置步骤1.配置RCC 打开外设时钟2.配置GPIO 选择端口输入模式3.配置AFIO 选择要用的一路GPIO 连接至EXTI 4.配置EXTI 选择边沿触发方式 上升沿 下降沿 双边沿 选择触发响应方式 中断响应 事件响应 5.配置NVIC 选择一个合适的优先…

Uni-App中的u-datetime-picker时间选择器Demo

目录 前言Demo 前言 对于网页端的推荐阅读&#xff1a;【ElementUI】详细分析DatePicker 日期选择器 事情起因是两个时间选择器同步了&#xff0c;本身是从后端慢慢步入全栈&#xff0c;对此将这个知识点从实战进行提炼 通过Demo进行总结 Demo 用于选择日期和时间的组件&a…

宝藏速成秘籍(3)选择排序法

一、前言 1.1、概念 选择排序法&#xff08;Selection Sort&#xff09;是一种简单直观的排序算法。它的基本思想是&#xff1a;每次从待排序的数组中选择最小&#xff08;或最大&#xff09;的元素&#xff0c;将其放在已排序部分的末尾&#xff0c;直到所有元素都排序完毕。…

外网访问公司内网服务器?

【天联】组网天联可以解决不同地区电脑与电脑、设备与设备、电脑与设备之间的信息远程通信问题。在全国各主要节点部署加速服务器&#xff0c;实现在低带宽、跨运营商的网络环境下高速访问&#xff1b;这为公司内网服务器提供了一个可行的外网访问解决方案。 在现代办公环境中…

智能驾驶新高度:比亚迪无图城市领航夜闯城中村

在各种创新科技日新月异的今天&#xff0c;智能驾驶技术也给我们带来了越来越多的惊喜。 近日&#xff0c;比亚迪旗下的高端品牌腾势&#xff0c;凭借其全新车型腾势N7&#xff0c;在智能驾驶领域展现出了令人瞩目的实力。 在一场别开生面的“无图城市领航”实测中&#xff0c;…

Unet心电信号分割方法(Pytorch)

心血管疾病是一种常见病&#xff0c;严重影响人们的健康及日常生活。 近年来随着人们生活习惯的不断变化&#xff0c;心血管疾病对人们影响愈加明显&#xff0c;发病率呈现出逐年攀升的趋势&#xff0c;心血管疾病是中国城乡居民死亡的首要原因。心电图ECG已被广泛用于研究心跳…