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

news2025/1/20 1:46:21

目录

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

本篇结束!

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

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

相关文章

双非本科求职_面试经验_上集

前言 后知后觉开始找实习&#xff0c;也马上进入秋招&#xff0c;写此文一方面是记录一下我的面试经历作为复盘&#xff0c;另一方面也给同样在广州&#xff0c;双非二本的同志一个参考&#xff08;快来看菜鸟&#xff09;&#xff0c;从而获得一定的动力和自信&#xff0c;毕…

【水文专业词汇】气象水文、水利工程等

水文专业词汇&#xff1a;气象水文、水利工程等 气象水文类水循环过程地区分类 水利工程类跨流域调水工程 参考 气象水文类 水循环过程 中文英文降水/降雨precipitation/rainfall径流runoff/streamflow产汇流runoff generation 地区分类 中文英文雨养作物区rain-fed agricu…

多模态大模型:关于Better Captions那些事儿

Overview 一、ShareGPT4V1.1、Motivation1.2、ShareGPT4V数据集构建1.3、ShareGPT4V-7B模型 一、ShareGPT4V 题目: ShareGPT4V: Improving Large Multi-Modal Models with Better Captions 机构&#xff1a;中科大&#xff0c;上海人工智能实验室 论文: https://arxiv.org/pdf…

少儿编程:从兴趣到升学的关键之路

随着科技的飞速发展&#xff0c;计算机编程已经逐渐渗透到我们生活的方方面面。对于新时代的少儿来说&#xff0c;掌握编程技能不仅可以开拓视野&#xff0c;提高思维能力&#xff0c;还可能成为他们未来升学和就业的重要砝码。6547网将探讨如何将少儿编程从兴趣培养成一种有力…

基于ERC20代币协议实现的去中心化应用平台

文章目录 内容简介设计逻辑ERC20TokenLoanPlatform 合约事件结构体状态变量函数 Remix 运行实现部署相关智能合约存款和取款贷款和还款 源码地址 内容简介 使用 solidity 实现的基于 ERC20 代币协议的借贷款去中心化应用平台(极简版)。实现存款、取款、贷款、还款以及利息计算的…

低代码开发:数字化转型的引擎

引言 在当今数字化时代&#xff0c;组织面临着不断变化的市场需求和技术挑战。数字化转型已成为维持竞争力的关键&#xff0c;而低代码开发正在崭露头角&#xff0c;成为加速创新和数字化转型的有力工具。本文将深入探讨低代码开发的核心概念、优势和应用&#xff0c;以揭示它…

单调栈分类、封装和总结

作者推荐 map|动态规划|单调栈|LeetCode975:奇偶跳 通过枚举最小&#xff08;最大&#xff09;值不重复、不遗漏枚举所有子数组 C算法&#xff1a;美丽塔O(n)解法单调栈左右寻找第一个小于maxHeight[i]的left,right&#xff0c;[left,right]直接的高度都是maxHeight[i] 可以…

【动态规划算法(dp算法)】之背包问题

文章目录 背包问题动规五部曲一、0-1背包问题 &#xff1a;限制物品不可重复 (要么不选 要么选一个)二、完全背包问题&#xff1a;不限制重复&#xff08;要么不选 要么可以多选&#xff09;&#xff08;完全背包可以转化为0-1背包问题&#xff09; 动态规划&#xff1a;01背包…

宝塔面板 -- 创建第一个自己的网站

文章目录 前言 一、安装宝塔面板 二、注册宝塔面板 三、安装nginx 四、第一个hello world运行 五、总结 文章目录 前言一、安装宝塔面板二、注册宝塔面板三、安装nginx四、第一个hello world运行五、总结 前言 阿里云最近对在校大学生免费每人赠送一台服务器&#xff0c…

Apache ShenYu 网关JWT认证绕过漏洞 CVE-2021-37580

Apache ShenYu 网关JWT认证绕过漏洞 CVE-2021-37580 已亲自复现 漏洞名称漏洞描述影响版本 漏洞复现环境搭建漏洞利用 修复建议总结 Apache ShenYu 网关JWT认证绕过漏洞 CVE-2021-37580 已亲自复现) 漏洞名称 漏洞描述 Apache ShenYu是一个异步的&#xff0c;高性能的&#x…

第九周算法题(哈希映射,二分,Floyd算法 (含详细讲解) )

第九周算法题 第一题 题目来源&#xff1a;33. 搜索旋转排序数组 - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a;整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 <…

HA启动Advanced SSH Web Terminal 提示附加组件似乎尚未准备就绪,它可能仍在启动。是否要再试一次?

环境&#xff1a; Home Assistant OS11.1 Advanced SSH & Web Terminal 17.0 问题描述&#xff1a; HA安装好SSH加载项&#xff0c;启动Advanced SSH & Web Terminal 提示附加组件似乎尚未准备就绪&#xff0c;它可能仍在启动。是否要再试一次&#xff1f; 解决方案…

尚硅谷 java 2023(基础语法)笔记

一、变量与运算符 1、HelloWorld的编写和执行 class HelloChina{public static void main(String[] args){System.out.println("hello,world!!你好&#xff0c;中国&#xff01;");} } 总结&#xff1a; 1. Java程序编写和执行的过程&#xff1a; 步骤1&#xff1…

解决 MATLAB 遗传算法中 exitflg=4 的问题

一、优化问题简介 以求解下述优化问题为例&#xff1a; P 1 : min ⁡ p ∑ k 1 K p k s . t . { ∑ k 1 K R k r e q l o g ( 1 α k ∗ p k ) ≤ B b s , ∀ k ∈ K p k ≥ 0 , ∀ k ∈ K \begin{align} {P_1:}&\mathop{\min}_{\bm{p}}{ \sum\limits_{k1}^K p_k } \no…

微软写了份GPT-4V说明书:166页讲解又全又详细demo示例一应俱全

原文&#xff1a;微软写了份GPT-4V说明书&#xff1a;166页讲解又全又详细demo示例一应俱全 - 哔哩哔哩 编者按&#xff1a;这篇文章深入研究了GPT-4V的用法、基本功能&#xff0c;用较大篇幅介绍了GPT-4V在遵循文字说明、视觉指向和视觉参考提示、视觉文本提示等方面展示出的…

Swiper轮播图系列

一、初始化Swiper new Swiper(.swiper-container, {initialSlide: 0,slidesPerView: 3,breakpoints: {750: {slidesPerView: 1},990: {slidesPerView: 2}},spaceBetween: 12,loop: true,speed: 1000,autoplay: {disableOnInteraction: false, // 手动滑动后&#xff0c;不停止…

阿里云 ACK One 新特性:多集群网关,帮您快速构建同城容灾系统

云布道师 近日&#xff0c;阿里云分布式云容器平台 ACK One[1]发布“多集群网关”[2]&#xff08;ACK One Multi-cluster Gateways&#xff09;新特性&#xff0c;这是 ACK One 面向多云、多集群场景提供的云原生网关&#xff0c;用于对多集群南北向流量进行统一管理。 基于 …

【UML】第9篇 类图(概念、作用和抽象类)(1/3)

目录 一、类图的概念 二、类图的主要作用 三、类图的构成 3.1 类的名称 3.2 抽象类&#xff08;Abstract Class&#xff09; 一、类图的概念 类图是UML模型中静态视图。它用来描述系统中的有意义的概念&#xff0c;包括具体的概念、抽象的概念、实现方面的概念等。静态视…

Pytorch项目,肺癌检测项目之四

# 安装图像处理 的两个包 simpleITK 和 ipyvolume # 安装缓存相关的两个包 diskcache 和 cassandra-driver import gzip from diskcache import FanoutCache, Disk from cassandra.cqltypes import BytesType from diskcache import FanoutCache,Disk,core from diskcache…

浏览器原理篇—渲染优化

渲染优化 通常一个页面有三个阶段&#xff1a;加载阶段、交互阶段和关闭阶段 加载阶段&#xff0c;是指从发出请求到渲染出完整页面的过程&#xff0c;影响到这个阶段的主要因素有网络和 JavaScript 脚本。交互阶段&#xff0c;主要是从页面加载完成到用户交互的整合过程&…