【C++从0到王者】第二十七站:搜索二叉树

news2024/11/25 0:27:03

文章目录

  • 前言
  • 一、二叉搜索树的概念
  • 二、二叉搜索树的实现
    • 1.二叉树的结点定义
    • 2.二叉搜索树的结构
    • 3.二叉搜索树的构造函数
    • 4.二叉搜索树的插入(非递归)
    • 5.二叉搜索树的中序遍历(排序)
    • 6.二叉搜索树的查找(非递归)
    • 7.二叉搜索树的删除(非递归)
    • 8.二叉搜索树的查找(递归)
    • 9.二叉搜索树的插入(递归)
    • 10.二叉搜索树的删除(递归)
    • 11.二叉搜索树的销毁
    • 12.拷贝构造函数
    • 13.赋值运算符重载
  • 三、二叉搜索树的实现完整代码
  • 总结

前言

在前面我们其实已经分析过二叉树了,所谓二叉树就是度为2的树。而二叉树中,又有一种特殊的树称之为二叉搜索树

一、二叉搜索树的概念

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

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

image-20230904160138971

image-20230904160210605

为什么我们将这样的树称作二叉搜索树呢?因为这样的树天生就十分适合搜索。是想一下,使用上图中最右边的树,如果我们想找到7的话,首先7比6大,所以一定在右子树,而7小于9,所以一定在左子树。结果发现就找到了。从而我们可以发现,这种树的效率天生就很高。

而且这棵树最多找高度次。也就是说最多找N次(注意不是logN次,因为我们可能碰到下图中右边的树),下图中的两棵树都是二叉搜素树。也就是说时间复杂度为O(N)

image-20230904161039079

普通的二叉搜索树的时间复杂度为O(N),但是我们可以对其进行一些特殊变换,使之成为AVL树,红黑树。这样时间复杂度就可以降为了O(logN)了。

这棵树还有一个名字是二叉排序树,这是因为当我们对这棵树进行中序遍历的时候,结果是升序的。

二、二叉搜索树的实现

1.二叉树的结点定义

对于二叉树的结点,我们类似于list的结点定义一样,使用一个struct类。在这个类中,我们可以有默认构造函数,这个默认构造函数的缺省值为K()。

template<class K>
struct BSTreeNode
{
	BSTreeNode(const K& val = K())
		:_key(val)
		,_left(nullptr)
		,_right(nullptr)
	{}
	K _key;
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
};

2.二叉搜索树的结构

为了方便我们后序的结点的定义,我们可以先将结点给typedef一下。然后我们的成员变量其实也就这一个根节点。只要有根节点就可以构建出二叉搜索树了。

private:
	typedef BSTreeNode<K> Node;
	Node* _root;

3.二叉搜索树的构造函数

public:
	BSTree()
		:_root(nullptr)
	{}

如上代码所示,对于构造函数其实是比较简答的,我们只需要将二叉树的根结点初始化为空即可

4.二叉搜索树的插入(非递归)

对于插入接口,我们需要考虑的情况有如下几种:

  1. 二叉树为空的时候,直接new一个结点插入上去即可
  2. 如果二叉树不为空,那么我们就需要进行比较
    • 如果我们想要插入的值大于当前结点的值,那么我们就将当前结点迭代到右孩子
    • 如果我们想要插入的值小于当前结点的值,那么我们将当前结点迭代到左孩子
    • 如果恰好等于当前结点的值,那么我们可以认为插入失败了,因为此时已经不满足二叉搜索树的定义了。
    • 最终我们会发现如此迭代下去,最终的当前结点一定会为空指针,这并非我们所期望的。我们肯定是还需要它的父节点的,这样才能让我目标值插入进去。所以我们还需要一个值来记录当前所迭代位置的父节点。即我们下面代码的parent
    • 有了parent以后,我们现在就是new一个新的结点,然后直接parent链接起来。不过此时又分为两种情形,是左孩子还是右孩子,其实并不好确定。于是我们可以使用parent当前的值与目标值进行比对从而判断出是插入左孩子还是右孩子。
public:
	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}
		else
		{
			Node* parent = _root;
			Node* cur = _root;
			while (cur != nullptr)
			{
				if (cur->_key == key)
				{
					return false;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					parent = cur;
					cur = cur->_right;
				}
			}
			cur = new Node(key);
			if (parent->_key > key)
			{
				parent->_left = cur;
			}
			else if (parent->_key < key)
			{
				parent->_right = cur;
			}
			return true;
		}
	}

5.二叉搜索树的中序遍历(排序)

插入了以后,我们可以使用中序遍历对其进行输出,由于中序遍历就是排序后的情形,所以我们可以很方便的看到我们当前二叉树的结果。

对于排序,我们最好写一个子函数来实现,因为没有子函数的话,参数不好进行控制,会使得代码十分繁琐。

如下代码所示,对于中序遍历我们其实还是比较容易看懂的。无非就是先左子树,然后根,然后右子树即可。我们最好将子函数放在私有的部分中,因为在类外我们并不需要直接使用这个函数

public:
	void InOrder()
	{
		_InOrder(_root);
	}
private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}

6.二叉搜索树的查找(非递归)

对于查找的逻辑,是非常简单的,它与插入也是比较相似的,当我们当前结点的值小于目标的时候,迭代到右子树,当大于目标的时候,迭代到左子树。当相等的时候返回true即可。但若当cur都为nullptr了的时候还没有找到,那就是没有找到,返回false即可

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

7.二叉搜索树的删除(非递归)

对于二叉搜索树的删除操作,其实是比较难的。这里出现了至少十种以上的情形。

image-20230906160052509

  1. 找目标结点。这里的找目标结点,我们采用与插入法类似的操作,有一个当前结点和一个父结点。一开始自然是cur为根节点,parent为nullptr。然后我们进行迭代, 当cur不为空的时候,如果当前cur里面的值大于目标值,则parent迭代为cur,cur迭代为左孩子。如果cur里面的值小于目标值的话,则parent迭代为cur,cur迭代为右孩子。如果相等的情况下,由于比较复杂,我们在后面在进行讨论。如果迭代下去。只要没有出现相等的情形,迟早会离开循环,离开循环了,那么直接返回false即可

  2. 如果中间遇到相等的情况,也就是说,我们已经找到了目前要删除的值,这个值就是cur所指向的结点。这个时候,我们需要进一步将其进行分析。

    • 这里存在的主要问题就是cur与parent以及cur的孩子之间的关系比较复杂。

    • 首先cur的孩子就分为四种情况,分别是没有孩子、只有左孩子、只有右孩子、两个孩子都有。由于我们要删除cur结点,所以孩子的情况的不同,我们所需要的处理方式就不一样。

      • 如果没有孩子的话,那么其实我们只需要直接删除该结点,然后将parent所指向cur的位置给置空即可。如下图所示:即当我们想要在下面这棵树中删除13的时候的情形

      • 如果cur只有一个左孩子,也就是说右孩子为空。那么我们可以,使用托孤的方式,即将cur的左孩子直接与parent连接起来,然后在直接删除cur即可

        image-20230906163742459

      • 如果cur只有一个右孩子,也就是说,左孩子为空,那么我们可以和上面一样采用类似的手段,托孤,将 cur的右孩子直接与parent进行连接,然后删除cur

      image-20230906164034831

      • 如果两个孩子都存在的话,那么此时情形比较复杂,我们采用替代法。替代法的基本思路是,将要删除的该结点视为一棵子树,然后找到这棵树的左子树的最大值,或者右子树的最小值。然后我们与这两个中的任何一个进行替换即可,然后删除原来被替换的结点。如下图所示、

        image-20230906164621321

      其实关于以上的cur与其孩子的四种情形,可以简化为三种情况:即将没有孩子归为只有一个左孩子或者只有一个右孩子的情形,因为我们可以将他们的孩子视为一个nullptr,如果这样思考的话,那么前三种方案都是使用托孤的思路。唯独第四种,我们需要使用替代法进行解决

    • 除了cur孩子之间的问题以外,还有parent和cur之间的关系也是存在一些情况的。

      • cur为parent的左孩子或右孩子,这种情形是比较简单的,也是前面图中的情形。我们只需要与cur与它的孩子的情况一关联即可。其实就是前面cur与其孩子的变化。

      • parent不存在,即cur就是根节点,这种情况我们需要进行特殊处理,因为此时已经不存在父节点了

        ① 当cur的左孩子或者右孩子任意一个为空的时候,此时我们采用托孤的处理方案,不过此时的托孤需要一些变化,因为cur其实就是_root,所以我们直接让_root赋值为cur->_left或者cur->_right即可。

        image-20230906171243565

        ②当cur的左孩子和右孩子都存在的时候,我们还是使用替代法

        如下是我们正常时候的替代,变化还不是很大。

        image-20230906172736448

        但是当恰好leftMax就是cur的左侧结点的时候,这时候就出现意外了,因为我们是需要进行连接的,所以需要知道leftMax的父节点是谁才可以方便连接,上图是因为leftMax肯定是它的右孩子,而此时变成了左孩子,所以需要特殊处理。(这里的特殊处理在前面的每一次使用替代法中都会出现这种情况)

        image-20230906172850668

根据以上的分析,我们已经可以写出以下的删除代码了:

由于这里存在三代人之间的关系,即parent与cur之间的关系可分为三种情况,cur与其孩子的关系可分为三种情况,这里可以不严格的要求先考哪一类情况。我们两种代码都给出

以下是先考虑parent与cur这两代人之间的关系所写的代码

public:
	bool Erase1(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 (parent == nullptr)
				{
					if (cur->_left == nullptr)
					{
						_root = cur->_right;
						delete cur;
						return true;
					}
					else if (cur->_right == nullptr)
					{
						_root = cur->_left;
						delete cur;
						return true;
					}
					else
					{
						Node* leftMaxParent = cur;
						Node* leftMax = cur->_left;
						if (leftMax->_right == nullptr)
						{
							leftMax->_right = cur->_right;
							delete cur;
							_root = leftMax;
							return true;
						}
						while (leftMax->_right)
						{
							leftMaxParent = leftMax;
							leftMax = leftMax->_right;
						}
						std::swap(leftMax->_key, cur->_key);
						leftMaxParent->_right = leftMax->_left;
						delete leftMax;
						leftMax = nullptr;
						return true;
					}
				}
				if (parent->_left == cur)
				{
					if (cur->_left == nullptr)
					{
						parent->_left = cur->_right;
						delete cur;
						return true;
					}
					else if (cur->_right == nullptr)
					{
						parent->_left = cur->_left;
						delete cur;
						return true;
					}
					else 
					{
						Node* leftMaxParent = cur;
						Node* leftMax = cur->_left;
						if (leftMax->_right == nullptr)
						{
							leftMax->_right = cur->_right;
							delete cur;
							parent->_left = leftMax;
							return true;
						}
						while (leftMax->_right)
						{
							leftMaxParent = leftMax;
							leftMax = leftMax->_right;
						}
						std::swap(leftMax->_key, cur->_key);
						leftMaxParent->_right = leftMax->_left;
						delete leftMax;
						leftMax = nullptr;
						return true;
					}
				}
				else
				{
					if (cur->_left == nullptr)
					{
						parent->_right = cur->_right;
						delete cur;
						return true;
					}
					else if (cur->_right == nullptr)
					{
						parent->_right = cur->_left;
						delete cur;
						return true;
					}
					else
					{
						Node* leftMaxParent = cur;
						Node* leftMax = cur->_left;
						if (leftMax->_right == nullptr)
						{
							leftMax->_right = cur->_right;
							delete cur;
							parent->_right = leftMax;
							return true;
						}
						while (leftMax->_right)
						{
							leftMaxParent = leftMax;
							leftMax = leftMax->_right;
						}
						std::swap(leftMax->_key, cur->_key);
						leftMaxParent->_right = leftMax->_left;
						delete leftMax;
						leftMax = nullptr;
						return true;
					}
				}
			}
		}
		return false;
	}

以下是根据先考虑cur与其孩子之间的关系所写的代码:

public:
	bool Erase2(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 (parent->_left == cur)
					{
						parent->_left = cur->_right;
					}
					else if (parent->_right == cur)
					{
						parent->_right = cur->_right;
					}
				}
				else if (cur->_right == nullptr)
				{
					if (parent == nullptr)
					{
						_root = cur->_left;
					}
					else if (parent->_left == cur)
					{
						parent->_left = cur->_left;
					}
					else if (parent->_right = cur)
					{
						parent->_right = cur->_left;
					}
				}
				else
				{
					Node* leftMax = cur->_left;
					Node* leftMaxParent = cur;
					while (leftMax->_right)
					{
						leftMaxParent = leftMax;
						leftMax = leftMax->_right;
					}
					std::swap(cur->_key, leftMax->_key);
					if (leftMaxParent->_left == leftMax)
					{
						leftMaxParent->_left = leftMax->_left;
					}
					else
					{
						leftMaxParent->_right = leftMax->_left;
					}

					cur = leftMax;
				}
				delete cur;
				return true;
			}
		}
	}

可见,虽然无论先写哪一种情形,其实都可以完成这个接口,但是代码量有显著的差异。这里推荐先考虑cur和其后代之间的关系。因为替代法这个出现于cur与其后代之间的关系之中。

8.二叉搜索树的查找(递归)

二叉树其实本身还是比较适合递归的。这里的思路也很简单, 不过我们为了可以控制参数,我们必须要写一个子函数去解决,当根节点为空的时候,直接返回false即可,就是没找到,当当前的结点小于要查找的结点,那么去右子树找,当大于时候,去左子树找即可。

public:
	bool FindR(const K& key)
	{
		return _FindR(_root, key);
	}
private:
	bool _FindR(Node* root, const K& key)
	{
		if (root == nullptr)
		{
			return false;
		}
		if (root->_key == key)
		{
			return true;
		}
		else if (root->_key > key)
		{
			return _FindR(root->_left, key);
		}
		else
		{
			return  _FindR(root->_right, key);
		}
	}

9.二叉搜索树的插入(递归)

这里的递归插入也是比较简单的,我们仍然使用子函数调用,当小于待插入的值的时候则去右子树插入,当大于待插入的值的时候,去左子树插入,但是如果遇到相等了,那么就只能插入失败了。最终我们一定会遇到一个空树,我们可以直接new一个结点来,不过这里的问题是如何连接?其实我们可以使用引用,使用引用的话,此时的root本身就早已跟父节点连接到一块了,所以我们直接给这个结点进行赋值,即可解决问题。

public:
	bool InsertR(const K& key)
	{
		return _InsertR(_root, key);
	}
private:
	bool _InsertR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}
		if (root->_key < key)
		{
			return _InsertR(root->_right, key);
		}
		else if (root->_key > key)
		{
			return _InsertR(root->_left, key);
		}
		else
		{
			return false;
		}
	}

10.二叉搜索树的删除(递归)

如下是递归式删除的操作。在这里我们还是使用子函数调用方便控制参数。这里最好使用引用,因为引用的话永远都是这一棵树。比较容易操作,否则就得传入二级指针就比较麻烦了。

如果root为空,就代表这棵树里面根本就没有key,自然直接返回nullptr即可。

如果当前结点的值小于key,那么去右子树删除,如果大于,去左子树删除。

当等于的时候,我们可以这样操作:先将待删除的结点给记录下来,如果该节点左孩子为空,或者右孩子为空的话,我们可以直接覆盖的方式去迭代。这步操作相对而言是比较妙的。

image-20230906183305765

那么我们不妨思考一下:前面的非递归形式,能否使用这种方式来进行迭代呢?其实是不可以的,这里能迭代是因为引用,永远都是那个空间,且由于在不同的栈帧原因导致了引用的指向被巧妙的改变了。而前者他们并没有使用引用,都是使用局部变量来进行迭代的,因为C++中的引用是不可以改变指向的。所以自然不可以。

如下图所示,非递归中,迭代的本质,仅仅只是局部变量的不断更替,不会直接影响root,这里局部变量的修改仅仅只是间接修改堆区已有数据的连接关系。如果直接赋值的话,只是修改了栈区的局部变量的数据,并未修改实际关系。

image-20230906184430650

如果cur的两个孩子都存在的话,我们还是使用替代法,设置一个局部变量leftMax,让其指向cur->_left,然后不断迭代他,找到这颗树的左子树最大结点。然后交换结点,最后我们知道要删除的结点一定在左子树,所以我们使用递归在左子树中找到要删除的结点即可。

注意这里千万不要将leftMax给传入了。这样做看似可以,但是leftMax是一个局部变量,等到离开本层的递归的时候,leftMax被断了,整棵树的逻辑关系全部混乱了。

public:	
	bool EraseR(const K& key)
	{
		return _EraseR(_root, key);
	}
private:
	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;
			}
			else if (root->_right == nullptr)
			{
				root = root->_left;
			}
			else
			{
				Node* leftMax = root->_left;
				while (leftMax->_right)
				{
					leftMax = leftMax->_right;
				}
				std::swap(leftMax->_key, root->_key);
				return _EraseR(root->_left, key);
			}
			delete del;
			return true;
		}
	}

11.二叉搜索树的销毁

销毁操作比较简答, 直接按照后序的方式进行销毁即可。最好传引用,因为可以置空root指针

public:
	~BSTree()
	{
		_Destory(_root);
	}
private:
	void _Destory(Node*& root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Destory(root->_left);
		_Destory(root->_right);
		delete root;
		root == nullptr;
	}

12.拷贝构造函数

拷贝构造函数,我们需要实现一个深拷贝,这里我们使用先序的方式进行构造。

public:
	BSTree(const BSTree<K>& t)
	{
		_root = Copy(t._root);
	}
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;
	}

13.赋值运算符重载

赋值运算符重载,我们可以使用现代写法,这样做是最方便的

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

三、二叉搜索树的实现完整代码

#pragma once

template<class K>
struct BSTreeNode
{
	BSTreeNode(const K& val = K())
		:_key(val)
		,_left(nullptr)
		,_right(nullptr)
	{}
	K _key;
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
};

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

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

	~BSTree()
	{
		_Destory(_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;
		}
		else
		{
			Node* parent = _root;
			Node* cur = _root;
			while (cur != nullptr)
			{
				if (cur->_key == key)
				{
					return false;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					parent = cur;
					cur = cur->_right;
				}
			}
			cur = new Node(key);
			if (parent->_key > key)
			{
				parent->_left = cur;
			}
			else if (parent->_key < key)
			{
				parent->_right = cur;
			}
			return true;
		}
	}
	void InOrder()
	{
		_InOrder(_root);
	}

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

	bool Erase1(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 (parent == nullptr)
				{
					if (cur->_left == nullptr)
					{
						_root = cur->_right;
						delete cur;
						return true;
					}
					else if (cur->_right == nullptr)
					{
						_root = cur->_left;
						delete cur;
						return true;
					}
					else
					{
						Node* leftMaxParent = cur;
						Node* leftMax = cur->_left;
						if (leftMax->_right == nullptr)
						{
							leftMax->_right = cur->_right;
							delete cur;
							_root = leftMax;
							return true;
						}
						while (leftMax->_right)
						{
							leftMaxParent = leftMax;
							leftMax = leftMax->_right;
						}
						std::swap(leftMax->_key, cur->_key);
						leftMaxParent->_right = leftMax->_left;
						delete leftMax;
						leftMax = nullptr;
						return true;
					}
				}
				if (parent->_left == cur)
				{
					if (cur->_left == nullptr)
					{
						parent->_left = cur->_right;
						delete cur;
						return true;
					}
					else if (cur->_right == nullptr)
					{
						parent->_left = cur->_left;
						delete cur;
						return true;
					}
					else 
					{
						Node* leftMaxParent = cur;
						Node* leftMax = cur->_left;
						if (leftMax->_right == nullptr)
						{
							leftMax->_right = cur->_right;
							delete cur;
							parent->_left = leftMax;
							return true;
						}
						while (leftMax->_right)
						{
							leftMaxParent = leftMax;
							leftMax = leftMax->_right;
						}
						std::swap(leftMax->_key, cur->_key);
						leftMaxParent->_right = leftMax->_left;
						delete leftMax;
						leftMax = nullptr;
						return true;
					}
				}
				else
				{
					if (cur->_left == nullptr)
					{
						parent->_right = cur->_right;
						delete cur;
						return true;
					}
					else if (cur->_right == nullptr)
					{
						parent->_right = cur->_left;
						delete cur;
						return true;
					}
					else
					{
						Node* leftMaxParent = cur;
						Node* leftMax = cur->_left;
						if (leftMax->_right == nullptr)
						{
							leftMax->_right = cur->_right;
							delete cur;
							parent->_right = leftMax;
							return true;
						}
						while (leftMax->_right)
						{
							leftMaxParent = leftMax;
							leftMax = leftMax->_right;
						}
						std::swap(leftMax->_key, cur->_key);
						leftMaxParent->_right = leftMax->_left;
						delete leftMax;
						leftMax = nullptr;
						return true;
					}
				}
			}
		}
		return false;
	}

	bool Erase2(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 (parent->_left == cur)
					{
						parent->_left = cur->_right;
					}
					else if (parent->_right == cur)
					{
						parent->_right = cur->_right;
					}
				}
				else if (cur->_right == nullptr)
				{
					if (parent == nullptr)
					{
						_root = cur->_left;
					}
					else if (parent->_left == cur)
					{
						parent->_left = cur->_left;
					}
					else if (parent->_right = cur)
					{
						parent->_right = cur->_left;
					}
				}
				else
				{
					Node* leftMax = cur->_left;
					Node* leftMaxParent = cur;
					while (leftMax->_right)
					{
						leftMaxParent = leftMax;
						leftMax = leftMax->_right;
					}
					std::swap(cur->_key, leftMax->_key);
					if (leftMaxParent->_left == leftMax)
					{
						leftMaxParent->_left = leftMax->_left;
					}
					else
					{
						leftMaxParent->_right = leftMax->_left;
					}

					cur = leftMax;
				}
				delete cur;
				return true;
			}
		}
	}

	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);
	}
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 _Destory(Node*& root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Destory(root->_left);
		_Destory(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->_right, key);
		}
		else if (root->_key > key)
		{
			return _EraseR(root->_left, key);
		}
		else
		{
			Node* del = root;
			if (root->_left == nullptr)
			{
				root = root->_right;
			}
			else if (root->_right == nullptr)
			{
				root = root->_left;
			}
			else
			{
				Node* leftMax = root->_left;
				while (leftMax->_right)
				{
					leftMax = leftMax->_right;
				}
				std::swap(leftMax->_key, root->_key);
				return _EraseR(root->_left, key);
			}
			delete del;
			return true;
		}
	}
	bool _InsertR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}
		if (root->_key < key)
		{
			return _InsertR(root->_right, key);
		}
		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 true;
		}
		else if (root->_key > key)
		{
			return _FindR(root->_left, key);
		}
		else
		{
			return  _FindR(root->_right, key);
		}
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}
private:
	Node* _root;
};

对于上面的代码,我们可以使用如下代码进行测试

#include<iostream>
#include<algorithm>
using namespace std;
#include"BinarySearchTree.h"


void Test1()
{
	BSTree<int> root;
	int arr[] = { 8,3,1,10,6,4,7,14,13,5,10,15,20,21,22,32,2 };
	for (auto& e : arr)
	{
		root.Insert(e);
	}
	root.InOrder();
	cout << endl;

	for (auto& e : arr)
	{
		root.Erase1(e);
		root.InOrder();
		cout << endl;
	}

	BSTree<int> root2;
	for (auto& e : arr)
	{
		root2.Insert(e);
	}
	root2.InOrder();
	cout << endl;

	for (auto& e : arr)
	{
		root2.Erase2(e);
		root2.InOrder();
		cout << endl;
	}
	cout << root2.FindR(1) << endl;
}

void Test2()
{
	BSTree<int> root;
	int arr[] = { 8,3,1,10,6,4,7,14,13,5,10,15,20,21,22,32,2 };
	for (auto& e : arr)
	{
		root.InsertR(e);
	}
	root.InOrder();
	cout << endl;

	cout << root.FindR(1) << endl;


	for (auto& e : arr)
	{
		root.EraseR(e);
		root.InOrder();
		cout << endl;
	}
	cout << root.FindR(1) << endl;
}
void Test3()
{
	BSTree<int> root;
	int arr[] = { 8,3,1,10,6,4,7,14,13,5 };
	for (auto& e : arr)
	{
		root.InsertR(e);
	}
	root.InOrder();
	cout << endl;
	
	BSTree<int> r1(root);

	r1.InOrder();
	cout << endl;
	BSTree<int> t2 = r1;
	t2.InOrder();


}

int main()
{
	Test3();
	return 0;
}

最终结果都是符合我们的预期的。


总结

本文详细介绍了二叉搜索树的概念和实现,二叉搜索树的删除是最麻烦的,因为它的考虑的情况确实是非常多的,也确实是比较难的。希望读者可以好好品味

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

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

相关文章

Excel VSTO开发5 -Excel对象结构

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 5 Excel对象结构 Excel提供了几个比较重要的对象&#xff1a; Application、Workbooks、Workbook、Worksheets、Worksheet 为了便…

for循环命名

for循环命名 一、需求背景二、基本语法三、具体使用 一、需求背景 当我们使用双重循环查找某个目标&#xff0c;找到后想要在内循环跳出整个双重循环&#xff0c;我们可能想到用一个标志位&#xff0c;在外层循环判断到标志位为true时&#xff0c;跳出外层循环。 例如&#xf…

MySQL数据库和表的操作

数据库基础 存储数据用文件就可以了&#xff0c;为什么还要弄个数据库? 文件保存数据有以下几个缺点&#xff1a; 1、文件的安全性问题 2、文件不利于数据查询和管理 3、文件不利于存储海量数据 4、文件在程序中控制不方便 数据库存储介质&#xff1a; 磁盘 内存 为了解决上…

【Spring面试】一、SpringBoot启动优化与最大连接数

文章目录 Q1、SpringBoot可以同时处理多少请求Q2、SpringBoot如何优化启动速度Q3、谈谈对Spring的理解Q4、Spring的优缺点 Q1、SpringBoot可以同时处理多少请求 调试&#xff1a; 写一个测试接口&#xff1a; RestController Slf4j public class RequestController{GetMapping…

找短视频音效、BGM,就上这5个网站

找视频剪辑音效、BGM就上这5个网站下载&#xff0c;免费、付费、商用的全都有&#xff0c;赶紧收藏起来~ 1、菜鸟图库 https://www.sucai999.com/audio.html?vNTYwNDUx 菜鸟图库是一个综合性素材网站&#xff0c;站内涵盖设计、图片、办公、视频、音效等素材。其中音效素材就…

javaee spring整合mybatis spring帮我们创建dao层

项目结构 pom依赖 <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/P…

Excel VSTO开发4 -其他事件

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 4 其他事件 针对插件的事件主要有Startup、Shutdown这两个事件&#xff0c;在第2节中已经讲解。在开发窗口中&#xff0c;选择对象…

线程的6种状态

线程的6种状态 状态说明new初始状态&#xff1a;线程被创建&#xff0c;但还没有调用start()方法runnable运行状态&#xff1a;Java线程将操作系统中的就绪和运行两种状态笼统的称作“运行”blocked阻塞状态&#xff1a;表示线程阻塞于锁waiting等待状态&#xff1a;表示线程进…

Direct3D颜色

在Direct3D中颜色用RGB三元组来表示&#xff0c;RGB数据可用俩种不同的结构来保存&#xff0c;第一种是D3DCOLOR&#xff0c;它实际上与DWORD类型完全相同&#xff0c;共有32位&#xff0c;D3DCOLOR类型种的各位被分成四个8位项&#xff0c;每项存储了一种颜色分量的亮度值。 由…

U2Net使用方法和实现多类别语义分割模型改造

作者的碎碎念&#xff1a;U2Net是用来实现SOD的语义分割&#xff0c;本篇论文会介绍算法内容、主要代码、使用方法&#xff0c;以及如何将二分类语义分割修改为多类别语义模型。如果只想知道怎么训练自己的数据集&#xff0c;或者如何修改网络&#xff0c;可以通过目录进行跳转…

【C语言】错题本(2)

题目: 将题目代码粘贴在下面便于分析: #define MAX_SIZE AB struct _Record_Struct {unsigned char Env_Alarm_ID : 4;unsigned char Para1 : 2;unsigned char state;unsigned char avail : 1;}*Env_Alarm_Record;struct _Record_Struct *pointer (struct _Record_Struct*)m…

【PyCharm Community Edition】:PCAN-USB上位机开发

PCAN-USB上位机开发 一级目录二次开发问题记录继承父类的函数platform.system()判断不准确打开PCANBasic.dll出错延伸阅读一级目录 二次开发 下载链接 问题记录 继承父类的函数 python中super().init() platform.system()判断不准确 根据不同系统,打开DLL的方法不同,本来…

IDEA批量解决Lombok警告,开发者必备技巧!

问题背景 用Lombok的Data注解的时候&#xff0c;因为封装了一个公共的Base基础类&#xff0c;总是会提示以下警告提示&#xff1a; Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. …

软件测试的基础(1)

程序员(开发) :编写程序代码(实现产品需求) 产品:收集并设计需求-需求文档(根据用户需求进行产品设计) UI设计师:设计界面,向外展示的形态 前端:用代码实现页面的显示 DBA:数据库设计(系统数据之间的关联) 运维:版本控制和发布、升级迭代,环境搭建和维护 客服:客户支持,…

jsvmp逆向(补环境篇)

书接上回 上篇文章写到tx的jsxmp的算法逆向&#xff0c;文章链接在这里。初试jsvmp加密 。但是可能有伙伴觉得不够详细。 这里放一个大佬的文章链接。 https://www.52pojie.cn/thread-1521480-1-1.html 。其实就是一个变形的xtea加密。 大佬的文章已经讲了很清楚了&#…

小程序测试应该进行哪些测试?起到什么作用?

在如今小程序蓬勃发展的时代&#xff0c;越来越多的企业选择开发小程序来扩大业务。在推出小程序之前&#xff0c;进行全面的测试是至关重要的。 一、小程序测试的注意事项 1、功能测试&#xff1a;测试小程序的各项功能是否正常&#xff0c;包括页面跳转、数据加载、提交操作…

依赖导入失败场景和解决方案

在使用 Maven 构建项目时&#xff0c;可能会发生依赖项下载错误的情况&#xff0c;主要原因有以下几种&#xff1a; 下载依赖时出现网络故障或仓库服务器宕机等原因&#xff0c;导致无法连接至 Maven 仓库&#xff0c;从而无法下载依赖。 依赖项的版本号或配置文件中的版本号错…

恒运资本:股市板块轮动顺口溜?

股市是一个变化多端的场所&#xff0c;不同的板块会因为不同的方针、商场影响、经济形势等多种原因而有不同的体现。因而&#xff0c;不同时期不同板块的轮动也成为了研究的热门。下面咱们就通过一个顺口溜&#xff0c;来深化了解股市板块轮动&#xff1a; “钢铁、水泥、煤炭…

Linux如何安装MySQL

Linux安装MySQL5.7 1、下载 官网下载地址&#xff1a;http://dev.mysql.com/downloads/mysql/ 2、复制下面几个文件 3、检查当前系统是否安装过mysql、检查当前mysql依赖环境、检查/tmp文件夹权限 1&#xff09;检查当前系统是否安装过mysql&#xff0c;执行安装命令前&am…

CAS策略

CAS CAS&#xff08;Compare And Swap&#xff09;比较并交换 CAS是多线程环境下对共享变量进行修改时的一种策略&#xff0c;主要存在三个参数&#xff1a;当前值、预估值、结果 CAS采用的策略是当一个线程要对共享变量进行修改时&#xff0c;需要获取内存中共享变量的值作…