二叉搜索树 --- C++实现

目录

1.二叉搜索树的概念

2.二叉搜索树的操作

3. 二叉树的实现

4.二叉搜索树的应用

5. 二叉树的性能分析

6. 二叉树进阶练习题


1.二叉搜索树的概念

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

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

二叉搜索树(BST,Binary Search Tree),也称二叉排序树或二叉查找树

2.二叉搜索树的操作

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

1. 二叉搜索树的查找

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

2. 二叉搜索树的插入

    插入的具体过程如下:

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

3. 二叉搜索树的删除

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

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

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

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

3. 二叉树的实现

代码中有每个操作都有两种写法,一种是非递归写法,一种是递归写法。 

namespace key
{
	template<class K>
	struct BSTreeNode
	{
		BSTreeNode<K>* left;
		BSTreeNode<K>* right;
		K _key;

		BSTreeNode(const K& key = K())
			:left(nullptr)
			, right(nullptr)
			, _key(key)
		{
		}

	};

	template<class K>
	class BSTree
	{
		typedef BSTreeNode<K> Node;
	public:

		BSTree()
			:_root(nullptr)
		{
		}

		//C++11 强制生成默认构造
		//BSTree() = default; 

		BSTree(const BSTree<K>& root)
		{
			_root = Copy(root._root);
		}

		BSTree<K>& operator=(BSTree<K> tree)
		{
			swap(tree._root, _root);
			return *this;
		}


		bool Insert(const K& key)//插入
		{
			if (_root == nullptr)
			{
				_root = new Node(key);
				return true;
			}
			else
			{
				Node* cur = _root;
				Node* parent = nullptr;
				while (cur)
				{
					parent = cur;
					if (cur->_key > key)
					{
						cur = cur->left;
					}
					else if (cur->_key < key)
					{
						cur = cur->right;
					}
					else
					{
						return false;
					}
				}
				cur = new Node(key);
				if (cur->_key < parent->_key)
				{
					parent->left = cur;
				}
				else
				{
					parent->right = cur;
				}
				return true;
			}
		}

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

		bool Erase(const K& key)//删除
		{
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				if (cur->_key > key)
				{
					parent = cur;
					cur = cur->left;
				}
				else if (cur->_key < key)
				{
					parent = cur;
					cur = cur->right;
				}
				else//找到删除
				{
					if (cur->left == nullptr)
					{
						//左为空
						if (parent == nullptr)//根节点
						{
							_root = cur->right;
						}
						else
						{
							if (cur == parent->left)
							{
								parent->left = cur->right;
							}
							else if (cur == parent->right)
							{
								parent->right = cur->right;
							}
							delete cur;
						}
					}
					else if (cur->right == nullptr)
					{
						//右为空
						if (parent == nullptr)//根节点
						{
							_root = cur->left;
						}
						else
						{
							if (cur == parent->left)
							{
								parent->right = cur->left;
							}
							else if (cur == parent->right)
							{
								parent->right = cur->left;
							}
							delete cur;
						}
					}
					else
					{//左右都不为空
						//右树的最小节点(最左节点)
						Node* subLeft = cur->right;
						Node* parent = cur;
						while (subLeft->left)
						{
							parent = subLeft;
							subLeft = subLeft->left;
						}
						if (subLeft == cur->right)//右树的最左节点是根,最左节点不是父节点的左孩子
						{
							swap(subLeft->_key, cur->_key);
							parent->right = subLeft->right;
						}
						else
						{
							swap(subLeft->_key, cur->_key);
							parent->left = subLeft->right;
						}
						delete subLeft;
					}
					return true;
				}

			}
			return false;
		}


		//递归遍历
		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
		//递归查找

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

		//递归插入
		bool InsertR(const K& key)
		{
			return _InsertR(_root, key);
		}
		//递归删除
		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}

		~BSTree()
		{
			Destroy(_root);
		}

	private:
		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;
		}

		void Destroy(Node*& root)//auto不能做函数参数
		{
			if (root == nullptr)
			{
				return;
			}
			Destroy(root->left);
			Destroy(root->right);
			delete root;
			root = nullptr;
		}


		bool _EraseR(Node*& root, const K& key)
		{
			if (root == nullptr)
			{
				return false;
			}
			if (root->_key > key)
			{
				return _EraseR(root->left, key);
			}
			if (root->_key < key)
			{
				return _EraseR(root->right, key);
			}
			else
			{
				//删除,用引用十分巧妙
				if (root->right == nullptr)
				{//引用的是父节点的一个指针
					Node* del = root;
					root = root->left;
					delete del;
					return true;
				}
				else if (root->left == nullptr)
				{
					Node* del = root;
					root = root->right;
					delete del;
					return true;
				}
				else
				{
					Node* subLeft = root->right;

					while (subLeft->left)
					{
						subLeft = subLeft->left;
					}
					swap(subLeft->_key, root->_key);
					//让子树递归删除 
					return _EraseR(root->right, key);

				}
			}
		}

		bool _InsertR(Node*& root, const K& key)
		{
			//这里root用引用
			if (root == nullptr)
			{
				root = new Node(key);
				return true;
			}
			if (root->_key < key)
			{//引用的是父节点的右孩子
				_InsertR(root->right, key);
			}
			if (root->_key > key)
			{
				_InsertR(root->left, key);
			}
			else
			{
				return false;
			}
		}
		//bool _InsertR(Node* root, const K& key)
		//{
		//	if (root == nullptr)
		//	{
		//		_root = new Node(key);
		//		return true;
		//	}
		//	if (key == root->_key)
		//	{
		//		return false;
		//	}
		//	if (key < root->_key)
		//	{
		//		if (root->left == nullptr)
		//		{
		//			root->left = new Node(key);
		//			return true;
		//		}
		//		else
		//		{
		//			return _InsertR(root->left, key);
		//		}
		//	}
		//	if (key > root->_key)
		//	{
		//		if (root->right == nullptr)
		//		{
		//			root->right = new Node(key);
		//			return true;
		//		}
		//		else
		//		{
		//			return _InsertR(root->right, key);
		//		}
		//	}
		//}

		bool _FindR(Node* root, const K& key)
		{
			if (root == nullptr)
			{
				return false;
			}
			else if (root->_key == key)
			{
				return true;
			}
			else if (root->_key < key)
			{
				return _FindR(root->right, key);
			}
			else
			{
				return _FindR(root->left, key);
			}
		}

		void _InOrder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			_InOrder(root->left);
			cout << root->_key << " ";
			_InOrder(root->right);
		}

	private:
		Node* _root = nullptr;
	};
}

4.二叉搜索树的应用

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

比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:

  1. 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
  2. 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:

  1. 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
  2. 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对
     
//改造后的 key_value 结构
namespace key_value
{
	template<class K, class V>
	struct BSTreeNode
	{
		BSTreeNode<K,V>* left;
		BSTreeNode<K,V>* right;
		K _key;
		V _value;

		BSTreeNode(const K& key = K(),const V& value = V())
			: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;
			}
			else
			{
				Node* cur = _root;
				Node* parent = nullptr;
				while (cur)
				{
					parent = cur;
					if (cur->_key > key)
					{
						cur = cur->left;
					}
					else if (cur->_key < key)
					{
						cur = cur->right;
					}
					else
					{
						return false;
					}
				}
				cur = new Node(key,value);
				if (cur->_key < parent->_key)
				{
					parent->left = cur;
				}
				else
				{
					parent->right = cur;
				}
				return true;
			}
		}

		Node* Find(const K& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (cur->_key == key)
				{
					return cur;
				}
				else if (cur->_key < key)
				{
					cur = cur->right;
				}
				else
				{
					cur = cur->left;
				}
			}
			return nullptr;
		}

		bool Erase(const K& key)
		{
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				if (cur->_key > key)
				{
					parent = cur;
					cur = cur->left;
				}
				else if (cur->_key < key)
				{
					parent = cur;
					cur = cur->right;
				}
				else//找到删除
				{
					if (cur->left == nullptr)
					{
						//左为空
						if (parent == nullptr)//根节点
						{
							_root = cur->right;
						}
						else
						{
							if (cur == parent->left)
							{
								parent->left = cur->right;
							}
							else if (cur == parent->right)
							{
								parent->right = cur->right;
							}
							delete cur;
						}
					}
					else if (cur->right == nullptr)
					{
						//右为空
						if (parent == nullptr)//根节点
						{
							_root = cur->left;
						}
						else
						{
							if (cur == parent->left)
							{
								parent->right = cur->left;
							}
							else if (cur == parent->right)
							{
								parent->right = cur->left;
							}
							delete cur;
						}
					}
					else
					{//左右都不为空
						//右树的最小节点(最左节点)
						Node* subLeft = cur->right;
						Node* parent = cur;
						while (subLeft->left)
						{
							parent = subLeft;
							subLeft = subLeft->left;
						}
						if (subLeft == cur->right)//右树的最左节点是根,最左节点不是父节点的左孩子
						{
							swap(subLeft->_key, cur->_key);
							parent->right = subLeft->right;
						}
						else
						{
							swap(subLeft->_key, cur->_key);
							parent->left = subLeft->right;
						}
						delete subLeft;
					}
					return true;
				}

			}
			return false;
		}


		//递归遍历
		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. 二叉树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其时间复杂度为:O(logN)

最差情况下,二叉搜索树退化为单支树(或者类似单支),其时间复杂度为:O(N)

问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?那么我们后续文章学习的AVL树和红黑树就可以上场了。

6. 二叉树进阶练习题

这些题目更适合使用C++完成,难度也更大一些

  1. 二叉树创建字符串。OJ链接
  2. 二叉树的分层遍历1。OJ链接
  3. 二叉树的分层遍历2。OJ链接
  4. 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先 。OJ链接
  5. 二叉树搜索树转换成排序双向链表。OJ链接
  6. 根据一棵树的前序遍历与中序遍历构造二叉树。OJ链接
  7. 根据一棵树的中序遍历与后序遍历构造二叉树。OJ链接
  8. 二叉树的前序遍历,非递归迭代实现 。OJ链接
  9. 二叉树中序遍历 ,非递归迭代实现。OJ链接
  10. 二叉树的后序遍历 ,非递归迭代实现。OJ链接

本篇详细代码可查看我的Gitee

本篇结束!

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

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

相关文章

LDO频率补偿

频率补偿 为了维持系统稳定的条件&#xff0c;一般的做法是建立一个低频几点&#xff0c;并把第二个极点放在单位增益频率 f0db 附近。在线性稳压器中&#xff0c;这两个极点是输出极点Po和误差放大器极点Pe。在确定了哪一个极点应该是主极点后&#xff0c;补偿的目的就是理解系…

(Mac上)使用Python进行matplotlib 画图时,中文显示不出来

【问题描述】 ①报错确缺失字体&#xff1a; ②使用matplotlib画图&#xff0c;中文字体显示不出来 【问题思考】 在网上搜了好多&#xff0c;关于使用python进行matplotlib画图字体显示不出来的&#xff0c;但是我试用了下&#xff0c;对我来说都没有。有些仅使用于windows系…

Laravel框架使用phpstudy本地安装的composer用Laravel 安装器进行安装搭建

一、首先需要安装Laravel 安装器 composer global require laravel/installer 二、安装器安装好后&#xff0c;可以使用如下命令创建项目 laravel new sys 三、本地运行 php artisan serve 四、 使用Composer快速安装Laravel5.8框架 安装指定版本的最新版本&#xff08;推荐&a…

2023 年第四季度 Chainlink 产品更新

在回顾 2023 年时&#xff0c;可以明显看到 Chainlink 生态系统所取得的进步是非常显著的。 我们以三个优先事项开始了这一年&#xff1a; 推出了 CCIP&#xff08;我们的跨链互操作协议&#xff09;&#xff0c;使得跨链交易和活动更加安全。推出数据流&#xff08;Data Str…

管理 Jenkins 详细指南

目录 系统配置 安全 状态信息 故障 排除 工具和操作 系统配置 系统&#xff0c;配置全局设置和路径&#xff0c;端口更改&#xff0c;下载地址等。 工具&#xff0c;配置工具、其位置和自动安装程序。 插件&#xff0c;添加、删除、禁用或启用可以扩展 Jenkins 功能的插…

<JavaEE> 网络编程 -- 网络编程和 Socket 套接字

目录 一、网络编程的概念 1&#xff09;什么是网络编程&#xff1f; 2&#xff09;网络编程中的基本概念 1> 收发端 2> 请求和响应 3> 客户端和服务端 二、Socket套接字 1&#xff09;什么是“套接字”&#xff1f; 2&#xff09;Socket套接字的概念 3&…

跨平台应用程序开发软件,携RAD Studio 12新版上线

RAD Studio 是一款专为程序员而准备的跨平台应用程序开发软件&#xff0c;内置Delphi和CBuilder这两种开发工具&#xff0c;另外还提供了新的C功能&#xff0c;扩展了对ExtJS的RAD服务器支持&#xff0c;增强了对vcL的高dpi支持&#xff0c;提高了firemonk (FMX)的质量等等&…

长知识,Session强制账号下线,限制账号登录!

在Web开发中&#xff0c;会话管理是确保用户连续、安全地访问应用程序的关键。PHP中的会话机制&#xff08;session&#xff09;为我们提供了这种功能。通过会话&#xff0c;我们可以跟踪用户的状态&#xff0c;存储和检索用户特定的数据。然而&#xff0c;有时候我们需要强制用…

Python学习路线 - Python语言基础入门 - Python异常、模块与包

Python学习路线 - Python语言基础入门 - Python异常、模块与包 了解异常什么是异常bug单词的诞生异常演示 异常的捕获方法为什么要捕获异常捕获常规异常捕获指定异常捕获多个异常捕获异常并输出描述信息捕获所有异常异常 else异常的finally 异常的传递Python模块什么是模块模块…

C++ Qt开发:Charts折线图绘制详解

Qt 是一个跨平台C图形界面开发库&#xff0c;利用Qt可以快速开发跨平台窗体应用程序&#xff0c;在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置&#xff0c;实现图形化开发极大的方便了开发效率&#xff0c;本章将重点介绍QCharts折线图的常用方法及灵活运用。 折线图…

.raw 是一个 Anndata 包中的对象,用于存储原始的单细胞数据。scanpy种如何查看 .raw 对象的内容,

1查看 .raw 对象的内容&#xff0c;可以使用以下方法&#xff1a; .raw 是一个 Anndata 包中的对象&#xff0c;用于存储原始的单细胞数据。 使用 .X 属性查看原始数据矩阵&#xff1a;.raw.X 这将返回一个 Numpy 数组&#xff0c;其中包含原始数据的数值。 使用 .var_names 属…

LZW算法

LZW算法是一种流行且高效的无损数据压缩算法。它的名称来自于它的发明者Lempel、Ziv和Welch。LZW算法通过利用重复出现的字串来实现数据的可靠压缩&#xff0c;同时保证压缩后的数据能够精确还原为原始数据。 LZW算法的基本原理很简单&#xff0c;在压缩过程中&#xff0c;它维…

Python算法例24 落单的数Ⅱ

1. 问题描述 给出3n1个非负整数元素的数组&#xff0c;除其中一个数字之外&#xff0c;其他每个数字均出现三次&#xff0c;找到这个数字。 2. 问题示例 给出[1&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;3&#xff0c;3&#xff0c;2&#xff0c;2&#xff0c;4&…

2023年都找不到工作,软件测试已经崩了?

最近后台很多粉丝给我留言&#xff1a; 2023年软件测试已经崩盘了吗&#xff0c;为什么都找不到工作了&#xff1f; 确实&#xff0c;今年经济大环境不好&#xff0c;企业也都在降本增效&#xff0c;如果技术能力还在被应届生竞争岗位的阶段&#xff0c;只会越来越难。 找不…

网站怎么才能做好SEO?网站SEO指引!!

在当今互联网的激烈竞争中&#xff0c;SEO&#xff08;搜索引擎优化&#xff09;已成为提升网站流量和吸引更多用户的关键手段。为了帮助您更好地掌握SEO网站优化技巧&#xff0c;本文将深入探讨以下几个方面&#xff1a; 一、关键词策略 关键词策略是SEO优化的基石。正确选择…

fba海派和传统海运的区别,亚马逊 FBA货物包装技巧—站斧浏览器

fba海派和传统海运的区别 1、美国FBA海派是什么&#xff1f; 美国FBA海派即将商品通过海洋运输的方式运送到美国亚马逊FBA仓库的服务。这种方式主要适用于大批量或大件商品&#xff0c;因为相比其他物流方式&#xff0c;海派具备成本低和运载量大的优势。 2、传统海运是什么…

Vue项目如何打包

1. 确保你已经在项目根目录下安装了Vue CLI。如果没有安装&#xff0c;可以通过以下命令进行安装&#xff1a; npm install -g vue/cli 2. 在项目根目录下打开终端或命令行工具&#xff0c;运行以下命令来创建一个生产环境的打包文件&#xff1a; npm run build 这个命令会执…

汽车服务品牌网站建设的作用是什么

汽车服务涵盖多个层面&#xff0c;在保修维护这一块更是精准到了车内车外&#xff0c;无论是品牌商还是市场中各维修部&#xff0c;都能给到车辆很好的维修养护服务。如今车辆的人均拥有量已经非常高&#xff0c;也因此市场中围绕汽车相关的从业者也比较多。 首先就是拓客引流…

C语言中二维数组的存储和二进制数在底层的排列顺序

1 二维数组变量的存储 二维数组在内存中是按照先行后列的顺序存储的&#xff0c;即先存储第一行的所有元素&#xff0c;再存储第二行的所有元素&#xff0c;以此类推。每个元素在内存中占据一定的字节数&#xff0c;这个字节数由该元素的类型决定。例如&#xff0c;int类型的元…

C语言学习NO.9-指针(一)内存和地址,指针变量,指针变类型的意义,const修饰指针,指针运算,野指针,assret断言,指针的使用和传址调用

指针是什么&#xff1f; 指针理解的2个要点&#xff1a; 1.指针是内存中一个最小单元的编号&#xff0c;也就是地址&#xff1b; 2.平时口语中说的指针&#xff0c;通常指的是指针变量&#xff0c;是用来存放内存地址的变量。 总结&#xff1a;指针就是地址&#xff08;变量的地…