C++中二叉搜索树的底层原理及实现

小编在学习完二叉搜索树(SearchBinaryTree)之后觉得虽然二叉搜索树不是很难,但是它对于后面学习C++中的AVL树和红黑树及map和set的封装都有重要的作用,因此小编今天带给大家二叉搜索树的原理及实现,话不多说,开始学习!~~~

一、二叉搜索树的底层原理

1、二叉搜索树的图片(回顾二叉树)

通过上面一张图片大家可能不能得出二叉搜索树的原理,话不多说,直接告诉大家

原理:任意一个节点的左子树的值小于这个节点的值,并且这个节点的右子树的值大于这个节点的值。通俗理解也就是小于这个节点的值放到这个节点的左边,大于这个节点的值放到这个节点的右边即可。

大家通过在C语言数据结构中学习的二叉树的基础,肯定可以明白这个二叉搜索树的结构,因为二叉搜索树和二叉树的树形结构一样,但是稍微不一样的点就是二叉搜索树有自己的原理,不只是简单的储存数据,二叉搜索树储存的数据都是有特定的意义,并且都符合二叉搜索树的原理才可以。

二、二叉搜索树的实现

1、在了解了二叉树的原理内容后,大家先跟小编写一个二叉搜索树的基础结构

	// 搜索二叉树 左边节点小于父节点,右边节点大于父节点
	template<class T>
	struct BSTreeNode
	{
		T _data;
		struct BSTreeNode<T>* _left;
		struct BSTreeNode<T>* _right;

		BSTreeNode(const T& data)
			:_data(data)
			, _left(nullptr)
			, _right(nullptr)
		{}
	};

上面就是一个二叉树的基本结构,相信大家并不陌生,并且每次创建的节点让左右节点的指针都置为空。

2、第二步,大家跟着小编实现一个二叉树的插入功能,在这里,大家应该已经明白了二叉搜索树的原理,左节点的值小于父节点,右节点的值大于根节点,因此大家可以根据插入的值的大小来判断这个这个插入的值应该插入到哪块。接下来,画图给大家介绍一个例子,大家肯定秒懂:

每次插入的值,直到比较到空节点的位置就是该值插入的位置,再明白了这个插入原理之后相信大家应该可以实现一个插入的模块了,小编已经实现完啦,将代码和注释放到下面,供大家参考:

		typedef struct BSTreeNode<T> Node;     // 由于节点的名字太长,所以在这里我重命名一下
		bool Insert(const T& x)
		{
			if (_root == nullptr)
			{
				_root = new Node(x);
				return true;
			}
			Node* parent = nullptr;   // 用 parent 的二叉树的指针记录当前节点的父节点的指针,为插入节点做准备
			Node* cur = _root;        // cur 节点用来记录二叉树根节点的指针
			while (cur)
			{
				if (cur->_data < x)   // 插入的值比当前节点的值大,走到右边
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_data > x) // 插入的值比当前节点的值小,走到左边
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					// 走到else说明搜索二叉树里面有这个值,所以就不用插入
					return false;
				}
			}
			// 走到这块表达已经找到了插入的位置
			// 在插入时还应该判断当前节点应该插入到父节点的左还是右,判断完直接插入
			if (parent->_left == cur && parent->_data > x)
				parent->_left = new Node(x);
			else
				parent->_right = new Node(x);
		}

3、第三步大家请跟着我学习二叉树的节点删除操作,这个可就有点难啦,不过不影响,我来告诉大家原理和操作,画图及注释,大家肯定可以很清楚的学会这个删除节点的操作。不过在删除一个节点的时候需要明白这个节点是否存在,如果这个节点存在才有删除的必要,那这里就需要大家先实现一个查找模块,查找操作和插入操作有点相同,在这里小编就不仔细讲了,直接将代码及注释放到下面供大家参考及学习。

查找操作:

		bool Find(const T& x)
		{
			// 在这里查找二叉搜索树中是否存在 x 这个值
			Node* cur = _root;   // 先记录根节点方便遍历这颗二叉搜索树
			while (cur)
			{
				if (cur->_data < x)   // 如果这个 x 的值大于当前节点的值,就走向右侧
				{
					cur = cur->_right;
				}
				else if (cur->_data > x) // 如果这个 x 的值小于当前节点的值,就走向左侧
				{
					cur = cur->_left;
				}
				else
				{
					return true;       // 如果当前节点的值等于 x 及说明已经找到 返回true
				}
			}
			return false;              // 走到这块说明没有找到 ,返回false
		}

大家在实现删除节点的操作的时候需要先来理解下删除的原理,删除操作有四种情况,下面我分别画图来告诉大家这四种情况,还有这四种情况的处理方式,难度由易到难,放在下面:

情况一(删除的节点为叶子节点):

情况二(删除的节点不为叶子节点,并且存在一个子节点):

情况三(删除中间节点,并且当前节点有两个子节点):

情况四(删除中间节点,并且当前节点有两个子节点(不同于情况三)):

好了看到这块相信大家已经明白了删除操作的所有流程,那我把实现的过程代码及注释放到下面供大家参考:

		bool erase(const T& x)
		{
			assert(_root != nullptr);
			// 当只有一个根节点是特殊处理
			if (_root->_left == nullptr && _root->_right == nullptr)
			{
				delete _root;
				_root = nullptr;
				return true;
			}
			// 删除有两种情况
			// 1、删除的节点只有一个或者没有子节点
			// 2、删除的节点有两个节点
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur)
			{
				// 第一种情况
				if (cur->_data > x)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_data < x)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					// 处理的特殊情况 根节点是需要消除的节点 并且只有一个子节点
					if (cur == _root && cur->_left == nullptr)
					{
						_root = _root->_right;
						delete cur;
						return true;
					}
					if (cur == _root && cur->_right == nullptr)
					{
						_root = _root->_left;
						delete cur;
						return true;
					}
					int flag = 0;
					// 这里是处理第一种情况和第二种情况,把他们放在一块处理,这里有一个空指针说明已经找到了叶子节点
					// 或者叶子节点的上一个节点,但是只有一个子节点
					if (parent->_right == cur && cur->_left == nullptr)
					{
						parent->_right = cur->_right;
						flag = 1;
					}
					if (parent->_left == cur && cur->_left == nullptr)
					{
						parent->_left = cur->_right;
						flag = 1;
					}
					if (parent->_right == cur && cur->_right == nullptr)
					{
						parent->_right = cur->_left;
						flag = 1;
					}
					if (parent->_left == cur && cur->_right == nullptr)
					{
						parent->_left = cur->_left;
						flag = 1;
					}
					if (flag == 1)
					{
						delete cur;
						return true;
					}
					break;
				}
			}
			// 如果是正常情况下退出循环,说明没有找到要删除的数据
			if (cur == nullptr)
				return false;
			// 第二种情况
			// 可以选择在左边找一个最大值,或者在右边找一个最小值来替代删除的元素
			// 这里实现的是从右边找最小的
			Node* pt = nullptr;
			Node* rt = cur->_right;
			// 这种情况是第四种情况
			if (rt->_left == nullptr)
			{
				// 说明这个rt的节点就是右边最小的节点
				swap(rt->_data, cur->_data);
				cur->_right = rt->_right;
				delete rt;
				return true;
			}
			// 走到这块说明rt不是右边最小的节点
			// 这个是第三种情况
			while (rt->_left)
			{
				pt = rt;
				rt = rt->_left;
			}
			swap(rt->_data, cur->_data);
			delete rt;
			pt->_left = nullptr;
			return true;
		}

4、第四步就只需要处理的是按照大小顺序打印这棵树的每个节点的值,先讲解打印操作的原理

操作原理:因为这棵树的排序方式是根节点大于左子树,根节点小于右子树,所以使用二叉树的中序遍历就可以把这颗树按照大小顺序打印出来。

具体操作和数据结构中二叉树的中序遍历相同,这里我就直接向大家展示代码及注释即可:

		// 查找二叉树的中序遍历刚好是顺序排序
		void InOrder()
		{
			// 中序遍历需要递归来解决,所以这个 _root 不好传 如果直接用_root 的话 _root 就会被改变
			Node* cur = _root;
			_InOrder(cur);
			cout << endl;
		}
	private:
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;
			_InOrder(root->_left);
			cout << root->_data << " ";
			_InOrder(root->_right);
		}

		Node* _root = nullptr;

5、搜索二叉树的分析

如上图搜索二叉树可能会出现第二种情况,所以时间复杂度会大大提到,第二种情况的时间复杂度最后为O(N),但如果是接近平衡的搜索二叉树时间复杂度就会接近O(nlgn),大大提高效率,所以以搜索二叉树衍生除了AVL树和红黑树后面带给大家学习,就会解决这种一边倒的情况,让搜索二叉树接近平衡

好啦,今天的内容就到这啦,搜索二叉树的内容相信大家一定会有所收获,我们下期再见!~~~

  • 18
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 二分搜索算法,也称作折半查找,是一种常用的查找算法。其基本思想是将一个有序数组分成两部分,取间值进行比较,如果查找值小于间值,则在前半部分继续查找;如果查找值大于间值,则在后半部分继续查找,直到找到目标值或者找不到为止。 C++实现二分搜索算法的代码如下: ```c++ int binary_search(int arr[], int n, int target) { int left = 0, right = n - 1; while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] == target) { return mid; } else if (arr[mid] < target) { left = mid + 1; } else { right = mid - 1; } } return -1; } ``` 该函数接受一个有序数组`arr`,数组长度`n`和目标值`target`作为参数,返回目标值在数组的下标,如果找不到则返回-1。 接下来我们进行实验分析,以验证二分搜索算法的正确性和效率。 1. 实验环境 - 操作系统:Windows 10 - CPU:Intel Core i5-8250U - 编译器:Visual Studio 2019 2. 实验数据 我们使用了三组数据进行测试,分别是: - 数据集1:包含1000个元素的有序数组,其目标值为数组的第1个元素。 - 数据集2:包含10000个元素的有序数组,其目标值为数组的第5000个元素。 - 数据集3:包含100000个元素的有序数组,其目标值为数组的第99999个元素。 3. 实验结果 - 数据集1:算法执行时间为0毫秒,搜索结果为0。 - 数据集2:算法执行时间为0毫秒,搜索结果为4999。 - 数据集3:算法执行时间为0毫秒,搜索结果为99999。 4. 实验分析 从实验结果来看,二分搜索算法的效率非常高,即使在数据集3也能够快速找到目标值。同时,由于算法的正确性得到了验证,我们可以放心地在实际开发使用该算法。 需要注意的是,在使用二分搜索算法时,要求数据必须是有序的。如果数据无序,需要先进行排序操作,这会增加算法的时间复杂度。此外,该算法还有一个局限性,即不能处理重复的数据,因为无法确定重复数据在数组的位置。 ### 回答2: 二分搜索算法是一种高效的搜索算法,适用于有序的数组或列表。它的实现非常简单,通过比较间元素和目标值的大小,不断缩小搜索范围,直至找到目标值或确定不存在。 在进行实验分析时,我们首先需要设计实验目标和实验步骤。实验目标可以是比较不同数组大小下的搜索时间效率,或者比较二分搜索与其他搜索算法的性能差异。 实验步骤可以分为以下几个部分: 1. 实验准备:选择合适的编程语言和开发环境,在实验使用适当大小的有序数组或列表。 2. 实现二分搜索算法:编写代码实现二分搜索算法,确保逻辑正确、效率高。 3. 针对不同输入规模进行测试:根据实验目标,选择适当的输入规模(比如数组大小)进行测试。记录每次搜索的时间。 4. 进行多次实验:为了保证实验结果的可靠性,需要多次重复实验,取平均值。 5. 比较实验结果:将实验结果进行比较分析,可以绘制图表或整理数据表格来展示实验结果。可以比较不同算法或不同输入规模下的搜索效率。 6. 总结实验结论:根据实验结果,总结二分搜索算法的性能特点和优缺点,可以结合其他算法进行比较,给出相应的评价。 总之,通过上述实验分析过程,我们可以对二分搜索算法的实现进行较为详细的评估,并得出相应的结论,为进一步优化算法或解决实际问题提供参考。 ### 回答3: 二分搜索算法是一种在有序数组查找特定元素的算法。实现实验分析主要包括算法的实现步骤、时间复杂度分析以及实验结果的评估。 首先,二分搜索算法的实现步骤如下: 1. 定义数组的起始位置left和结束位置right,初始时left为0,right为数组长度减一。 2. 计算间元素的下标mid,mid为(left + right) / 2。 3. 比较间元素和目标元素的大小。如果间元素等于目标元素,则返回找到的下标;如果间元素大于目标元素,则将结束位置right更新为mid-1;如果间元素小于目标元素,则将起始位置left更新为mid+1。 4. 重复步骤2和步骤3,直到找到目标元素或者起始位置left大于结束位置right。 其次,二分搜索算法的时间复杂度分析为O(log n),其n为数组的长度。这是由于每次比较都将搜索范围减半,所以最多需要进行log n次比较。 最后,通过实验评估二分搜索算法的性能。实验可以通过构造不同大小的有序数组,然后分别使用二分搜索算法查找不同目标元素,记录算法的执行时间。通过比较不同规模数据下的执行时间,可以分析算法在不同数据规模下的性能表现。 综上所述,二分搜索算法的实现实验分析包括了算法的实现步骤、时间复杂度分析以及实验结果的评估。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值