AVL树删除节点deletenode函数分析 (C++实现)

 先放一下代码:(这里只放一些必要的内容。因为每个人的习惯不同,只有思路可以借鉴。请注意注释。关于旋转函数,我在上一篇帖子分析insert时贴出过,仅供参考。如果在某个地方没有理解,请先继续往下看一会,或许能找到答案。如果发现代码和解释有问题,请务必指出。

avltree deletenode(int X, avltree T)
{
	//X为删除目标的关键字值
	//info为关键字值
	position tmp;
	if (T == NULL)
		return NULL;
	else if (X < T->info)
	{
		T->left=deletenode(X, T->left);
		if (Height(T->right)-Height(T->left) >= 2)//height函数用于返回节点所处的高度
		{
			tmp = T->right;
			if(Height(tmp->right)>Height(tmp->left))
				T = SingleRotateWithRight(T);//右树单旋转
			else
				T = DoubleRotateWithRight(T);//右树双旋转

		}
	}
	else if (X > T->info)
	{
		T->right=deletenode(X, T->right);
		if (Height(T->left) - Height(T->right) >= 2)
		{
			tmp = T->left;
			if (Height(tmp->left) > Height(tmp->right))
				T = SingleRotateWithLeft(T);//左树单旋转
			else
				T = DoubleRotateWithLeft(T);//左树双旋转
		}
	}
	else
	{
		if (T->left == NULL && T->right == NULL)//若目标节点没有为叶子
		{
			delete T;
			return NULL;
		}
		else if (T->right == NULL)//若目标节点只有左子树
		{
			tmp = T->left;
			delete T;
			return tmp;
		}
		else if (T->left==NULL)//若目标节点只有右子树
		{
			tmp = T->right;
			delete T;
			return tmp;
		}
		else//若目标节点左右都有子树
		{
			if (Height(T->left) > Height(T->right))
			{
				tmp = findmax(T->left);//找出参数节点中最大的节点,返回地址
				T->info = tmp->info;
				T->left = deletenode(tmp->info,T->left);
			}
			else
			{
				tmp = findmin(T->right);//找出参数节点中最小的节点,返回地址
				T->info = tmp->info;
				T->right = deletenode(tmp->info, T->right);
			}
		}
	}
	T->height = max(Height(T->left), Height(T->right)) + 1;
	return T;
}

 

前提条件:假设现在面对的是一颗完整正确的AVL树,而我们需要对其进行删除节点的操作。

 

主要思路是运用递归的方法来进行查找,向函数中输入目标节点的关键字以及根节点地址,进行查找。

首先进入递归,函数通过这两条代码以及上面的 if条件语句 进行匹配关键字:

		T->left=deletenode(X, T->left);
		T->right=deletenode(X, T->right);

当我们成功找到了这个关键字所在的节点,进入本次递归,此时T节点代表了目标节点。(方便区分起见,我将每个目标节点T称之为T1节点)

于是进入了再往下的环节:判断该节点是否有子树。

情景一:(无子树)

假设该节点是叶,那么它既没有左子树也没有右子树,直接删除该节点,返回NULL值,回到了进入本次递归的函数位子,假设是这一段:

		T->right=deletenode(X, T->right);

那么,T1节点的父节点成功的将本该指向T1的指针指向了NULL,实现了最基础的 ‘叶删除’ 操作。

 

情景二/情景三:(一个子树)

要么只有左子树,要么只有右子树,这是两个相近的情景,所以何在一起解释。

在每一次的递归中,函数都会创建一个tmp指针用来储存可能必要的信息(你也可以对这个函数进行优化,毕竟不是每一轮递归都需要它,这或许能省下一部分空间)

假设现在我们要删除的目标节点只有一个左子树:

那么我们将tmp指向它左子树的第一个节点,并将这个地址返回,然后T1节点被删除。和情景一相同,它的父节点成功指向了返回值,也就是T1的左子树。

然而需要注意的是,这是在AVL树中的实现。按照AVL树的性质,倘若一个节点没有右子树,那么它的左子树最多也只能有一个节点。所以每个节点对应的高度就有可能发生变化。

	T->height = max(Height(T->left), Height(T->right)) + 1;

因为叶子仍然是叶子,高度仍然为 0 (假设叶子的高度均为0,当然,这只是假设罢了),于是通过返回的递归右重新测算了改变高度的节点的高度。

至此,删除节点被实现了。

 

情景四:(两个子树)

最麻烦也最难理解的部分(只是相对而言罢了)。

if (Height(T->left) > Height(T->right))

他判断了一下目标节点左树和右树哪个比较高,这里不妨先假设一下左树比较高的情景吧。

函数令tmp指向了左树中最大的那个节点,并将该节点的关键字赋予T1节点(实际上是将tmp复制给T1)。

然后进入下一轮递归

T->left = deletenode(tmp->info,T->left);

注意:这一次,查找的目标关键字变成了左树中最大的那个。

于是我们到达了第二个目标节点T2,并对它进行了删除(这是一个非常简单的删除方法,因为AVL性质规定了数值的大小,只要不停的向右走,走到没有右子树的时候,就能遇见这个最大值,所以这个T2节点一定没有右子树,情景和上面的一样)。

而之所以要找左树中最大的值,是因为进行复制之后,并不会破坏AVL树在数值上的结构:节点左树中的所有值低于节点,右树中所有值高于节点。

最后测算高度,完成了删除节点的工作。

 

旋转判定:

以上工作只是完成了 ‘ 删除节点 ’ 这一项,但事实上,删除节点之后,还必须面临打破平衡条件的可能性。

回到每一轮递归的入口:(本轮T节点将被称为T3)

		T->left=deletenode(X, T->left);
		if (Height(T->right)-Height(T->left) >= 2)//height函数用于返回节点所处的高度
		{
			tmp = T->right;
			if(Height(tmp->right)>Height(tmp->left))
				T = SingleRotateWithRight(T);//右树单旋转
			else
				T = DoubleRotateWithRight(T);//右树双旋转
		}

当我们离开递归之后,必须进行判断是否打破了平衡条件(递归实现了高度的重新测算,这也是非常棒的地方) 。

注:判断条件写了“右树-左树>2”,而并没有包括“左树-右树”的情况。原因是因为:这个路口是指向左树的,也就是说,我们将在左树中删除某个节点。二叉树本身应该保持平衡,倘若现在左树被删除节点,那么左树就不可能比右树要高,所以只需要判断这一种情况即可。在向右查找的过程中也是如此。

 

假设现在平衡被打破了。也就是说,右树比左树高了 2(其实高度差不可能超过 2 ,但我习惯写成 “>=” 罢了)。

那么该轮tmp将指向T3的右子树第一个节点,然后判断究竟是那一边打破了平衡(必然是比较高的那一边打破平衡)。

假设是tmp的左树更高,那么就需要进行双旋转,如图:(最开始想要删除的节点已经被删除了,造成了如下的情况出现)

注:

				T = DoubleRotateWithRight(T);//右树双旋转

这些旋转函数都将返回旋转之后的新根。

 

其他情况也是相同,判断是否旋转,并判断应该选择哪一种旋转。

且在每一轮的递归里,都重新计算了高度。

至此,整个函数完成了删除节点的全部流程。

 

 

猜你喜欢

转载自blog.csdn.net/Tokameine/article/details/113684828