AVL树是可以在插入、删除节点之后进行自平衡的树,平衡的定义是结点的左子树和右子树的深度差不超过1,也就:
| Deep(LeftChild) - Deep(RightChild)| <= 1,整棵树的平衡也就是树的每个结点都是满足上述情况。那么下面我们要解决问题就是当树在插入数据和删除数据的时候,如果树不平衡了我们该怎么办。
上面就是我们在插入或者删除的时候可能导致的树的不平衡的所有情况,不信可以自己去尝试。下面我们进行分类讨论:
情况1和4是最简单的,我们只需要对结点Z进行一个左右的旋转即可,旋转和红黑树的旋转一样。目的是降低子树的高度差。
情况2和情况3需要先对y进行左或者右旋转,变成情况1和4,然后再照着情况1,和4处理。
虽然情况我们说明白了,但是我们应该要注意到这么一个问题,当我们插入或者删除数据的时候,可能并不是引起附近的结点不平衡,有可能是引起的很远的结点的不平衡,例如:
当我们插入L的时候,结果引起的是根节点E的不平衡,所以现在我们要面临的一个问题就是如何判断当前是出于什么情况。下面我提供一种思路:
我们可以这样看,其实每次插入删除的时候引起的不平衡的结点都是插入、删除的结点的父节点,不肯是其父节点的兄弟结点之类,而且我们总是通过不平衡结点加上往下的两个结点,来判断出事情况几。往下的结点怎么选取呢?
选取的规定就是,看刚插入或者删除的结点是在不平衡结点的左子树还是右子树,加入在左子树,那么继续判断刚插入或者删除的结点在该左子树的左子树还是右子树,只需要这么两步就可以判断出情况。代码如下:
//树的自平衡函数,传入的参数是新插入的节点
PAVLTree AVLTreeBalance(PAVLTree *Node)
{
if(!(*Node))
return NULL;
PAVLTree *BFNode;
PAVLTree Temp = *Node;
PAVLTree Parent;
//0--left,1--right
int LeftOrRight = -1;
PAVLTree find = (*Node);
BFNode = Node;
//查找到不平衡的结点
while(find)
{
if(find->balanceFactor == -2 || find->balanceFactor == 2)
break;
find = find->Parent;
}
//当前树平衡
if(!find)
{
while(Temp->Parent)
Temp = Temp->Parent;
return Temp;
}
(*BFNode) = find;
//如果存在父节点
if((*BFNode)->Parent)
{
Parent = (*BFNode)->Parent;
if(Parent->left == (*BFNode))
LeftOrRight = 0;
else
LeftOrRight = 1;
}
else
Parent = NULL;
//辅助判断是哪种情况的变量
int Second,Third;
Second = searchKey((*BFNode),Temp->key,&find);
if(Second == 0)
Third = searchKey((*BFNode)->left,Temp->key,&find);
else
Third = searchKey((*BFNode)->right,Temp->key,&find);
//情况一,左左左
if(Second == 0 && Third == 0)
(*BFNode) = rightRotate((*BFNode));
//情况二,右右右
else if(Second == 1 && Third == 1)
(*BFNode) = leftRotate((*BFNode));
//情况三,左左右
else if(Second == 0 && Third == 1)
{
(*BFNode)->left = leftRotate((*BFNode)->left);
(*BFNode) = rightRotate((*BFNode));
}
//情况四,右右左
else
{
(*BFNode)->right = rightRotate((*BFNode)->right);
(*BFNode) = leftRotate((*BFNode));
}
if(LeftOrRight != -1)
{
if(LeftOrRight == 0)
Parent->left = (*BFNode);
else
Parent->right = (*BFNode);
}
//返回根节点
while(Temp->Parent)
Temp = Temp->Parent;
return Temp;
}
上面是AVL树实现的最关键的函数,也就是自平衡函数,在删除和插入的时候都需要调用。那么有了这个函数,插入就显得非常的简单,下面是代码:
//插入操作
PAVLTree insertKey(PAVLTree *root,char key,int val)
{
//为空,表示应该插入的位置
if(!(*root))
{
(*root) = (PAVLTree)malloc(sizeof(AVLTree));
(*root)->key = key;
//(*root)->Parent = (*root);
(*root)->val = val;
(*root)->left = (*root)->right = NULL;
//Balance factor and primary value is 0
(*root)->balanceFactor = 0;
return (*root);
}
//如果已经存在那么修正值就行:
else if((*root)->key == key)
{
(*root)->key = key;
return (*root);
}
//在左子树
else if((*root)->key > key)
insertKey(&(*root)->left,key,val)->Parent = (*root);
//在右子树
else if((*root)->key < key)
insertKey(&(*root)->right,key,val)->Parent = (*root);
CalculateBF(*root);
return (*root);
}
删除操作
删除的策略是:1.叶子结点那么直接删除。
2.不是叶子结点,但是只有一个儿子结点,那么删除该节点,用其儿子结点顶替
3.不是叶子结点,但是有两个儿子结点,那么查找该树的中序遍历情况下,要删除节点的下一个结点来顶替,(其实就是右子树的最小值结点)
//删除函数
PAVLTree deleteKey(PAVLTree *root,char key)
{
PAVLTree DeleteNode = (PAVLTree)malloc(sizeof(AVLTree));
//查找到,那么删除
if((*root)->key == key)
{
//为了调试少写一点
DeleteNode->key = (*root)->key;
DeleteNode->val = (*root)->val;
//叶子节点,直接删除
if(!(*root)->left && !(*root)->right)
(*root) = NULL;
else if(!(*root)->left)
(*root) = (*root)->right;
else if(!(*root)->right)
(*root) = (*root)->left;
//最复杂的情况需要寻找中序遍历的下一个点来顶替(右子树的最小结点)
else
{
PAVLTree Temp = deleteMin(&(*root)->right);
(*root)->key = Temp->key;
(*root)->val = Temp->val;
}
}
else if((*root)->key > key)
DeleteNode = deleteKey(&(*root)->left,key);
else
DeleteNode = deleteKey(&(*root)->right,key);
CalculateBF(*root);
AVLTreeBalance(root);
return DeleteNode;
}
Tips:因为AVL树操作中有许多的操作需要向上进行,所以数据结构这样设计可能会最比较方便:
typedef struct treeNode{
char key;
int val;
//平衡因子,-1,0,1为合理值
int balanceFactor;
struct treeNode *left;
struct treeNode *right;
//父节点,方便向上的操作进行
struct treeNode *Parent;
}AVLTree,*PAVLTree;
一些辅助函数:
//左旋转操作
PAVLTree leftRotate(PAVLTree Node)
{
PAVLTree Temp = Node->right;
Node->right = Temp->left;
Temp->left = Node;
Temp->Parent = Node->Parent;
if(Temp->left)
Temp->left->Parent = Temp;
if(Temp->right)
Temp->right->Parent = Temp;
if(Node->left)
Node->left->Parent = Node;
if(Node->right)
Node->right->Parent = Node;
return Temp;
}
//右旋转操作
PAVLTree rightRotate(PAVLTree Node)
{
PAVLTree Temp = Node->left;
Node->left = Temp->right;
Temp->right = Node;
Temp->Parent = Node->Parent;
if(Temp->left)
Temp->left->Parent = Temp;
if(Temp->right)
Temp->right->Parent = Temp;
if(Node->left)
Node->left->Parent = Node;
if(Node->right)
Node->right->Parent = Node;
return Temp;
}
//获得树的深度
int GetDeepth(PAVLTree Node)
{
if(!Node)
return 0;
int left = GetDeepth(Node->left);
int right = GetDeepth(Node->right);
return (left > right ? (left+1) : (right+1));
}
//重新计算树的平衡因子,但是只会回溯插入节点的沿途父节点,始终以左节点层数减去右节点层数
void CalculateBF(PAVLTree Node)
{
//根节点的父节点就终止
if(!Node)
return;
Node->balanceFactor = GetDeepth(Node->left)-GetDeepth(Node->right);
CalculateBF(Node->Parent);
}
旋转操作中加入了父结点的改变的步骤,所以相比于红黑树代码稍复杂一点。