大话数据结构学习笔记 - 查找之平衡二叉树(AVL
)及其C
实现
平衡二叉树(AVL
树)
平衡二叉树(Self-Balancing Binary Search Tree
或Height-Balanced Binary Search Tree
)是一种二叉排序树, 其中每一个节点的左子树和右子树的高度差至多等于1
. 平衡二叉树是高度平衡的二叉排序树,高度平衡即要么是空树,要么其左子树和右子树都是平衡二叉树,且左子树和右子树的高度差不超过1
- 平衡因子
BF
(Balance Factor
): 二叉树结点的左子树深度减去右子树深度的值 - 最小不平衡树: 距离插入节点最近的,且平衡因子的绝对值大于
1
的结点为根的子树
二叉树结构
typedef struct AVLTNode
{
ElementType data;
int height;
struct AVLTNode *lchild, *rchild;
}AVLTNode, *AVLTree;
旋转
节点的插入和删除都会影响到平衡二叉树的结构,进而影响到某些结点的平衡因子, 若平衡因子的取值不是{-1, 0, 1}
的话,表明该AVL
树失去平衡,则此时需要通过旋转使AVL
树重新平衡。 失衡的情况分为以下几种
上面四种都是失去平衡的AVL
树,依次记为LL, LR, RL, RR
。对于每种失衡情况,还有其他的形式
总得来说,失去平衡的AVL
树一定符合上述四种类型
LL:
LeftLeft
, 也称为左左
。 在某结点的左子树的左子树上插入或在某结点的右子树上删除一个节点后,导致该结点的平衡因子变为2
, 则该结点成为最小不平衡树的根节点,该最小不平衡树失去平衡比如上图
LL
情况,在根节点8
的左子树的左子树上插入节点1
或节点3
,都会导致以8
为根节点的树的平衡因子为2
, 故8
为根节点的树为最小不平衡树,则此时需要对该最小不平衡树的根节点进行LL
单旋RR:
RightRight
, 也称为右右
。 在某结点的右子树的右子树上插入或在某结点的左子树上删除一个节点后,导致该结点的平衡因子变为-2
, 则该结点成为最小不平衡树的根节点,该最小不平衡树失去平衡比如上图
RR
情况,在根节点8
的右子树的右子树上插入节点13
或节点15
,都会导致以8
为根节点的树的平衡因子为-2
, 故8
为根节点的树为最小不平衡树,则此时需要对该最小不平衡树的根节点进行RR
单旋LR:
LeftRight
, 也称为左右
。 在某结点的左子树的右子树上插入或在某结点的右子树上删除一个节点后,导致该结点的平衡因子变为2
, 则该结点成为最小不平衡树的根节点,该最小不平衡树失去平衡比如上图
LR
情况,在根节点8
的左子树的右子树上插入节点5
或节点7
,都会导致以8
为根节点的树的平衡因子为2
, 故8
为根节点的树为最小不平衡树,则此时需要对该最小不平衡树的根节点的左子树进行LL
单旋, 然后再对根节点进行RR
单旋LR:
RightLeft
, 也称为右左
。 在某结点的右子树的左子树上插入或在某结点的左子树上删除一个节点后,导致该结点的平衡因子变为-2
, 则该结点成为最小不平衡树的根节点,该最小不平衡树失去平衡比如上图
RL
情况,在根节点8
的右子树的左子树上插入节点9
或节点11
,都会导致以8
为根节点的树的平衡因子为-2
, 故8
为根节点的树为最小不平衡树,则此时需要对该最小不平衡树的根节点的右子树进行RR
单旋, 然后再对根节点进行LL
单旋
LL 单旋
LL
失衡情况,可通过一次旋转让最小不平衡树恢复平衡,如下图,旋转之前 k2
的平衡因子为2
, 即k2
为最小不平衡树的根节点, 然后将最小不平衡树围绕其根节点k2
旋转,旋转为右图所示。
注意:虽然是顺时针向右旋转,但不可记混,此为LL
单旋
LL
单旋代码
/*
* LL 单旋
* 返回值: 旋转后的节点
*/
AVLTNode* LL_Rotate(AVLTNode *p)
{
AVLTNode *q = p->lchild;
p->lchild = q->rchild;
q->rchild = p;
p->height = MAX(GetHeight(p->lchild), GetHeight(p->rchild)) + 1;
q->height = MAX(GetHeight(q->lchild), GetHeight(q->rchild)) + 1;
return q;
}
RR单旋
RR
失衡情况,可通过一次旋转让最小不平衡树恢复平衡,如下图,旋转之前 k1
的平衡因子为-2
, 即k1
为最小不平衡树的根节点, 然后将最小不平衡树围绕其根节点k1
旋转,旋转为右图所示。
注意:虽然是逆时针向左旋转,但不可记混,此为RR
单旋
RR单旋代码
/*
* RR 单旋
* 返回值: 旋转后的节点
*/
AVLTNode* RR_Rotate(AVLTNode *p)
{
AVLTNode *q = p->rchild;
p->rchild = q->lchild;
q->lchild = p;
p->height = MAX(GetHeight(p->lchild), GetHeight(p->rchild)) + 1;
q->height = MAX(GetHeight(q->lchild), GetHeight(q->rchild)) + 1;
return q;
}
LR双旋
LR
失衡情况, 需通过两次旋转才能让最小不平衡树恢复平衡。如下图所示,第一次旋转是围绕最小不平衡树根节点k3
的左子树根节点k1
进行的RR
单旋, 第二次是围绕最小不平衡树根节点k3
进行的LL
单旋
LR双旋代码
/*
* LR双旋: 先左后右双向旋转
* 返回值: 旋转后的节点
*/
AVLTNode* LR_Rotate(AVLTNode *p)
{
p->lchild = RR_Rotate(p->lchild);
return LL_Rotate(p);
}
RL双旋
RL
失衡情况, 需通过两次旋转才能让最小不平衡树恢复平衡。如下图所示,第一次旋转是围绕最小不平衡树根节点k1
的右子树根节点k3
进行的LL
单旋, 第二次是围绕最小不平衡树根节点k1
进行的RR
单旋
RL双旋代码
/*
* RL双旋: 先右后左双向旋转
* 返回值: 旋转后的结点
*/
AVLTNode* RL_Rotate(AVLTNode *p)
{
p->rchild = LL_Rotate(p->rchild);
return RR_Rotate(p);
}
插入
插入某结点到AVL
树
/*
* 插入节点到 AVL 树中, 并返回根节点
*/
AVLTree Insert(AVLTree T, ElementType data)
{
if(T == NULL) /* 若树为空或找到插入值的位置 */
{
T = CreateNode(data);
if(!T)
return NULL;
}
else if(data < T->data) /* 将节点插入到左子树 */
{
T->lchild = Insert(T->lchild, data); /* 递归寻找插入节点位置 */
if (GetHeight(T->lchild) - GetHeight(T->rchild) == 2) /* 判断插入后左子树与右子树的平衡因子是否为 2 */
{
if (data < T->lchild->data) /* 表示插入到第一个失衡根节点的左子树的左子树上 */
T = LL_Rotate(T);
else /* 表示插入到第一个失衡根节点的左子树的右子树上 */
T = LR_Rotate(T);
}
}
else if(data > T->data) /* 将节点插入到右子树 */
{
T->rchild = Insert(T->rchild, data); /* 递归寻找插入节点位置 */
if (GetHeight(T->rchild) - GetHeight(T->lchild) == 2) /* 判断插入后左子树与右子树的平衡因子是否为 -2 */
{
if (data > T->rchild->data) /* 表示插入到第一个失衡根节点的右子树的右子树上 */
T = RR_Rotate(T);
else /* 表示插入到第一个失衡根节点的右子树的左子树上 */
T = RL_Rotate(T);
}
}
else
printf("Fail to insert the same node!");
T->height = MAX(GetHeight(T->lchild), GetHeight(T->rchild)) + 1;
return T;
}
搜索
/*
* 查找某个值, 返回该结点
*/
AVLTNode* Search(AVLTree T, ElementType data)
{
if(!T || data == T->data)
return T;
else if(data < T->data)
return Search(T->lchild, data);
else
return Search(T->rchild, data);
}
删除
删除AVL
树中某结点
/*
* 删除某个值
*/
AVLTree Delete(AVLTree T, ElementType data)
{
/* 判断 T 是否为空 */
if(T == NULL)
return NULL;
else if(data < T->data) // 若删除的值小于当前结点所指向的值
{
T->lchild = Delete(T->lchild, data); // 在左子树中递归查找要删除的值所在的结点
if(GetHeight(T->rchild) - GetHeight(T->lchild) == 2) // 删除节点后, 判断结点是否失衡, 即平衡因子是否为 -2
{
AVLTNode *rchild = T->rchild;
/* 比较以当前结点右子树为根节点的子树的高度, 判断应该采用哪种旋转类型 */
if(GetHeight(rchild->rchild) > GetHeight(rchild->lchild))
T = RR_Rotate(T);
else
T = RL_Rotate(T);
}
}
else if(data > T->data) // 若删除的值小于当前结点所指向的值
{
T->rchild = Delete(T->rchild, data); // 在右子树中递归查找要删除的值所在的结点
if(GetHeight(T->lchild) - GetHeight(T->rchild) == 2) // 删除节点后, 判断结点是否失衡, 即平衡因子是否为 2
{
AVLTNode *lchild = T->lchild;
/* 比较以当前结点左子树为根节点的子树的高度, 判断应该采用哪种旋转类型 */
if(GetHeight(lchild->lchild) > GetHeight(lchild->rchild))
T = LL_Rotate(T);
else
T = LR_Rotate(T);
}
}
else // 找到删除节点
{
if(T->lchild && T->rchild) /* 左右子树均不空 */
{
// 若左子树高度高于右子树, 则选取待删除节点的前驱节点取代待删除节点
if(GetHeight(T->lchild) > GetHeight(T->rchild))
{
AVLTNode *tmp = GetMax(T->lchild);
T->data = tmp->data;
T->lchild = Delete(T->lchild, tmp->data);
}
else // 否则选取待删除节点的后继节点取代待删除节点
{
AVLTNode *tmp = GetMin(T->rchild);
T->data = tmp->data;
T->rchild = Delete(T->rchild, tmp->data);
}
}
else // 若待删除节点只有左子树或右子树, 或为叶子节点
{
AVLTNode *tmp = T;
T = T->lchild != NULL ? T->lchild : T->rchild;
free(tmp);
}
}
return T;
}
测试
int arr[]= {3,2,1,4,5,6,7,16,15,14,13,12,11,10,8,9}, ilen = LENGTH(arr);
AVLTree tree = NULL;
for(int i = 0; i < ilen; i++)
tree = Insert(tree, arr[i]);
PreOrder(tree);
InOrder(tree);
输出
== 前序遍历: 7 4 2 1 3 6 5 13 10 9 8 11 15 14 16
== 中序遍历: 1 2 3 4 5 6 7 8 9 10 11 13 14 15 16
关于上述操作用到的其他函数,都在源代码中实现,故可通过查阅源代码了解
时间复杂度
- 插入、删除和查找的时间复杂度都为 O(logn) O ( l o g n )
源代码
结语
平衡二叉树作为查找算法还是很重要滴,并且有了AVL
树的知识后,对于后面的B-Tree
以及B+Tree
和红黑树都有很大的帮助。一定要仔细理解,并且在学习每个操作的过程中,动手模拟操作过程,有助于理解以及加深印象。加油, Fighting