先来了解一些基本概念
二叉树树高
叶节点的树高都为1,如果一个节点的左右节点的高度不同,则取两者中较大的节点的高度加1为该节点的高度,别忘了加一
这里我们采用递归求节点的高度
int GetHeight(AVLTree AT)
{
int HL, HR, MaxH;
if (AT)//有这个节点,返回左右子树中较大的那个值再加一
{
HL = GetHeight(AT->Left);
HR = GetHeight(AT->Right);
MaxH = HL > HR ? HL : HR;
return (MaxH + 1);
}
else//就是没有这个节点,返回0
return 0;
}
平衡因子
平衡因子是判断平衡二叉树是否平衡的条件,每个节点的平衡因子都不同,(节点的平衡因子=该节点的左子树第一个节点的高度—该节点的右子树的第一个节点的高度)若无左(右)子树,则左(右)子树的高度为0。平衡二叉树的平衡因子的绝对值<=1,如果某个节点平衡因子的绝对值不满足这个条件,插入的这个节点就为问题节点,这是我们就需要进行调整,调整方法有左单旋,右单旋,左右双旋,右左双旋。
调整方法
这个是二叉树节点的结构体定义
typedef int ElementType;
typedef struct AVLNode* AVLTree;
struct AVLNode//二叉树节点结构体
{
ElementType Data;//节点数据
AVLTree Left;
AVLTree Right;
int Height;//节点高
};
每当插入一个数据节点后,都要判断二叉树是否平衡,检测到二叉树不平衡,就要通过左单旋,右单旋来调整二叉树使其达到平衡,二叉树的左单旋和右单旋混合使用,可以组成左右双旋和右左双旋
左单旋
如图,新插入C节点后A的平衡因子由1变成2,需要进行调整变成平衡二叉树
下面这种是一般情况
这个是实现的代码
AVLTree SingleLeftRotation(AVLTree A)
{
AVLTree B = A->Left;
A->Left = B->Right;
B->Right = A;
//只需要更新A,B的节点的高度
A->Height = MAX(GetHeight(A->Left), GetHeight(A->Right)) + 1;//更新A节点的树高
B->Height = MAX(GetHeight(B->Left), A->Height) + 1;//更新B节点的树高
return B;
}
右单旋
右单旋和左单旋就是对称的情况
下面是实现的代码
AVLTree SingleRightRotation(AVLTree A)
{
AVLTree B = A->Right;
A->Right = B->Left;
B->Left = A;
//只需要更新A,B的节点的高度
A->Height = MAX(GetHeight(A->Left), GetHeight(A->Right)) + 1;//更新A节点的树高
B->Height = MAX(GetHeight(B->Right), A->Height) + 1;//更新B节点的树高
return B;
}
左右双旋
左右双旋就是先进行右单旋,再进行左单旋,下图是一种简单的情况
下图是左右双旋的一般情况
下面是实现的代码,通过调用左单旋和右单旋的函数实现
AVLTree DoubleLeftRightRotation(AVLTree A)
{
A->Left = SingleRightRotation(A->Left);//先进行右单旋
A = SingleLeftRotation(A);//后进行左单旋
}
右左双旋
下面是实现的代码,先调用左单旋函数,后调用右单旋函数
AVLTree DoubleRightLeftRotaion(AVLTree A)
{
A->Right = SingleLeftRotation(A->Right);//先进行左单旋
A = SingleRightRotation(A);//后进行右单旋
}
整体代码实现
本人的设计思路是用户每输入一个数据,都使用递归,当达到最里面一层嵌套时找到节点插入的位置,然后插入节点。随后嵌套逐渐退出,所操作的节点逐渐向根节点靠近,对于每一个所操作的节点都要判断其平衡因子是否符合条件,若不符合则根据不同情况进行调整,每次嵌套结束之前都要更新当前节点的高度,递归结束后二叉树达到平衡,返回二叉树根节点指针。如果用户输入的数据为0,则,输入结束,中序遍历输出二叉树。
//这是主函数
int main()
{
int Data;
AVLTree AT = NULL;
while (1)
{
scanf_s("%d", &Data);
if (!Data)如果输入为0就跳出循环
break;
AT = VALTreeInsert(AT, Data);//把数据插入二叉树中并做调整
}
printf("中序遍历输出结果是");
InorderTraversal(AT);//中序遍历输出二叉树
return 0;
}
接下来是二叉树的插入操作
AVLTree VALTreeInsert(AVLTree AT, ElementType Data)
{
if (AT == NULL)//如果这个节点为空,就在这里建立一个节点,相当于把数据插入到二叉树上,接下来要做的就是判断和调整
{
AT = (AVLTree)malloc(sizeof(struct AVLNode));
AT->Data = Data;
AT->Left = AT->Right = NULL;//把指针置空
AT->Height = 1;//记得置1
}
else if (Data < AT->Data)//如果要插入的数据的值小于当前节点的值,就插入到该节点的左子树
{//递归遍历二叉树然后把数据插入到二叉树里面,此时我们已经跑到二叉树底层了,然后再从底层一点一点的往上返回判断平衡因子,发现不平衡问题就要调整
AT->Left = VALTreeInsert(AT->Left, Data);//接着往左子树递归遍历下去
if ((GetHeight(AT->Left) - GetHeight(AT->Right)) == 2)//因为是往该节点的左子树中插入数据,如果该节点变成了发现问题的节点,则该节点的平衡因子只能是2,不可能是-2
{
if (Data < AT->Left->Data)//如果要插入的数据小于该节点的左子树的第一个节点的数据,则表示该数据插入到了该节点的左子树的左边去了,我们要做的就是左单旋
AT = SingleLeftRotation(AT);//左单旋
else//否则就是插入到该节点的左子树的第一个节点的右边去了,我们要做的是左-右双旋
AT = DoubleLeftRightRotation(AT);//左右双旋
}
}
else if (Data > AT->Data)//如果要插入的数据的值大于当前节点的值,就插入到该节点的右子树
{
AT->Right = VALTreeInsert(AT->Right, Data);
if ((GetHeight(AT->Left) - GetHeight(AT->Right)) == -2)//因为是往该节点的右子树中插入数据,如果这个节点变成了发现问题的节点,则该节点的平衡因子只能是-2,不可能是-2
{
if (Data > AT->Right->Data)//如果要插入的数据大于该节点的右子树的第一个节点的数据,则表示该数据插入到的该节点的右子树的右边去了,我们要做的是右单旋
AT = SingleRightRotation(AT);//右单旋
else//否则该数据就是插入到了该节点的右子树第一个节点的左边去了,我们要做的是右-左双旋
AT = DoubleRightLeftRotaion(AT);//右左双旋
}
}
else//如果要插入的数据的值等于当前节点的值,则输出该值已存在,不进行插入操作
printf("该值已存在\n");
AT->Height = MAX(GetHeight(AT->Left), GetHeight(AT->Right)) + 1;//插入节点,调整二叉树之后都要更新树的高度,别忘了加1
return AT;
}
得,下面是完整代码
当然,递归可能会造成较大的空间复杂度,从而无法处理太大的数据量,读者理解了算法之后可以尝试使用非递归实现平衡二叉树
#include<stdio.h>
//
typedef int ElementType;
typedef struct AVLNode* AVLTree;
struct AVLNode
{
ElementType Data;
AVLTree Left;
AVLTree Right;
int Height;
};
/
int GetHeight(AVLTree AT);//计算树的高度
AVLTree VALTreeInsert(AVLTree AT, ElementType Data);//二叉树的插入兼调整操作
int MAX(int HL, int HR);//取两者中较大的那个数的值
AVLTree SingleLeftRotation(AVLTree A);//左单旋
AVLTree SingleRightRotation(AVLTree A);//右单旋
AVLTree DoubleLeftRightRotation(AVLTree A);//左右双旋
AVLTree DoubleRightLeftRotaion(AVLTree A);//右左双旋
void InorderTraversal(AVLTree AT);//中序遍历
/
int main()
{
int Data;
AVLTree AT = NULL;
while (1)
{
scanf_s("%d", &Data);
if (!Data)如果输入为0就跳出循环
break;
AT = VALTreeInsert(AT, Data);//把数据插入二叉树中并做调整
}
printf("中序遍历输出结果是");
InorderTraversal(AT);//中序遍历输出二叉树
return 0;
}
int GetHeight(AVLTree AT)
{
int HL, HR, MaxH;
if (AT)//有这个节点,返回左右子树中较大的那个值再加一
{
HL = GetHeight(AT->Left);
HR = GetHeight(AT->Right);
MaxH = HL > HR ? HL : HR;
return (MaxH + 1);
}
else//就是没有这个节点,返回0
return 0;
}
AVLTree VALTreeInsert(AVLTree AT, ElementType Data)
{
if (AT == NULL)//如果这个节点为空,就在这里建立一个节点,相当于把数据插入到二叉树上,接下来要做的就是判断和调整
{
AT = (AVLTree)malloc(sizeof(struct AVLNode));
AT->Data = Data;
AT->Left = AT->Right = NULL;//把指针置空
AT->Height = 1;//记得置1
}
else if (Data < AT->Data)//如果要插入的数据的值小于当前节点的值,就插入到该节点的左子树
{//递归遍历二叉树然后把数据插入到二叉树里面,此时我们已经跑到二叉树底层了,然后再从底层一点一点的往上返回判断平衡因子,发现不平衡问题就要调整
AT->Left = VALTreeInsert(AT->Left, Data);//接着往左子树递归遍历下去
if ((GetHeight(AT->Left) - GetHeight(AT->Right)) == 2)//因为是往该节点的左子树中插入数据,如果该节点变成了发现问题的节点,则该节点的平衡因子只能是2,不可能是-2
{
if (Data < AT->Left->Data)//如果要插入的数据小于该节点的左子树的第一个节点的数据,则表示该数据插入到了该节点的左子树的左边去了,我们要做的就是左单旋
AT = SingleLeftRotation(AT);//左单旋
else//否则就是插入到该节点的左子树的第一个节点的右边去了,我们要做的是左-右双旋
AT = DoubleLeftRightRotation(AT);//左右双旋
}
}
else if (Data > AT->Data)//如果要插入的数据的值大于当前节点的值,就插入到该节点的右子树
{
AT->Right = VALTreeInsert(AT->Right, Data);
if ((GetHeight(AT->Left) - GetHeight(AT->Right)) == -2)//因为是往该节点的右子树中插入数据,如果这个节点变成了发现问题的节点,则该节点的平衡因子只能是-2,不可能是-2
{
if (Data > AT->Right->Data)//如果要插入的数据大于该节点的右子树的第一个节点的数据,则表示该数据插入到的该节点的右子树的右边去了,我们要做的是右单旋
AT = SingleRightRotation(AT);//右单旋
else//否则该数据就是插入到了该节点的右子树第一个节点的左边去了,我们要做的是右-左双旋
AT = DoubleRightLeftRotaion(AT);//右左双旋
}
}
else//如果要插入的数据的值等于当前节点的值,则输出该值已存在,不进行插入操作
printf("该值已存在\n");
AT->Height = MAX(GetHeight(AT->Left), GetHeight(AT->Right)) + 1;//插入节点,调整二叉树之后都要更新树的高度,别忘了加1
return AT;
}
int MAX(int HL, int HR)
{
return HL > HR ? HL : HR;
}
AVLTree SingleLeftRotation(AVLTree A)
{
AVLTree B = A->Left;
A->Left = B->Right;
B->Right = A;
//只需要更新A,B的节点的高度
A->Height = MAX(GetHeight(A->Left), GetHeight(A->Right)) + 1;//更新A节点的树高
B->Height = MAX(GetHeight(B->Left), A->Height) + 1;//更新B节点的树高
return B;
}
AVLTree SingleRightRotation(AVLTree A)
{
AVLTree B = A->Right;
A->Right = B->Left;
B->Left = A;
//只需要更新A,B的节点的高度
A->Height = MAX(GetHeight(A->Left), GetHeight(A->Right)) + 1;//更新A节点的树高
B->Height = MAX(GetHeight(B->Right), A->Height) + 1;//更新B节点的树高
return B;
}
AVLTree DoubleLeftRightRotation(AVLTree A)
{
A->Left = SingleRightRotation(A->Left);//先进行右单旋
A = SingleLeftRotation(A);//后进行左单旋
}
AVLTree DoubleRightLeftRotaion(AVLTree A)
{
A->Right = SingleLeftRotation(A->Right);//先进行左单旋
A = SingleRightRotation(A);//后进行右单旋
}
void InorderTraversal(AVLTree AT)
{
if (AT != NULL)
{
InorderTraversal(AT->Left);//输出左节点
printf("%d ", AT->Data);//输出节点的值
InorderTraversal(AT->Right);//输出右节点
}
}