AVL树的插入和删除

1. 概述

AVL树是最早提出的自平衡二叉树,在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树。AVL树得名于它的发明者G.M. Adelson-Velsky和E.M. Landis。AVL树种查找、插入和删除在平均和最坏情况下都是O(log n),增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。本文介绍了AVL树的设计思想和基本操作。

2. 基本术语

有四种种情况可能导致二叉查找树不平衡,分别为:

(1)LL:插入一个新节点到根节点的左子树(Left)的左子树(Left),导致根节点的平衡因子由1变为2

(2)RR:插入一个新节点到根节点的右子树(Right)的右子树(Right),导致根节点的平衡因子由-1变为-2

(3)LR:插入一个新节点到根节点的左子树(Left)的右子树(Right),导致根节点的平衡因子由1变为2

(4)RL:插入一个新节点到根节点的右子树(Right)的左子树(Left),导致根节点的平衡因子由-1变为-2

针对四种种情况可能导致的不平衡,可以通过旋转使之变平衡。有两种基本的旋转:

(1)左旋转:将根节点旋转到(根节点的)右孩子的左孩子位置

(2)右旋转:将根节点旋转到(根节点的)左孩子的右孩子位置

3. AVL树的旋转操作

AVL树的基本操作是旋转,有四种旋转方式,分别为:左旋转,右旋转,左右旋转(先左后右),右左旋转(先右后左),实际上,这四种旋转操作两两对称,因而也可以说成两类旋转操作。

3.1 LL

LL情况需要右旋解决,如下图所示:

右旋部分代码如下:

BiTree SingleRight(BiTree k1)//单旋:右旋
{
    BiTree k2;
    k2 = k1->lchild;
    k1->lchild = k2->rchild;
    k2->rchild = k1;
    k1->height = MAX(Height(k1->lchild), Height(k1->rchild));
    k2->height = MAX(Height(k2->lchild), Height(k1));
    return k2;
}
这是没有删除的代码,没有父母节点,看起来简单些,不要过多考虑边界

3.2 RR

RR情况需要左旋解决,如下图所示:

左旋部分代码如下:

BiTree SingleLeft(BiTree k1)//单旋:左旋
{
	BiTree k2;

	k2 = k1->rchild;
	k1->rchild = k2->lchild;
	k2->lchild = k1;

	k1->height = MAX(Height(k1->lchild), Height(k1->rchild));
	k2->height = MAX(Height(k2->rchild), Height(k1));
	
	return k2;
}

3.3 LR

LR情况需要左右(先B左旋转,后A右旋转)旋解决,如下图所示:

BiTree LeftRight(BiTree k1)
{
	k1->lchild = SingleLeft(k1->lchild);
	return SingleRight(k1);
}

3.4 RL

RL情况需要右左旋解决(先B右旋转,后A左旋转),如下图所示:

BiTree RightLeft(BiTree k1)//双旋: 右左(从下往上  右旋-左旋)
{
	k1->rchild = SingleRight(k1->rchild);//右旋
	return SingleLeft(k1);//左旋
}

4. AVL数的插入和删除操作

(1) 插入操作:实际上就是在不同情况下采用不同的旋转方式调整整棵树

  在查找二叉树的插入的通识判断是否出现了不平衡,若是不平衡,则根据以上4中情况做相应的调整

(2) 删除操作:首先定位要删除的节点,将该节点删除(与查找二叉树一般)

然后沿着被删除的节点自下而上的调整树到根节点(需要回溯,所以要引入父母节点)

Note:

!!!由于引入了父母节点,所以在操作的时候,旋转,删除,插入都需要注意对父母节点的变化做处理

!!!!有着更为简便的删除------懒惰删除


附完整代码如下:

#include 
#include 
#include 
#include 

typedef struct tree
{
	char data;
	int hight;
	int bf;					//平衡因子:左树高度减去右树高度
	struct tree *lchild;
	struct tree *rchild;
	struct tree *parent;	//删除所用,指向父母
}Node, *Tree;

void Init(Tree *T, char data)//初始化根节点:父母为NULL
{
	*T = (Tree)malloc(sizeof(Node));
	(*T)->data = data;
	(*T)->hight = 1;
	(*T)->bf = 0;
	(*T)->parent = (*T)->lchild = (*T)->rchild = NULL;
}

int MAX(int a, int b)
{
	if (a > b)
		return a + 1;
	return b + 1;
}

int Height(Tree T)//返回高度
{
	if (T)
		return T->hight;
	return -1;
}

Tree SingleLeft(Tree k1)//单旋:左旋  注意父母的变化
{						//引入如父母这一指针,所以需要注意在旋转的时候,需要将父母也变换
	Tree k2 = k1->rchild;

	k1->rchild = k2->lchild;
	if (k2->lchild)
		k2->lchild->parent = k1;
	k2->lchild = k1;
	
	k2->parent = k1->parent;//注意这二者的顺序,若是相反,则会有k2->parent = k2;
	k1->parent = k2;
	
	k1->hight = MAX(Height(k1->lchild), Height(k1->rchild));
	k2->hight = MAX(Height(k2->lchild), Height(k2->rchild));
	k1->bf = Height(k1->lchild) - Height(k1->rchild);
	k2->bf = Height(k2->lchild) - Height(k2->rchild);

	return k2;
}

Tree SingleRight(Tree k1)//单旋:右旋
{
	Tree k2 = k1->lchild;

	k1->lchild = k2->rchild;
	if (k2->rchild)
		k2->rchild->parent = k1;
	k2->rchild = k1;
	
	k2->parent = k1->parent;
	k1->parent = k2;

	k1->hight = MAX(Height(k1->lchild), Height(k1->rchild));
	k2->hight = MAX(Height(k2->lchild), Height(k2->rchild));
	k1->bf = Height(k1->lchild) - Height(k1->rchild);
	k2->bf = Height(k2->lchild) - Height(k2->rchild);
	
	return k2;
}

Tree LeftRight(Tree k1)//双旋:左旋——右旋
{
	k1->lchild = SingleLeft(k1->lchild);
	return SingleRight(k1);
}

Tree RightLeft(Tree k1)//双旋: 右旋--左旋
{
	k1->rchild = SingleRight(k1->rchild);
	return SingleLeft(k1);
}

Tree FindMin(Tree T)//找出最小节点
{
	//错误写法如下:这样会造成对原本数据的改变,我们要做的只是找到最小的元素而已
	/*
	while (T->lchild)
		T = T->lchild;
	return T;
	*/
	Tree temp = T;
	while (temp->lchild)
		temp = temp->lchild;
	return temp;
}

Tree Insert(Tree T, Tree pre, char data)//插入,边插入边旋转调整平衡   四种对应关系
{										//由于有parent,所以需要引入pre指向前驱指针
	if (!T)
	{
		T = (Tree)malloc(sizeof(Node));
		if (!T)
		{
			printf("Can Not Init");
			return NULL;
		}
		T->hight = 1;
		T->bf = 0;
		T->data = data;
		T->parent = pre;
		T->lchild = T->rchild = NULL;
	}
	else 
	{
		if (T->data < data)
		{
			T->rchild = Insert(T->rchild, T, data);
			if (T->bf == -2)
			{
				if (data < T->rchild->data)//RL
					T = RightLeft(T);
				else if (data > T->rchild->data)//RR
						T = SingleLeft(T);
			}
		}
		if (T->data > data)
		{
			T->lchild = Insert(T->lchild, T, data);
			if (T->bf == 2)
			{
				if (data < T->lchild->data)//LL
					T = SingleRight(T);
				else if (data > T->lchild->data)//LR
						T = LeftRight(T);
			}
		}
	}

	T->hight = MAX(Height(T->lchild), Height(T->rchild));//高度和平衡因子的更新
	T->bf = Height(T->lchild) - Height(T->rchild);

	return T;
}

Tree Delete(Tree T, char data)//删除操作,比较复杂
{							  //先按照一般的查找二叉树将节点删除后,再由该节点自下而上的回溯,调整每个节点到平衡至根节点
	Tree temp, target = T, parent = NULL, grand;
	char ele;
	
	while (target)//删除操作
	{
		if (target->data < data)
			target = target->rchild;
		else if (target->data > data)
				target = target->lchild;
			else if (target->lchild && target->rchild)//找出删除节点
				{//两个孩子情况
					temp = FindMin(target->rchild);
					parent = temp->parent;//注意该语句的顺序,若是对target做了处理且target就是temp的父母,那么此时parent并不准确
					target->data = temp->data;
					//if (temp->rchild)//最小节点必然没有左子树
					//判断删除节点是其父母的左(右)子树,分别处理;
					if (temp->parent->data <= temp->data)//右子树:注意等号,由于此时temp->parent可能正是target,故可能等于
					{	
						temp->parent->rchild = temp->rchild;
						if (temp->rchild)//注意指针父母的变化
							temp->rchild->parent = temp->parent;
					}
					else //左子树
					{
						temp->parent->lchild = temp->rchild;
						if (temp->rchild)
							temp->rchild->parent = temp->parent;
					}

					free(temp);
					break;
				}
				else//只有一个孩子或者没有孩子(树叶) 
				{
					temp = target;
					parent = temp->parent;
					if (target->lchild)//有左孩子
						if (parent->data < data)//判断删除节点位置在父母节点的左还是右
						{
							parent->rchild = target->lchild;
							target->lchild->parent = parent;
						}
						else 
						{
							parent->lchild = target->lchild;
							target->lchild->parent = parent;
						}
					else 	//不用判断右孩子是否为空:若是为空,就是孩子,此时等于target->rchild也就是等于NULL
						if (parent->data < data)
						{
							parent->rchild = target->rchild;
							if (target->rchild)
								target->rchild->parent = parent;
						}
						else 
						{
							parent->lchild = target->rchild;
							if (target->rchild)
								target->rchild->parent = parent;
						}
						
					free(temp);
					break;
				}
	}
	
	while (parent)//由删除节点往上调整平衡
	{
		ele = parent->data;
		parent->hight = MAX(Height(parent->lchild), Height(parent->rchild));//更新高度和平衡因子
		parent->bf = Height(parent->lchild) - Height(parent->rchild);
		grand = parent->parent;
		if (parent->bf == -2)//四种对应情况,相对于插入来说,有着特殊情况,即bf==0的情况
		{
			if (parent->rchild->bf > 0)
			{
				parent = RightLeft(parent);
			}
			else 
			{
				parent = SingleLeft(parent);
			}
		}
		else if (parent->bf == 2)
			{
				if (parent->lchild->bf < 0)
					parent = LeftRight(parent);
				else 
					parent = SingleRight(parent);
			}
		
		if (grand)//祖父母:这是必须的,否则无法连接
		{
			if (grand->data > ele)//判断位置
				grand->lchild = parent;
			else 
				grand->rchild = parent;
			parent->parent = grand;
		}
		else
			T = parent;//注意:此时说明已经到了根节点,需要变化
		parent = parent->parent;//往上走
	}

	return T;
}

int main()
{
	Tree T;
	
	int i;
	char *s="ckbfjlaegmdh";
	
	Init(&T,'i');

	for (i = 0; i < strlen(s); i++)
		T = Insert(T, T, s[i]);
	T = Delete(T, 'a');
	//T = Delete(T, 'a');
	//T = Delete(T, 'j');
	//T = Delete(T, 'b');
	//T = Delete(T, 'b');
	
	return 0;
}

以下是没有删除的代码,没有父母节点,看起来简单些,不要过多考虑边界。

/*
 * AVL树 :每个节点的左子树和右子树的高度相差至多为1
 * :左旋   root旋转到root->right->left
 * : 右旋   root旋转到root->left->right
 * 
 * 左左:  右旋
 * 右右:  左旋
 * 右左:  先右旋再左旋  (从下往上)
 * 左右:  先左旋再右旋
*/


#include 
#include 
#include 
#include 
#define MAX(a, b) ((((a) > (b)) ? (a) : (b)) + 1)
typedef struct tree
{
	int data; 
	int height;
	struct tree *lchild; 
	struct tree *rchild;
}BiNode, *BiTree;

void Inisit(BiTree *T, int c)
{
	*T = (BiTree)malloc(sizeof(BiNode));
	(*T)->data = c;
	(*T)->rchild = (*T)->lchild = NULL;
}

int Height(BiTree T)//返回树的高度, 树为空的时候返回-1
{
	if (T)
		return T->height;
	return -1;
}

BiTree SingleLeft(BiTree k1)//单旋:左旋
{
	BiTree k2;

	k2 = k1->rchild;
	k1->rchild = k2->lchild;
	k2->lchild = k1;

	k1->height = MAX(Height(k1->lchild), Height(k1->rchild));
	k2->height = MAX(Height(k2->rchild), Height(k1));
	
	return k2;
}

BiTree SingleRight(BiTree k1)//单旋: 右旋
{
	BiTree k2;

	k2 = k1->lchild;
	k1->lchild = k2->rchild;
	k2->rchild = k1;

	k1->height = MAX(Height(k1->lchild), Height(k1->rchild));
	k2->height = MAX(Height(k2->lchild), Height(k1));

	return k2;
}

BiTree RightLeft(BiTree k1)//双旋: 右左(从下往上  右旋-左旋)
{
	k1->rchild = SingleRight(k1->rchild);//右旋
	return SingleLeft(k1);//左旋
}

BiTree LeftRight(BiTree k1)
{
	k1->lchild = SingleLeft(k1->lchild);
	return SingleRight(k1);
}

BiTree Insert(BiTree T, int c)
{
	if (!T)
	{
		T = (BiTree)malloc(sizeof(BiNode));
		if (!T)
		{
			printf("ERROR");
			return NULL;
		}
		T->lchild = T->rchild = NULL;
		T->data = c;
	}
	
	if (c > T->data)//Right
	{
		T->rchild = Insert(T->rchild, c);
		if (Height(T->rchild) - Height(T->lchild) == 2)
			if (c > T->rchild->data)//Right-Right
				T = SingleLeft(T);
			else
				T = RightLeft(T);//Right-Left
	}
	else if (c < T->data) 
	{
		T->lchild = Insert(T->lchild, c);
		if (Height(T->lchild) - Height(T->rchild) == 2)
			if (c < T->lchild->data)//Left-Left
				//T = SingleRight(T->lchild->data)
					T = SingleRight(T);
			else 
				T = LeftRight(T);//Left-Right
	}

	T->height = MAX(Height(T->lchild), Height(T->rchild));//对高度做调整
	return T;
}


int main()
{
	BiTree T;
	int i;
	int s[]={2, 6, 1, 3, 5, 7, 16, 15, 14, 13, 12, 11, 8, 9, 10};
	
	Inisit(&T, 4);

	for (i = 0; i < 15; i++)
		T = Insert(T, s[i]);

	return 0;
}


你可能感兴趣的:(算法C++描述)