1. 平衡二叉树
平衡二叉树,又称AVL树。它或者是一棵空树,或者是具有下列性质的树:
1) 左子树和右子树深度差的绝对值不超过1;
2) 左子树和右子树都是二叉平衡树。
平衡二叉树采用二叉链表的结构进行存储。结构体中增加结点的高度,用以计算结点的平衡因子。结点高度定义:空结点的高度为0;非空结点的高度为以该结点为根结点的树的高度。
二叉链表:
typedef int TElemType;
typedef struct BiNode
{
TElemType data;
struct BiNode *lchild;
struct BiNode *rchild;
int height;
}BiNode, *BiTree;
结点高度:
int GetHeight(BiTree T)
{
if (T)
return T->height;
return 0;
}
当在树中插入(删除)一个结点时,检查树是否因插入(删除)操作而失衡,若失衡,则找出其中的最小非平衡二叉树,对最小非平衡二叉树进行调整,以达到新的平衡。
对最小非平衡树进行调整的操作是旋转,共有4种旋转方式LL型,LR型,RL型,RR型,分别介绍如下:
1) LL型(单次右旋)
当根结点左子树的左子树中的节点导致根结点的平衡因子为2时,采用LL型旋转进行调整。图示为两种需进行单次右旋的非平衡树。
LL型旋转,也就是单次右旋,将根结点的左孩子作为新的根结点,根结点左孩子的右子树作为之前根结点的左子树。图示如下:
注意:旋转之后,整个树中只有结点k1,k2的高度发生变化,而x,y,z三棵子树中所有结点的高度均未发生变化。
代码:
bool LLRotate(BiTree *T)
{
BiTree lc;
lc = (*T)->lchild;
(*T)->lchild = lc->rchild;
lc->rchild = (*T);
//注意要更新结点的高度。整个树中只有*T的左子树和lc的右子树发生了变化,所以只需更改这两棵树的高度。
(*T)->height = max(GetHeight((*T)->lchild), GetHeight((*T)->rchild)) + 1;
lc->height = max(GetHeight(lc->lchild), GetHeight(lc->rchild)) + 1;
*T = lc;
return true;
}
2) RR型(单次左旋)
当根结点右子树的右子树中的节点导致根结点的平衡因子为-2时,采用RR型旋转进行调整。图示为两种需进行单次左旋的非平衡树。RR型旋转与LL型旋转相对称。
RR型旋转,也就是单次左旋,将根结点的右孩子作为新的根结点,根结点右孩子的左子树作为之前根结点的右子树。图示如下:
注意:旋转之后,整个树中只有结点k1,k2的高度发生变化,而x,y,z三棵子树中所有结点的高度均未发生变化。
代码:
bool RRRotate(BiTree *T)
{
BiTree rc;
rc = (*T)->rchild;
(*T)->rchild = rc->lchild;
rc->lchild = (*T);
//注意要更新结点的高度。整个树中只有*T的右子树和lc的左子树发生了变化,所以只需更改这两棵树的高度。
(*T)->height = max(GetHeight((*T)->lchild), GetHeight((*T)->rchild)) + 1;
rc->height = max(GetHeight(rc->lchild), GetHeight(rc->rchild)) + 1;
*T = rc;
return true;
}
3) LR型(先单次左旋,再单次右旋)
当根结点左子树的右子树中的节点导致根结点的平衡因子为2时,采用LR型旋转进行调整。图示为两种需进行LR型旋转的非平衡树。
LR型旋转是先以根结点的左孩子为中心进行单次左旋,再以根结点为中心进行单次右旋。图示如下:
代码:
bool LRRotate(BiTree *T)
{
RRRotate(&((*T)->lchild));
LLRotate(T);
return true;
}
4) RL型(先单次右旋,再单次左旋)
当根结点右子树的左子树中的节点导致根结点的平衡因子为-2时,采用RL型旋转进行调整。图示为两种需进行RL型旋转的非平衡树。RL型旋转与LR型旋转相对应。
RL型旋转是先以根结点的右孩子为中心进行单次右旋,再以根结点为中心进行单次左旋。图示如下:
代码:
bool RLRotate(BiTree *T)
{
LLRotate(&((*T)->rchild));
RRRotate(T);
return true;
}
3. 插入操作
bool AVLInsert(BiTree *T, TElemType key)
{
BiTree t;
//如果当前查找的根结点为空树,表明查无此结点,故插入结点。
if (!*T)
{
t = (BiTree)malloc(sizeof(BiNode));
t->data = key;
t->height = 1;
t->lchild = NULL;
t->rchild = NULL;
*T = t;
return true;
}
//已有此结点,不再插入。
else if (key == (*T)->data)
{
return false;
}
//在左子树中递归插入。
else if (key < (*T)->data)
{
if (!AVLInsert(&((*T)->lchild), key))
return false;
else
{
//插入成功,修改树的高度。
(*T)->height = max(GetHeight((*T)->lchild), GetHeight((*T)->rchild)) + 1;
//已在*T的左子树插入结点key,判断是否需要进行旋转以保持二叉平衡树的特性。
if (2 == GetHeight((*T)->lchild) - GetHeight((*T)->rchild))
{
//在左子树的左子树中插入结点。
if (GetHeight((*T)->lchild->lchild) > GetHeight((*T)->lchild->rchild))
{
LLRotate(T);
}
//在左子树的右子树中插入结点。
else
{
LRRotate(T);
}
}
return true;
}
}
//在右子树中递归插入。
else // (key > (*T)->data)
{
if (!AVLInsert(&(*T)->rchild, key))
return false;
else
{
//插入成功,修改树的高度。
(*T)->height = max(GetHeight((*T)->lchild), GetHeight((*T)->rchild)) + 1;
//已在*T的右子树插入结点key,判断是否需要进行旋转以保持二叉平衡树的特性。
if (-2 == GetHeight((*T)->lchild) - GetHeight((*T)->rchild))
{
//在右子树的左子树中插入结点。
if (GetHeight((*T)->rchild->lchild) > GetHeight((*T)->rchild->rchild))
{
RLRotate(T);
}
//在右子树的右子树中插入结点。
else
{
RRRotate(T);
}
}
return true;
}
}
}
4. 删除操作
算法思想:
利用递归找到要删除的结点,并删除结点,删除结点的方法与平衡二叉树的方法相似,不同之处在于:当前结点有左右子树时,如果平衡因子为0或1,则找当前结点的直接前驱,如果平衡因子为-1,则找当前结点的直接后继。删除结点后,递归判断是否因为删除结点而导致二叉树失衡,如果失衡,采用上面介绍的方法进行调整。
代码:
bool AVLDelete(BiTree *T, TElemType key)
{
BiTree pre, post;
//没有找到该结点
if (!*T)
return false;
//找到结点,将它删除
else if (key == (*T)->data)
{
//待删除节点为叶子结点
if (!(*T)->lchild && !(*T)->rchild)
*T = NULL;
//待删除结点只有右孩子
else if (!(*T)->lchild)
*T = (*T)->rchild;
//待删除结点只有左孩子
else if (!(*T)->rchild)
*T = (*T)->lchild;
//待删除结点既有左孩子,又有右孩子
else
{
/*当待删除结点*T的左子树的高度>右子树的高度时,用*T的前驱结点pre代替*T,再将结点pre从树中删除,从而保证删除结点后的树仍为二叉平衡树*/
if (GetHeight((*T)->lchild) > GetHeight((*T)->rchild))
{
//寻找前驱结点pre
pre = (*T)->lchild;
while (pre->rchild)
{
pre = pre->rchild;
}
//用pre替换*T
(*T)->data = pre->data;
/*删除节点pre,虽然能够确定pre所属最小子树的根结点为&pre,但是不采用AVLDelete(&pre,pre->data)删除pre。目的是方便递归更改节点的高度*/
AVLDelete(&((*T)->lchild), pre->data);
}
/*当待删除结点*T的左子树的高度<=右子树的高度时,用*T的后继结点post代替*T,再将结点post从树中删除,从而保证删除结点后的树仍为二叉平衡树*/
else
{
//寻找后继节点post
post = (*T)->rchild;
while (post->lchild)
post = post->lchild;
//用post替换*T
(*T)->data = post->data;
/*删除节点post,虽然能够确定post所属最小子树的根结点为&post,但是不采用AVLDelete(&post,post->data)删除post。目的是方便递归更改节点的高度*/
AVLDelete(&((*T)->rchild), post->data);
}
}
return true;
}
//在左子树中递归删除
else if (key < (*T)->data)
{
if (!AVLDelete(&((*T)->lchild), key))
return false;
else
{
//删除成功,修改树的高度
(*T)->height = max(GetHeight((*T)->lchild), GetHeight((*T)->rchild)) + 1;
//已经在*T的左子树删除结点key,判断是否需要进行旋转以保持二叉平衡树的特性
if (-2 == GetHeight((*T)->lchild) - GetHeight((*T)->rchild))
{
if (GetHeight((*T)->rchild->lchild) > GetHeight((*T)->rchild->rchild))
{
RLRotate(T);
}
else
{
RRRotate(T);
}
}
return true;
}
}
//在右子树中递归删除
else
{
if (!AVLDelete(&((*T)->rchild), key))
return false;
else
{
//删除成功,修改树的高度
(*T)->height = max(GetHeight((*T)->lchild), GetHeight((*T)->rchild)) + 1;
//已经在*T的右子树删除结点key,判断是否需要进行旋转以保持二叉平衡树的特性
if (2 == GetHeight((*T)->lchild) - GetHeight((*T)->rchild))
{
if (GetHeight((*T)->lchild->lchild) > GetHeight((*T)->lchild->rchild))
{
LLRotate(T);
}
else
{
LRRotate(T);
}
}
return true;
}
}
}
关键点:
1. 当待删除结点*T既有左子树又有右子树时,那么在左子树高度大于右子树高度时,用结点*T的前驱结点pre替换*T,之后再删除前驱结点pre;在右子树高度大于左子树高度时,用结点*T的后继节点post替换结点*T,之后再删除后继结点post。这样可以保证在删除操作之后,树在不进行旋转操作的情况下仍为二叉平衡树。
2. 在删除前驱结点pre和后继结点post时,使用AVLDelete(&((*T)->lchild), pre->data)和AVLDelete(&((*T)->rchild), post->data)。这样可以保证被删除结点pre和post的父节点直至根结点的所有结点的高度都会被递归修正一次。