平衡二叉树(AVL)结点删除操作

结点的删除向来是二叉树的操作中的难点,平衡二叉树中结点删除相对更是复杂,由于删除结点操作以后还要保持其平衡的特征,

所以给我们删除的操作带来了一些小麻烦!

之前的文章我写过一个关于AVL树的结点插入的操作,当时我在二叉树结点的定义中加入了一个height的参数,这个参数表示以该

结点为根的子树的深度。在本文中我将这个参数删掉,利用一个方法Depth(),来求该结点的深度!如果读者有兴趣,自己把这个参数的操作

加进去,难度不会很大,逻辑对了就OK。 虽然因此效率低了不少,不过难度降低了不少!

好依然首先把结点结构定义打出来:

typedef int ElemType;

typedef struct Node
{
	ElemType	  data;
	struct Node	* lchild;
	struct Node	* rchild;
}avlNode,*avlTree;

删除结点的操作依然需要分几种情况来分析:

1、假如要删除的结点是一片叶子,即叶子结点:

那么直接删除这个结点,然后从它的父节点开始判断结点被删除以后是否依然满足平衡的特性,利用递归一直判断到根结点为止,

利用递归很容易解决。

2、非叶子结点的删除:

非叶子结点的删除稍微麻烦,要分2种情况,这两种情况又各自有一个对称的情况。

a)我以某结点T的左子树是删除结点操作之后最深的一个不满足平衡特性的结点,而它的左子树的深度大于右子树的深度。即(depth(l) – depth(r) == 2)

这说明是被删除的结点是T的左子树的右子树上。这是我们就利用右双旋转将其平衡(关于旋转的方法在之前文章总有描述,这里我只把旋转的方法写出来不做过多解释)。

b)我以某结点T的左子树是删除结点操作之后最深的一个不满足平衡特性的结点,而它的右子树的深度大于左子树的深度。即(depth(r) – depth(l) == 2)

被删除的结点是T的左子树的右子树上。这是我们就利用右单旋转将其平衡。

这两个方法各自有一个对称的情况,利用左旋转解决。

我先把旋转的方法写出来:

avlTree SingleRotateWithLeft(avlTree k2)
{
	avlTree k1;
	
	k1			= k2->lchild;
	k2->lchild	= k1->rchild;
	k1->rchild	= k2;

	return k1;
}

avlTree SingleRotateWithRight(avlTree k2)
{
	avlTree k1;
	k1			= k2->rchild;
	k2->rchild	= k1->lchild;
	k1->lchild	= k2;

	return k1;
}

avlTree DoubleRotateWithLeft(avlTree k1)
{
	k1->lchild = SingleRotateWithRight(k1->lchild);
	return SingleRotateWithLeft(k1);
}

avlTree DoubleRotateWithRight(avlTree k1)
{
	k1->rchild = SingleRotateWithLeft(k1->rchild);
	return SingleRotateWithRight(k1);
}

这几个旋转才做和插入时的旋转操作一模一样。

然后就是整个删除操作的方法:

因为删除一个节点可能导致对其祖先结点的旋转,因此可以事先将要节点的祖先节点都保存下来。也就是从根结点到T1节点的路径。调整的时候是从T1的父节点开始往山(可能持续到根节点),因此其路径适合保存在栈中。这样可以依次从栈中弹出节点,判断是否需要调整。

       需要注意的是,如果T1不是叶节点,需要查找其左子树的最右节点或者右子树的最左节点T2,这时的调整是从T2的父节点开始的,因此还需要记录T1到T2的路径,也就是说,最终得到的路径是从根结点到T2的。

       调整某个节点的时候,记此节点为Tc,假设其父节点为Tp。需要将Tp的左(或者右)子树指针指向调整后得到的新Tc。

因为删除一个节点可能导致对其祖先结点的旋转,因此可以事先将要节点的祖先节点都保存下来。也就是从根结点到T1节点的路径。调整的时候是从T1的父节点开始往山(可能持续到根节点),因此其路径适合保存在栈中。这样可以依次从栈中弹出节点,判断是否需要调整。

       需要注意的是,如果T1不是叶节点,需要查找其左子树的最右节点或者右子树的最左节点T2,这时的调整是从T2的父节点开始的,因此还需要记录T1到T2的路径,也就是说,最终得到的路径是从根结点到T2的。

       调整某个节点的时候,记此节点为Tc,假设其父节点为Tp。需要将Tp的左(或者右)子树指针指向调整后得到的新Tc。

avlTree Delete(avlTree T,ElemType e)
{
	if(T == NULL)
	{
		return NULL;
	}
	else if(e < T->data)
	{
		T->lchild = Delete(T->lchild,e);
		if(Depth(T->rchild) - Depth(T->lchild) == 2)
		{
			if (Depth(T->rchild->rchild) < Depth(T->rchild->lchild))
			{
				T = DoubleRotateWithRight(T);
			} 
			else
			{
				T = SingleRotateWithRight(T);
			}
		}
	}
	else if(e > T->data)
	{
		T->rchild = Delete(T->rchild,e);
		if(Depth(T->lchild) - Depth(T->rchild) == 2)
		{
			if(Depth(T->lchild->lchild) <  Depth(T->lchild->rchild))
			{
				T = DoubleRotateWithLeft(T);
			}
			else
			{
				T = SingleRotateWithLeft(T);
			}
		}
	}
	else if(T->lchild == NULL && T->rchild == NULL)
	{
		free(T);
		return NULL;
	}
	else
	{
		avlTree tmp = FindMin(T->rchild);
		T->data = tmp->data;
		T->rchild = Delete(T->rchild,tmp->data);
	}
	return T;
}

学起来很蛋疼但是学完以后完全理解旋转以后就很容易啦,祝大家愉快!

    原文作者:平衡二叉树
    原文地址: https://blog.csdn.net/tiny39st/article/details/19820283
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞