平衡二叉樹(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;
}

學起來很蛋疼但是學完以後完全理解旋轉以後就很容易啦,祝大家愉快!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章