平衡二叉树,即对于一颗二叉查找树,它的任意一个结点的左子树与右子树高度之差小于1,这样的树我们称之为平衡二叉树。当一个树为平衡二叉树时,对它进行插入运算或者删除运算,都有可能会造成树的失衡,这时,我们就要对其进行调整,使他重新成为一颗平衡二叉树。
判断一颗树是否失衡
我们要判断一棵二叉查找树是否平衡,便要对其进行遍历,若存在结点,使得它的左子树高度和右子树高度之差大于1,则树不平衡。为了比较时方便,我们这样定义树的结构。
struct AVLNode
{
struct AVLNode *lchild // 左儿子
struct AVLNode *rchild // 右儿子
int height; // 高度
};
我们在结构体中加入了height这一变量,记录当前结点的高度,并规定,空节点的高度为-1,深度最深的叶子结点的高度最小,根节点的高度最大。
代码分析
#include<stdio.h>
#include<stdlib.h>
// 树的数据类型
typedef int TDataType;
// 树的结点
typedef struct SAVLNode
{
TDataType data;
struct SAVLNode *lchild;
struct SAVLNode *rchild;
int height;
}SAVLNode, *SAVLNodePtr;
// 判断两个数的大小,返回较大的数
int
Max(int a, int b);
// 返回当前结点的高度
int
Height(SAVLNodePtr pAVLNode);
// 对pAVLNode的左儿子的左子树进行了一次插入。返回新的根节点
SAVLNodePtr
SingleRotate2Right(SAVLNodePtr *pAVLNode);
// 对pAVLNode的右儿子的右子树进行了一次插入。返回新的根节点
SAVLNodePtr
SingleRotate2Left(SAVLNodePtr *pAVLNode);
// 对pAVLNode的左儿子的右子树进行了一次插入。返回新的根节点
SAVLNodePtr
DoubleRotate2Right(SAVLNodePtr *pAVLNode);
// 对pAVLNode的右儿子的左子树进行了一次插入。返回新的根节点
SAVLNodePtr
DoubleRotate2Left(SAVLNodePtr *pAVLNode);
// 在树pAVLNode插入x。返回值,返回新的根节点
SAVLNodePtr
Insert(TDataType x, SAVLNodePtr *pAVLNode);
// 创造一棵树,返回根节点
SAVLNodePtr
Create(TDataType *array, int n);
// 打印树,进行测试
void
Print(SAVLNodePtr pAVLNode);
// 删除值为x的结点。返回根节点
SAVLNodePtr
Delete(SAVLNodePtr *pAVLNode, TDataType elem);
int main()
{
TDataType array[] = {4, 3, 5, 1, 2};
int i = 5;
SAVLNodePtr root = NULL, pAVLNode = NULL;
root = Create(&array, i);
Print(root);
printf("\n");
for(int j = 1; j < i - 1; ++j)
{
Delete(&root, array[j]);
Print(root);
printf("\n");
}
return 0;
}
// 判断两个数的大小,返回较大的数
int
Max(int a, int b)
{
return a >= b? a : b;
}
// 返回当前结点的高度
int
Height(SAVLNodePtr pAVLNode)
{
if(pAVLNode == NULL)
return -1;
else
return pAVLNode->height;
}
// 对pAVLNode的左儿子的左子树进行了一次插入。返回新的根节点
SAVLNodePtr
SingleRotate2Right(SAVLNodePtr *pAVLNode)
{
if(*pAVLNode == NULL) // 检查空指针
return NULL;
// 旋转
SAVLNodePtr newRoot = (*pAVLNode)->lchild;
(*pAVLNode)->lchild = newRoot->rchild;
newRoot->rchild = *pAVLNode;
// 高度变化
(*pAVLNode)->height = Max(Height((*pAVLNode)->lchild), Height((*pAVLNode)->rchild)) + 1;
newRoot->height = Max(Height(newRoot->lchild), (*pAVLNode)->height) + 1;
return newRoot;
}
// 对pAVLNode的右儿子的右子树进行了一次插入。返回新的根节点
SAVLNodePtr
SingleRotate2Left(SAVLNodePtr *pAVLNode)
{
if(*pAVLNode == NULL) // 检查空指针
return NULL;
// 旋转
SAVLNodePtr newRoot = (*pAVLNode)->rchild;
(*pAVLNode)->rchild = newRoot->lchild;
newRoot->lchild = *pAVLNode;
// 高度变化
(*pAVLNode)->height = Max(Height((*pAVLNode)->lchild), Height((*pAVLNode)->rchild)) + 1;
newRoot->height = Max(Height(newRoot->rchild), (*pAVLNode)->height) + 1;
return newRoot;
}
// 对pAVLNode的左儿子的右子树进行了一次插入。返回新的根节点
SAVLNodePtr
DoubleRotate2Right(SAVLNodePtr *pAVLNode)
{
if(*pAVLNode == NULL)
return NULL;
// 先将在左儿子的右子树插入的情况转换为在左儿子的左子树插入的情况
(*pAVLNode)->lchild = SingleRotate2Left(&(*pAVLNode)->lchild);
// 直接调用函数进行平衡即可
return SingleRotate2Right(pAVLNode);
}
// 对pAVLNode的右儿子的左子树进行了一次插入。返回新的根节点
SAVLNodePtr
DoubleRotate2Left(SAVLNodePtr *pAVLNode)
{
if(*pAVLNode == NULL)
return NULL;
// 先将在有儿子的左子树插入的情况转换为在右儿子的右子树插入的情况
(*pAVLNode)->rchild = SingleRotate2Right(&(*pAVLNode)->rchild);
// 直接调用函数进行平均即可
return SingleRotate2Left(pAVLNode);
}
// 在树pAVLNode插入x。返回值,返回新的根节点
SAVLNodePtr
Insert(TDataType x, SAVLNodePtr *pAVLNode)
{
if(*pAVLNode == NULL)
{
*pAVLNode = (SAVLNodePtr)malloc(sizeof(SAVLNode)); // 开辟堆空间
if(*pAVLNode == NULL) // 开辟堆空间失败
return NULL;
// 初始化
(*pAVLNode)->data = x;
(*pAVLNode)->height = 0;
(*pAVLNode)->lchild = (*pAVLNode)->rchild = NULL;
return *pAVLNode;
}
else if(x < (*pAVLNode)->data)
{
(*pAVLNode)->lchild = Insert(x, &(*pAVLNode)->lchild);
if(Height((*pAVLNode)->lchild) - Height((*pAVLNode)->rchild) == 2)
{
if(x < (*pAVLNode)->lchild->data)
*pAVLNode = SingleRotate2Right(pAVLNode);
else
*pAVLNode = DoubleRotate2Right(pAVLNode);
}
}
else if(x > (*pAVLNode)->data)
{
(*pAVLNode)->rchild = Insert(x, &(*pAVLNode)->rchild);
if(Height((*pAVLNode)->rchild) - Height((*pAVLNode)->lchild) == 2)
{
if(x > (*pAVLNode)->rchild->data)
*pAVLNode = SingleRotate2Left(pAVLNode);
else
*pAVLNode = DoubleRotate2Left(pAVLNode);
}
}
else return NULL; // 相等的情况,即无法插入
(*pAVLNode)->height = Max(Height((*pAVLNode)->lchild), Height((*pAVLNode)->rchild)) + 1;
return *pAVLNode;
}
// 创造一棵树,返回根节点
SAVLNodePtr
Create(TDataType *array, int n)
{
SAVLNodePtr pAVLTree = NULL;
for(int i = 0; i < n; ++i)
Insert(array[i], &pAVLTree);
return pAVLTree;
}
// 打印树,进行测试
void
Print(SAVLNodePtr pAVLNode)
{
if(pAVLNode != NULL)
{
Print(pAVLNode->lchild);
printf("%d ", pAVLNode->data);
Print(pAVLNode->rchild);
}
}
// 删除值为x的结点。返回根节点
SAVLNodePtr
Delete(SAVLNodePtr *pAVLNode, TDataType elem)
{
if(*pAVLNode == NULL)
return NULL;
// 目标在左树
if(elem < (*pAVLNode)->data)
{
(*pAVLNode)->lchild = Delete(&(*pAVLNode)->lchild, elem);
// 树有不平衡的情况
if(Height((*pAVLNode)->rchild) - Height((*pAVLNode)->lchild) == 2)
{
if(Height((*pAVLNode)->rchild->rchild) > Height((*pAVLNode)->rchild->lchild))
*pAVLNode = SingleRotate2Left(pAVLNode);
else
*pAVLNode = DoubleRotate2Left(pAVLNode);
}
}
// 目标在右树
else if(elem > (*pAVLNode)->data)
{
(*pAVLNode)->rchild = Delete(&(*pAVLNode)->rchild, elem);
// 树有不平衡的情况
if(Height((*pAVLNode)->lchild) - Height((*pAVLNode)->rchild) == 2)
{
if(Height((*pAVLNode)->lchild->lchild) > Height((*pAVLNode)->lchild->rchild))
*pAVLNode = SingleRotate2Right(pAVLNode);
else
*pAVLNode = DoubleRotate2Right(pAVLNode);
}
}
// 找到了目标
else
{
// 目标结点两个儿子都在
if((*pAVLNode)->lchild != NULL && (*pAVLNode)->rchild != NULL)
{
// 移动高度高的子树,避免因移动结点导致树不平衡
if(Height((*pAVLNode)->lchild) > Height((*pAVLNode)->rchild))
{
SAVLNodePtr temp = (*pAVLNode)->lchild;
// 找到左子树中最大的结点
while(temp->rchild)
temp = temp->rchild;
// 将左子树中最大结点的值赋给当前结点
(*pAVLNode)->data = temp->data;
// 将左子树中最大结点删除,达到偷天换日的效果
(*pAVLNode)->lchild = Delete(&(*pAVLNode)->lchild, temp->data);
}
else
{
SAVLNodePtr temp = (*pAVLNode)->rchild;
// 找到右子树中最小的结点
while(temp->lchild)
temp = temp->lchild;
// 将右子树中最小结点的值赋给当前结点
(*pAVLNode)->data = temp->data;
// 将右子树中最小结点删除,达到偷天换日的效果
(*pAVLNode)->rchild = Delete(&(*pAVLNode)->rchild, temp->data);
}
}
else
{
if((*pAVLNode)->lchild != NULL)
{
SAVLNodePtr temp = (*pAVLNode)->lchild;
(*pAVLNode)->data = temp->data;
(*pAVLNode)->lchild = temp->lchild;
(*pAVLNode)->rchild = temp->rchild;
free(temp);
temp = NULL;
}
else if((*pAVLNode)->rchild != NULL)
{
SAVLNodePtr temp = (*pAVLNode)->rchild;
(*pAVLNode)->data = temp->data;
(*pAVLNode)->lchild = temp->lchild;
(*pAVLNode)->rchild = temp->rchild;
free(temp);
temp = NULL;
}
else
*pAVLNode = NULL;
}
}
return *pAVLNode;
}
对平衡二叉树进行插入操作时,若树失衡,则我们要找到导致二叉树失衡的高度最小的结点A,而该节点的子树都是平衡的。也就是说,我们只要对以该结点为根的子树进行操作即可,只要该子树恢复了平衡,那么整棵树也就恢复了平衡。
设插入导致树失衡的情况有一下四种:
- 对A的左儿子的左子树进行一次插入运算
- 对A的左儿子的右子树进行一次插入运算
- 对A的右儿子的左子树进行一次插入运算
- 对A的右儿子的右子树进行一次插入运算
我们可以观察出,第一种情况和第四种情况是对称的;第二种情况和第三种情况是对称的,因此我们可以将其分类为两种情况。
对于第一种情况和第四种情况,只需要对其进行一次旋转操作即可。对于第二种和第三种情况,要稍微复杂了一些,需要对其进行两次旋转操作。前者简单易懂,然而后者可能相对不是那么容易理解。其实过程很简单,先将后者转换成前者的情况,再进行第二次旋转就可以了。
插入的代码递归部分可能有些难以理解,我们可以这样想,用递归函数向下找到插入的结点。那么这之前的递归函数是不是都按照栈的结构放着呢?找到插入的节点后进行插入操作,之后递归函数出栈,后入栈的先出栈,出栈的时候对其进行一次判断,若平衡的话没啥事,不平衡的话就对其进行平衡后再出栈,如此回溯,这样插入的时候就能保证树是平衡的了。
删除操作的话和插入操作很类似,就是要考虑一下要删除的结点的情况,参考二叉查找树删除结点的三种情况即可。