二叉搜索树(BinarySearchTree)

Posted 正义的伙伴啊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二叉搜索树(BinarySearchTree)相关的知识,希望对你有一定的参考价值。

二叉搜索树(BinarySearchTree)

文章目录

二叉搜索树的概念

搜索二叉树满足一下条件:

  • 是一棵二叉树
  • 这可树上的任意一个节点满足:若他的右子树不为空,则右子树上的所有节点的值大于该节点的值
  • 这可树上的任意一个节点满足:若他的左子树不为空,则左子树上的所有节点的值小于该节点的值
  • 它的的左子树和右子树也是二叉搜索树

例如下图就是一个标准的二叉搜索树:

我们同时还可以发现:二叉搜索树的中序遍历出来数应该是一个从小到大的排列
我们下面讨论的二叉搜索树是单值二叉搜索树即:不会出现重复值的二叉搜索树!

如何定义 二叉搜索树的节点 && 二叉搜索树

这里我们采用模板类的方式来定义它的节点:

template<class Value>
	struct BSTreeNode
	
	public:
		BSTreeNode(const Value& x=Value())
			:val(x)
			, left(nullptr)
			, right(nullptr)
		
		BSTreeNode* left;
		BSTreeNode* right;
		Value val;
	;

这样每个节点里面就保存了一个val和两个指向左右子树的指针!

同样的我们采用模板类的形式建立 树 的结构

template<class V>
class BSTree

	typedef BSTreeNode<V> Node;
public:


	//.....函数功能......//
	//下面的所有函数都是写在这里的//


private:
	Node* root;
;

这样一个棵完整的二叉搜索树,从节点描述 到 树的结构组织,就建立完成了。

查找

如何从一个二叉搜索树中查找到某一个特定的值x?
其实很简单:从根节点开始寻找,根据二叉搜索树的特性:

  • 如果x大于该节点的值,则x可能会在该节点的右子树出现,而一定不会在节点的左子树出现,因为右子树的所有节点的值都比该节点的值大,而左子树的所有值都比该节点的值要小
  • 如果小于该节点的值,则x可能会在该节点的左子树出现,而一定不会在节点的右子树出现
  • 如果x正好等于该节点的值,则说明找到了
  • 如果节点走到null节点也就是树的某条路径中的最后一个值还没有找到,就说明这颗树里就没有x这个值

代码实现:

Node* Find(const k& x)


	Node* cur = root;
	Node* parent = root;

	while (cur)
	
		if (cur->val < x)
		
			parent = cur;
			cur = cur->right;
		
		else if (cur->val > x)
		
			parent = cur;
			cur = cur->left;
		
		else
		
			return cur;
		
	
	return nullptr;

插入

如何插入一个节点的思路也十分简单,但是在此之前我们要知道一点二叉搜索树的插入只能向叶子节点后面插入,所以:

  • 首先我们要找到在哪里插入这个节点,也就是上面find查找节点的思路,找到要插入的位置(该位置一定是nullptr)
  • 第二部很简单——插入节点:创建节点、链接到树结构上

代码

bool Insert(const V& x)

	Node* cur = root;
	Node* parent = cur;
	while (cur)
	
		if (cur->val < x)
		
			parent = cur;
			cur = cur->right;
		
		else if (cur->val > x)
		
			parent = cur;
			cur = cur->left;
		
		else
		
			return false;
		
	

	if (parent == nullptr)   //cur为根节点,也就是树为空的情况
		root = new Node(x);
	else
	
		cur = new Node(x);

		if (parent->val < x)  //将节点连接到树结构上
			parent->right = cur;
		else
			parent->left = cur;
	

删除

删除的思路与插入的思路相似,第一步都必须找到删除节点的位置,但是第二部如何删除就相当有学问了:
设要删除的节点为cur节点,而cur的父节点为parent
情况一:如果左右子树中至少有一个问空

  1. 如果cur的左子树为空

这时只要删除cur节点并将parent的左/右(取决于cur是parent的左还是右)链接到cur的右子树上就行了

  1. 如果cur的右子树为空

这时只要删除cur节点并将parent的左/右(取决于cur是parent的左还是右)链接到cur的左子树上就行了

  1. 如果cur的左右子树均为空
    这种情况其实不需要处理,这种情况可以看成上面两种情况任意一种情况的特化。例如情况一中的右子树为nullptr(也就是右子树为空),就实现了这种情况的删除。同理情况二也可以实现!

情况二:左右子树均不为空
仔细思考之后会发现:直接删除会导致搜索树结构的破坏,所以我们这里采取替换删除的方法(名字是我瞎编的😁)。

替换删除的思想很简单:如果删除情况一的节点也就是左右子树至少有一个为空的情况,是可以直接删除的,所以我们就找一个符合情况一的节点的值与cur节点的值替换且替换后满足搜索树的结构,最后再将这个节点从树上删除(因为满足情况一所以删除起来是可行的)

现在主要的问题是如何找到这个节点?
这个节点可以是cur节点的 :

  • 左子树的最大节点 :也就是左子树的最右边的节点,这个节点的右子树一定为空,左子树情况未知,满足情况一的条件,与cur的值替换之后由于是左子树的最大节点,所以大于左子树的任意节点,满足搜索树的结构

  • 右子树的最小节点:也就是右子树的最左边的节点,这个节点的左子树一定为空,右子树情况未知,满足情况一的条件,与cur的值替换之后由于是右子树的最小节点,所以小于右子树的任意节点,满足搜索树的结构

找到上面任意一个节点就可以完成删除。

代码
这个是找右子树的最小值的代码👇

bool Erase(const k& x)

	if (root == nullptr)   //开头检查一下是否是空树
		assert(root == nullptr);


	Node* cur = root;
	Node* parent = root;

	while (cur)
	
		if (cur->val < x)
		
			parent = cur;
			cur = cur->right;
		
		else if (cur->val > x)
		
			parent = cur;
			cur = cur->left;
		
		else
		
			//删除操作
			if (cur->left == nullptr || cur->right == nullptr) //左右子树至少一个为空
			
				if (cur == root)   //cur是根结点的情况要考虑一下!
				
					root = (cur->left == nullptr) ? cur->right : cur->left;
				
				else    //cur不是根节点
				
					if (cur->right == nullptr)
					
						if (parent->left == cur)
							parent->left = cur->left;
						else
							parent->right = cur->left;
					
					else if (cur->left == nullptr)
					
						if (parent->left == cur)
							parent->left = cur->right;
						else
							parent->right = cur->right;
					
				
			
			else  //左右子树都不为空 
			
				//找到右子树中的最小值与cur节点的值进行替换

				//找右子树最小节点,也就是右子树的最左边的节点,这个节点:左子树一定为null,右子树未知
				Node* tmp = cur->right;
				Node* father = cur;
				while (tmp->left)
				
					father = tmp;
					tmp = tmp->left;
				

				cur->val = tmp->val;  //交换值

				if (father == cur)   //这里就是要删除的cur节点 的右子树的最大值就是cur的右节点
					father->right = tmp->right;
				else
					father->left = tmp->right;
			
			return true;

		
	
	return false;


这个是找左子树的最大值的代码👇

bool Erase(const k& x)
		
			if (root == nullptr)   //开头检查一下是否是空树
				assert(root == nullptr);


			Node* cur = root;
			Node* parent = root;

			while (cur)
			
				if (cur->val < x)
				
					parent = cur;
					cur = cur->right;
				
				else if (cur->val > x)
				
					parent = cur;
					cur = cur->left;
				
				else
				
					//删除操作
					if (cur->left == nullptr || cur->right == nullptr) //左右子树至少一个为空
					
						if (cur == root)   //cur是根结点的情况要考虑一下!
						
							root = (cur->left == nullptr) ? cur->right : cur->left;
						
						else    //cur不是根节点
						
							if (cur->right == nullptr)
							
								if (parent->left == cur)
									parent->left = cur->left;
								else
									parent->right = cur->left;
							
							else if (cur->left == nullptr)
							
								if (parent->left == cur)
									parent->left = cur->right;
								else
									parent->right = cur->right;
							
						
					
					else  //左右子树都不为空 
					
						//找到右子树中的最小值与cur节点的值进行替换

						//找到左子树中的最大值与cur节点的值进行替换
						Node* tmp = cur->left;
						Node* father = cur;
						while (tmp->right)
						
							father = tmp;
							tmp = tmp->right;
						

						cur->val = tmp->val;

						if (father == cur)
							father->left = tmp->left;
						else
							father->right = tmp->left;


					
					return true;

				
			

			return false;
		

二叉搜索树的缺点

如果二叉搜索树用上面的代码插入肯会出现一些问题,例如插入:int a[5]=3,4,5,6,7,8,9; 时就会出现图右边的情况

对比左边的二叉搜索树你会发现他们两的搜索效率是不一样的
最优情况下,二叉搜索树为完全二叉树,其搜索的时间复杂度为:log2N
最优情况下,二叉搜索树退化为单支树,其搜索的时间复杂度为:N
右边的这种情况叫做二叉搜索树的退化

以上是关于二叉搜索树(BinarySearchTree)的主要内容,如果未能解决你的问题,请参考以下文章

二叉搜索树(BinarySearchTree)

数据结构:二叉搜索树的增删查改

数据结构:二叉搜索树的增删查改

二叉搜索树

二叉搜索树

二叉搜索树