C++实现二叉排序树

二叉排序树

视频讲解请看王道考研
链接如下
【王道计算机考研 数据结构】 https://www.bilibili.com/video/BV1b7411N798/?p=75&share_source=copy_web&vd_source=734d9fe130776e8ae82b2b5371a5f5b8

还有一个它动画做的不错,b站 蓝不过海呀
链接如下
【二叉搜索树(二叉排序树)(二叉查找树)】 https://www.bilibili.com/video/BV1uK421x7JJ/?share_source=copy_web&vd_source=734d9fe130776e8ae82b2b5371a5f5b8

主要是学习数据结构,真的做什么请直接使用map、set等容器

二叉排序树定义和性质

定义:
二叉排序树,又称二叉查找树(BST,Binary Search Tree)
一棵二叉树或者是空二叉树,或者是具有如下性质的二叉树:
左子树上所有结点的关键字均小于根结点的关键字.
右子树上所有结点的关键字均大于根结点的关键字。
左子树和右子树又各是一棵二又排序树

性质:
左子树结点值< 根结点值 < 右子树结点值
进行中序遍历(左根右),可以得到一个递增的有序序列

定义二叉排序树这一结构时,直接继承之前的二叉树
然后这里说一下,按理说排序是根据关键值key排序,我这里直接用data当关键值了,注意一下,我懒得改了

//二叉排序树
template<class ElemType>
class BST :public BiTree<ElemType>
{
	//省略一些代码……
}

二叉排序树的查找

我这里先说查找,再说插入和删除,因为插入和删除涉及到查找

二叉排序树中:左子树结点值< 根结点值< 右子树结点值,因此可以利用循环解决,不必使用递归
若树非空,目标值与根结点的值比较:
若相等,则查找成功;
若小于根结点,则在左子树上查找,否则在右子树上查找。
查找成功,返回结点指针;查找失败返回NULL

相对简单,直接上代码

template<class ElemType>
class BST :public BiTree<ElemType>
{
	//省略一些代码……
	/*功能:在本二叉排序树中查找元素e
	* 输入:
	* e:要查找的元素
	* 返回:
	* 成功:元素的结点的指针
	* 失败:NULL
	* 其它:
	*/
public:
	BTNode<ElemType>* Search(ElemType e);
}

template<class ElemType>
BTNode<ElemType>* BST<ElemType>::Search(ElemType e)
{
	BTNode<ElemType>* t = this->root;
	//若等于此结点值(说明找到),或者是空结点(说明找完了也没有找到),则结束循环
	//也就是说循环条件是,不等于此结点值并且不是空结点
	while (t != NULL && e != t->data)//好像t!=NULL要写在前面,这样t==NULL的时候就不继续往后了,否则NULL的时候还会去访问t->data
	{
		if (e < t->data)//小于,则在左子树中查找
			t = t->lchild;
		else//大于,则在左子树中查找
			t = t->rchild;
	}
	return t;
}

在后面的插入和删除中,我们需要找到“查找路径上的上一个结点”,以便于处理
将普通的查找简单改造即可实现

template<class ElemType>
class BST :public BiTree<ElemType>
{
//省略一些代码……
protected:
	/*功能:在本二叉排序树中查找元素e,并得到查找路径上的“上一个结点”
	* 输入:
	* e:要查找的元素
	* f:用来指向"上一个结点"的指针
	* 返回:
	* 成功:e元素的结点的指针
	* 失败:NULL
	* 其它:如果最后f为NULL,说明二叉树为空,或者根节点为要找的
	* 插入的时候要用
	*/
	BTNode<ElemType>* Search_f(ElemType e, BTNode<ElemType>*& f);
}

template<class ElemType>
BTNode<ElemType>* BST<ElemType>::Search_f(ElemType e, BTNode<ElemType>*& f)
{
	BTNode<ElemType>* t = this->root;
	f = NULL;
	//若等于此结点值(说明找到),或者是空结点(说明找完了也没有找到),则结束循环
	//也就是说循环条件是,不等于此结点值并且不是空结点
	while (t != NULL && e != t->data)//好像t!=NULL要写在前面,这样t==NULL的时候就不继续往后了,否则NULL的时候还会去访问t->data
	{
		f = t;//赋值f,使其为“上一个”
		if (e < t->data)//小于,则在左子树中查找
			t = t->lchild;
		else//大于,则在左子树中查找
			t = t->rchild;
	}
	return t;
}

二叉排序树的插入

若原二叉排序树为空,则直接插入结点;

否则,

​ 若关键字k小于此结点值,则插入到此结点左子树
​ 若关键字k大于此结点值,则插入到此结点右子树
若关键字k等于此结点值,原本就存在,插入失败

基本就是查找的要求,只是要得到“查找路径上的“上一个”结点,也就是我前面在查找中提到的函数Search_f

template<class ElemType>
class BST :public BiTree<ElemType>
{
//省略一些代码……
public:
	/*功能:在本二叉排序树中插入元素e
	* 输入:
	* e:要插入的元素
	* 返回:
	* 成功:true
	* 失败:false(原本存在元素e)
	* 其它:
	*/
	bool Insert(ElemType e);
}

template<class ElemType>
bool BST<ElemType>::Insert(ElemType e)
{	
	if (this->root == NULL)//如果没有根结点,直接申请
	{
		this->root =new BTNode<ElemType>(e);
		return true;
	}

	BTNode<ElemType>* t = NULL;
	BTNode<ElemType>* p = NULL;//指向查找路径上访问的最后一个结点(目标的父亲)
	//看看原本有没有,并确定插入位置
	t = this->Search_f(e, p);

	if (t != NULL)//原本存在元素e
		return false;
	else
	{
		BTNode<ElemType>* s = new BTNode<ElemType>(e);
		if (e < p->data)			
		{
			p->lchild = s;
			s->father = p;
		}
		else
		{
			p->rchild = s;
			s->father = p;
		}
		return true;
	}
}

后面我们会需要,插入完了之后不光知道是否插入成功,还要得到插入的结点指针
简单改造上面的代码

template<class ElemType>
class BST :public BiTree<ElemType>
{
//省略一些代码……
protected:
	/*功能:在本二叉排序树中插入元素e
	* 输入:
	* e:要插入的元素
	* 返回:
	* 成功:形成的结点的指针
	* 失败:NULL(原本存在元素e)
	* 其它:
	* 子类平衡二叉树中使用
	*/
	BTNode<ElemType>* Insert_p(ElemType e);
}
template<class ElemType>
BTNode<ElemType>* BST<ElemType>::Insert_p(ElemType e)
{
	if (this->root == NULL)//如果没有根结点,直接申请
	{
		this->root = new BTNode<ElemType>(e);
		return this->root;
	}

	BTNode<ElemType>* t = NULL;
	BTNode<ElemType>* p = NULL;//指向查找路径上访问的最后一个结点(目标的父亲)
	//看看原本有没有,并确定插入位置
	t = this->Search_f(e, p);

	if (t != NULL)//原本存在元素e
		return NULL;
	else
	{
		BTNode<ElemType>* s = new BTNode<ElemType>(e);
		if (e < p->data)
		{
			p->lchild = s;
			s->father = p;
		}
		else
		{
			p->rchild = s;
			s->father = p;
		}
		return s;
	}
}

二叉排序树的删除

现在开始,问题变得有点复杂了
先搜索找到目标结点:(被删除结点记为target)

if (this->root == NULL)//本来啥都没有直接返回false
	return false;

//先尝试找到e的结点
BTNode<ElemType>* target = this->Search(e);
//原本就不存在元素e,直接返回错
if (target == NULL)	return false;

然后

1、若被删除结点是叶结点

直接删除,不会破坏二叉排序树的性质

二叉排序树删除情况1
注意:

如果删除的时普通叶子节点

​ 看它是他爹的左孩子还是右孩子,然后处理他爹的对应指针,再删除它

如果删除的是根节点

​ 别忘了把类的root指针置为空

二叉排序树删除叶子结点
代码如下

//1、若被删除结点是叶结点,则直接删除
if (target->lchild == NULL && target->rchild == NULL)
{
	if (target->father == NULL)//要删除的target是根结点,而且是叶子节点,就是只有target一个
	{
		this->root = NULL;//处理一下根的指针
	}
	else//target不是根结点,如果是根节点直接删除就行
	{
		if (target->father->rchild == target)//别忘了处理父节点
			target->father->rchild = NULL;
		else
			target->father->lchild = NULL;
	}	
	delete target;
	return true;
}

2、若被删除结点只有一棵左子树或右子树

让target的子树成为target父结点的子树,替代target的位置。

二叉排序树删除情况2

同样的,请注意如果删除的就是根节点这种特殊情况

二叉排序树删除情况2注意
代码如下

//2、若结点target只有一棵左子树或右子树,则让target的子树成为target父结点的子树,替代target的位置。
//只有一棵左子树
else if (target->lchild != NULL && target->rchild == NULL)
{
	if (target->father == NULL)//target本身是根结点
	{
		this->root = target->lchild;
		target->lchild->father = NULL;
	}
	else
	{
		//如果目标是他爹的左孩子
		if (target == target->father->lchild)
		{
			target->father->lchild = target->lchild;
			target->lchild->father = target->father;
		}
		else//如果目标是他爹的右孩子
		{
			target->father->rchild = target->lchild;
			target->lchild->father = target->father;
		}			
	}		
	delete target;
	return true;
}
//只有一棵右子树
else if (target->lchild == NULL && target->rchild != NULL)
{
	if (target->father == NULL)//target本身是根结点
	{
		this->root = target->rchild;
		target->rchild->father = NULL;
	}
	else
	{
		//如果目标是他爹的左孩子
		if (target == target->father->lchild)
		{
			target->father->lchild = target->rchild;
			target->rchild->father = target->father;
		}
		else//如果目标是他爹的右孩子
		{
			target->father->rchild = target->rchild;
			target->rchild->father = target->father;
		}
	}
	delete target;
	return true;
}

3、若被删除结点有左、右两棵子树

令target的直接后继 (或直接前驱)替代target,然后从二叉排序树中删去这个直接后继 (或直接前驱),

​ 这样就转换成了第一或第二种情况。

​ target的直接后继,就是target的右子树的中序遍历第一个,也就是target的右子树一直“往左走”

​ 该节点一定没有左子树

​ target的直接前驱,就是target的左子树的中序遍历最后一个,也就是target的左子树一直“往右走”

​ 该节点一定没有右子树
二叉排序树删除情况3

注意,这时target结点不会被删除,它的直接后继(或前驱)才会被删除

我的代码中使用的是直接后继

先找到target的直接后继(记为successor),就是在target的右子树中一直“往左走”,然后把successor的数据付给target

BTNode<ElemType>* successor = NULL;//目标的直接后继
//在target的右子树中一直“往左走”
successor = target->rchild;
while (successor->lchild != NULL)//一直“往左走”
{
	successor = successor->lchild;
}

//令target的直接后继替代target
target->data = successor->data;

3.1直接后继为叶子结点

注意,直接后继可能是他爹的右孩子,这时,target右子树只有successor这一个结点,请分别处理,或删除前判断

二叉排序树删除3.1

if (successor->lchild==NULL && successor->rchild==NULL)//直接后继为叶子结点
{
	if (successor->father == target)//特殊情况,目标右子树就一个,见后面注释2
	{
		target->rchild = NULL;
	}
	else
	{
		//删去这个直接后继
		successor->father->lchild = NULL;//直接后继只可能是它爹的左孩子
	}			
}

3.2直接后继有右子树

同样的,直接后继可能是他爹的右孩子,这时,情况如图中所示,请分别处理,或删除前判断
二叉排序树删除3.2
代码如下

else//直接后继只有右子树
{
	if (successor->father == target)//特殊情况,目标右子树根节点就是后继,见后面注释4
	{
		target->rchild = successor->rchild;
		successor->rchild->father = target;
	}
	else
	{
		//删去这个直接后继
		successor->father->lchild = successor->rchild;//见后面注释3
		successor->rchild->father = successor->father;
	}
	
}

删除的完整代码

template<class ElemType>
class BST :public BiTree<ElemType>
{
public:
	/*功能:在本二叉排序树中删除元素e
	* 输入:
	* e:要删除的元素
	* 返回:
	* true:成功
	* false:失败(原本就不存在元素e)
	* 其它:会保证排序树结构不被破坏
	*/
	bool Delete(ElemType e);
}
template<class ElemType>
bool BST<ElemType>::Delete(ElemType e)
{
	if (this->root == NULL)//本来啥都没有直接返回false
		return false;

	//先尝试找到e的结点
	BTNode<ElemType>* target = this->Search(e);
	//原本就不存在元素e,直接返回错
	if (target == NULL)	return false;

	//1、若被删除结点是叶结点,则直接删除
	if (target->lchild == NULL && target->rchild == NULL)
	{
		if (target->father == NULL)//要删除的target是根结点,而且是叶子节点,就是只有target一个
		{
			this->root = NULL;//处理一下根的指针
		}
		else//target不是根结点,如果是根节点直接删除就行
		{
			if (target->father->rchild == target)//别忘了处理父节点
				target->father->rchild = NULL;
			else
				target->father->lchild = NULL;
		}	
		delete target;
		return true;
	}

	//2、若结点target只有一棵左子树或右子树,则让target的子树成为target父结点的子树,替代target的位置。
	//只有一棵左子树
	else if (target->lchild != NULL && target->rchild == NULL)
	{
		if (target->father == NULL)//target本身是根结点,见下面注释1
		{
			this->root = target->lchild;
			target->lchild->father = NULL;
		}
		else
		{
			//如果目标是他爹的左孩子
			if (target == target->father->lchild)
			{
				target->father->lchild = target->lchild;
				target->lchild->father = target->father;
			}
			else//如果目标是他爹的右孩子
			{
				target->father->rchild = target->lchild;
				target->lchild->father = target->father;
			}			
		}		
		delete target;
		return true;
	}
	//只有一棵右子树
	else if (target->lchild == NULL && target->rchild != NULL)
	{
		if (target->father == NULL)//target本身是根结点,见下面注释1
		{
			this->root = target->rchild;
			target->rchild->father = NULL;
		}
		else
		{
			//如果目标是他爹的左孩子
			if (target == target->father->lchild)
			{
				target->father->lchild = target->rchild;
				target->rchild->father = target->father;
			}
			else//如果目标是他爹的右孩子
			{
				target->father->rchild = target->rchild;
				target->rchild->father = target->father;
			}
		}
		delete target;
		return true;
	}
	
	/*3、若结点target有左、右两棵子树,
	* 则令target的直接后继(或直接前驱)替代target,然后从二叉排序树中删去这个直接后继(或直接前驱),
	* 这样就转换成了第一或第二种情况。
	* 我这里使用直接后继,也就是target的右子树的中序遍历第一个(在target的右子树中一直“往左走”)
	* 该节点一定没有左子树
	*/
	else
	{
		BTNode<ElemType>* successor = NULL;//目标的直接后继
		//在target的右子树中一直“往左走”
		successor = target->rchild;
		while (successor->lchild != NULL)//一直“往左走”
		{
			successor = successor->lchild;
		}

		//令target的直接后继替代target
		target->data = successor->data;

		if (successor->lchild==NULL && successor->rchild==NULL)//直接后继为叶子结点
		{
			if (successor->father == target)//特殊情况,目标右子树就一个,见后面注释2
			{
				target->rchild = NULL;
			}
			else
			{
				//处理相关指针
				successor->father->lchild = NULL;//直接后继只可能是它爹的左孩子
			}			
		}
		else//直接后继只有右子树
		{
			if (successor->father == target)//特殊情况,目标右子树根节点就是后继,见后面注释4
			{
				target->rchild = successor->rchild;
				successor->rchild->father = target;
			}
			else
			{
				//处理相关指针
				successor->father->lchild = successor->rchild;//见后面注释3
				successor->rchild->father = successor->father;
			}
			
		}	
		delete successor;//删去这个直接后继
		return true;
	}
}

类似的,后面会需要,删除后,知道真实删掉的结点的父结点,以及删掉的是左孩子还是右孩子
对上面的普通删除简单改造一下

template<class ElemType>
class BST :public BiTree<ElemType>
{
public:
	/*功能:在本二叉排序树中删除元素e
	* 输入:
	* e:要删除的元素
	* realDeleteFather:记录真实删掉的结点的父亲的指针
	* isLeft:记录删掉的是左孩子(true)还是右孩子(false)
	* 返回:
	* true:成功
	* false:失败(原本就不存在元素e)
	* 其它:
	* 会保证排序树结构不被破坏
	* 子类平衡二叉树中使用
	* 如果返回true,但realDeleteFather为NULL,说明删除的是根节点
	*/
	bool Delete_p(ElemType e, BTNode<ElemType>*& realDeleteFather, bool& isLeft);
}

template<class ElemType>
bool BST<ElemType>::Delete_p(ElemType e, BTNode<ElemType>*& realDeleteFather, bool& isLeft)
{
	realDeleteFather = NULL;
	isLeft = false;

	if (this->root == NULL)//本来啥都没有直接返回false
		return false;

	//先尝试找到e的结点
	BTNode<ElemType>* target = this->Search(e);
	//原本就不存在元素e,直接返回错
	if (target == NULL)	return false;

	//1、若被删除结点是叶结点,则直接删除
	if (target->lchild == NULL && target->rchild == NULL)
	{
		if (target->father == NULL)//要删除的target是根结点,而且是叶子节点,就是只有target一个
		{
			this->root = NULL;//处理一下根的指针
		}
		else//target不是根结点,如果是根节点直接删除就行
		{
			if (target->father->rchild == target)//别忘了处理父节点
			{
				isLeft = false;
				target->father->rchild = NULL;
			}				
			else
			{
				isLeft = true;
				target->father->lchild = NULL;
			}				
		}
		realDeleteFather = target->father;
		delete target;//找到真实删除的它爹
		return true;
	}

	//2、若结点target只有一棵左子树或右子树,则让target的子树成为target父结点的子树,替代target的位置。
	
	else if (target->lchild != NULL && target->rchild == NULL)//只有一棵左子树
	{
		if (target->father == NULL)//target本身是根结点,见下面注释1
		{
			this->root = target->lchild;
			target->lchild->father = NULL;
		}
		else
		{
			
			realDeleteFather = target->father;//找到真实删除的它爹
			//如果目标是他爹的左孩子
			if (target == target->father->lchild)
			{
				isLeft = true;
				target->father->lchild = target->lchild;
				target->lchild->father = target->father;
			}
			else//如果目标是他爹的右孩子
			{
				isLeft = false;
				target->father->rchild = target->lchild;
				target->lchild->father = target->father;
			}
		}		
		delete target;
		return true;
	}
	//只有一棵右子树
	else if (target->lchild == NULL && target->rchild != NULL)
	{
		if (target->father == NULL)//target本身是根结点,见下面注释1
		{
			this->root = target->rchild;
			target->rchild->father = NULL;
		}
		else
		{			
			realDeleteFather = target->father;//找到真实删除的它爹

			//如果目标是他爹的左孩子
			if (target == target->father->lchild)
			{
				isLeft = true;
				target->father->lchild = target->rchild;
				target->rchild->father = target->father;
			}
			else//如果目标是他爹的右孩子
			{
				isLeft = false;
				target->father->rchild = target->rchild;
				target->rchild->father = target->father;
			}
		}
		delete target;
		return true;
	}

	/*3、若结点target有左、右两棵子树,
	* 则令target的直接后继(或直接前驱)替代target,然后从二叉排序树中删去这个直接后继(或直接前驱),
	* 这样就转换成了第一或第二种情况。
	* 我这里使用直接后继,也就是target的右子树的中序遍历第一个(在target的右子树中一直“往左走”)
	* 该节点一定没有左子树
	*/
	else
	{
		BTNode<ElemType>* successor = NULL;//目标的直接后继
		
		//在target的右子树中一直“往左走”
		successor = target->rchild;
		while (successor->lchild != NULL)//一直“往左走”
		{
			successor = successor->lchild;
		}

		//令target的直接后继替代target
		target->data = successor->data;

		if (successor->lchild == NULL && successor->rchild == NULL)//情况1,直接后继为叶子结点
		{
			if (successor->father == target)//特殊情况,目标右子树就一个,见后面注释2
			{
				isLeft = false;
				realDeleteFather = target;

				target->rchild = NULL;
			}
			else
			{
				isLeft = true;
				realDeleteFather = successor->father;

				//删去这个直接后继
				successor->father->lchild = NULL;//直接后继只可能是它爹的左子树
			}
		}
		else//情况2,直接后继有右子树(直接后继的左子树必为空)
		{
			isLeft = true;
			realDeleteFather = successor->father;

			if (successor->father == target)//特殊情况,目标右子树根节点就是后继,见后面注释4
			{
				target->rchild = successor->rchild;
				successor->rchild->father = target;
			}
			else
			{
				//删去这个直接后继
				successor->father->lchild = successor->rchild;//见后面注释3
				successor->rchild->father = successor->father;
			}
		}
		delete successor;
		return true;
	}
}
  • 51
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
排序(Binary Search Tree,简称BST)是一种特殊的二,其中每个节点的左子中的所有键值小于节点的键值,右子中的所有键值大于节点的键值。这个特性使得二排序可以快速地进行搜索、插入和删除操作。 平衡二(Balanced Binary Tree)是一种特殊的二排序,它的左子和右子的高度之差的绝对值小于等于1。这个特性可以使得平衡二的高度保持在一个较小的范围内,从而提高了搜索、插入和删除操作的效率。 实现排序的算法通常包括插入、搜索和删除操作。当需要插入一个新的节点时,我们首先搜索中的位置,然后将新节点插入到正确的位置上,保持二排序的特性。搜索操作通过比较节点的键值和目标值来决定向左子还是右子搜索。删除操作需要考虑多种情况,包括删除的节点是叶子节点、删除的节点只有一个子节点、删除的节点有两个子节点等。具体的算法实现可以根据需要进行调整和优化。 实现平衡二的算法通常包括插入、搜索和删除操作,同时还需要进行平衡因子的计算和的调整。在插入过程中,我们首先按照二排序的规则插入新节点,然后计算每个节点的平衡因子,根据平衡因子的大小来进行的旋转操作,以保持的平衡性。具体的算法实现可以根据不同的平衡因子定义和旋转规则进行调整。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [二排序、二平衡算法](https://blog.csdn.net/shenwansan_gz/article/details/41516127)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [二排序和二平衡](https://blog.csdn.net/weixin_43829058/article/details/126978715)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cctv1324

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值