目录
前言&看前必读
本文参考b站up@TyrantLucifer,发布的视频教程数据结构-平衡二叉树(AVL)-C语言实现https://www.bilibili.com/video/BV1m64y147Tv/?p=2&vd_source=b64a25bce866a4532d6d87bb174d59e4这个视频讲解通俗易懂,代码可读性也比较高,值得网友去学习。所以本文代码基于此视频教程作为参考,不过这里的代码但略有改动,程序的思路是相同的。另附参考的原文地址:数据结构系列19-平衡二叉树 | tyrantlucifer
本文不讲大量空洞的理论。秉承以实践为主,理论为辅的宗旨讲解平衡二叉树的插入与删除的实现。前面第一节第二节主要是简述一些基本的概念,从第三节开始结合实际讲解程序的思路。如果您对平衡二叉树有了基本的认识可以直接跳到第三节。
个人技术水平有限,如有不妥之处请多多指教,相互交流学习
1.什么是平衡二叉树?
1.1平衡二叉树的基本概念
平衡二叉树(balance binary tree)又称作AVL树,AVL树名字来自提出者G.M. Adelson-Velsky和E.M. Landis。平衡二叉树是二叉搜索树的进化体,由此平衡二叉树具有二叉排序树所有的基本特征:
1.如果左子树不为空,则左子树上任意一个节点值均小于根节点值。
2.如果右子树不为空,则右子树上任意一个节点值均大于根节点值。
3.左右子树都是二叉排序树。
4.树中没有相同的节点。
除此之外,平衡二叉树还具备一个非常重要的特点:任意一个节点左右子树的高度之差的绝对值不超过1,加上前面所提到的四个特征,总共五个特征,缺一不可。我们通常把左右子树的高度差称为平衡因子(BF,Belance Fator),如下面图1所示,这是一颗典型的平衡二叉树。每个节点右上角标记的数是这个节点的平衡因子,显然这颗平衡二叉树具备其应有的所有特征。
图1 平衡二叉树
1.2为何要引入平衡二叉树?
二叉排序树的平均查找长度与树的形状有关,最好的情况是O(log2n),最差的情况是O(n),如下图所示,把1 2 3 4 5 这几个元素,按顺序依次插入节点将得到这样的一颗二叉树,如图2所示,此时二叉查找树被退化成链表。显然这个二叉排序树的平均查找长度为O(n),所以我们需要引入平衡二叉树来解决这一问题,平衡二叉树在插入/删除完成后,会沿着检查其插入/删除路径的每一个节点的平衡因子,当平衡因子BF等于2时会及时做出相应的调整,从而最大程度的避免这种情况发生。
图2 退化成链表的二叉排序树
2.平衡二叉树如何进行调整?
为了方便讲解平衡二叉树如何调整,我们要引入一个概念,即最小不平衡子树,最小不平衡二叉子树的根节点是离插入点最近且平衡因子的绝对值大于1的节点作为根节点的二叉子树,如图3所示的二叉排序树,在此树插入一个新节点90,如左侧所示,左侧原本98的平衡因子绝对值为1,插入后由1变为2,显然98就是最小不平衡二叉子树的根节点。如右边的红色圈圈所示。当然最小不平衡二叉子树有可能的整个二叉树本身,如图5所示,15是二叉树的根节点,它的平衡因子的绝对值为2。
图 3 最小不平衡子树
图5 最小不平衡二叉子树也有可能是根节点
温馨提示:下面简单介绍几种不平衡节点的调整方式,这些内容对编写程序的帮助有限,仅用于了解一些基本的概念,实际调整碰到的情况比较复杂一点,很多情况下只会这些概念编写程序显得有些束手无策,当然如果您对这些调整方式有了基本的认识,可以直接跳到二叉树的插入实现这一节,本文将讲详细讲解其插入删除时调整的原理和细节。
图6-图9看懂大概意思就行了,先不要钻牛角尖,对于平衡二叉树的学习由易到难,才是正确的学习方法,平衡二叉树调整的详细过程请见本文的第三节。平衡二叉树旋转必须要掌握稍微复杂的情况怎么处理,才能独立写出程序来。
对于平衡二叉树来说不平衡节点主要右RR型、LL型、RL型、LR型。导致不平衡节点出现的原因都是新节点插入导致的,所以我们需要对其进行旋转操作。
常见不平衡节点产生的原因以及处理方法
类型 | 原因 | 处理方法 |
RR | 新插入节点的插入到不平衡节点的右孩子的右边 | 左旋(图6) |
LL | 新插入节点的插入到不平衡节点的左孩子的左边 | 右旋(图7) |
RL | 新插入节点的插入到不平衡节点的右孩子的左边 | 先右旋再左旋(图8) |
LR | 新插入节点的插入到不平衡节点的左孩子的右边 | 先左旋再右旋(图9) |
对于RR型我们需要进行左旋操作,如图6所示:
图6 RR型调整
对于LL型我们需要进行先右旋操,如图7所示:
图7 LL型调整
对于RL型我们需要进行先右旋操作,再左旋操作,如图8所示:
图8 RL型调整
对于LR型我们需要进行先左旋操作,再右旋操作,如图9所示:
图9 LR型调整
3.平衡二叉树的插入实现
如图10-图12所示下面分别是稍微复杂情况下调整方法以及各节点平衡因子的情况,通过观察平衡因子的情况有助于我们写程序来判断当前二叉树是否应该要调整。实现平衡二叉树的插入操作首先要实现左旋函数和右旋函数,以确保插入元素后任然具有平衡二叉树的所有特性。
图10 稍复杂情况下的RR型的调整
如图10所示130是最新插入的节点,50是不平衡节点,它的平衡因子(以下将简称BF)为-2,属于RR型,RR型是新节点插入最小不平衡右子树的右子树导致的,因此我们对它进行左旋操作,先做准备工作,创建一个临时变量保存首先把最小不平衡子树的根节点右孩子保存起来,最小不平衡子树的根节点的右孩子指针指向最小不平衡右孩子的左孩子,然后再把最小不平衡子树的根节点的右孩子的左孩子指针指向最小不平衡子树的根节点,最后把最小不平衡子树的根节点的右孩子换做成该子树的根节点。
或许看着听着有点绕?没关系,看下面的代码并且结合图片更非常的直观的。
首先定义一个结构体
typedef struct TreeNode
{
int data;//数据
int height;//当前节点的高度
struct TreeNode* lchild;//左孩子
struct TreeNode* rchild;//右孩子
}TreeNode;
//获取当前节点高度
int getHeight(TreeNode* node)
{
return node ? node->height : 0;
}
//返回最大值
int max(int a, int b)
{
return a>b ? a : b;
}
实现RR型处理的左旋函数
//RR处理,左旋node是根节点指针,root是根节点二级指针
//当然作为读者可以适当优化一些,比如只用一个二级指针就能实现这个功能
void rrRotation(TreeNode *node, TreeNode** root)
{
TreeNode* temp = node -> rchild;//保存最小不平衡子树根节点的右孩子
node -> rchild = temp -> lchild;//根节点右孩子指向,自己右孩子的左孩子
temp -> lchild = node;//自己右孩子的左孩子指针指向根原来的最小不平衡二叉树的根节点
//重新节点计算高度
node -> height = max(getHeight(node -> lchild),getHeight(node -> rchild)) + 1;
temp -> height = max(getHeight(temp -> lchild),getHeight(temp -> rchild)) + 1;
//原来的最小不平衡二叉树的根节点的右孩子作为新的根节点
*root = temp;
}
图11 稍复杂情况下的LL型的调整
如图11所示8是最新插入的节点,15是不平衡节点,它的平衡因子BF为2,属于LL型,LL型是新节点插入最小不平衡左子树的左子树导致的,因此我们对它进行右旋操作,先做准备工作,创建一个临时变量保存首先把最小不平衡子树的根节点左孩子保存起来,最小不平衡子树的根节点的左孩子指针指向最小不平衡左孩子的右孩子,然后再把最小不平衡子树的根节点的左孩子的右孩子指针指向最小不平衡子树的根节点,最后把最小不平衡子树的根节点的左孩子换做成该子树的根节点。
看下面的代码并且结合图片更非常的直观的。
实现LL型处理的右旋函数
//LL处理,右旋,node是根节点指针,root是根节点二级指针
//当然作为读者可以适当优化一些,比如只用一个二级指针就能实现这个功能
void llRotation(TreeNode *node, TreeNode** root)
{
TreeNode *temp = node ->lchild;//保存最小不平衡子树根节点的左孩子
node -> lchild = temp->rchild;//根节点左孩子指向自己左孩子的右孩子
temp -> rchild = node;//自己左孩子的右孩子指针指向根原来的最小不平衡二叉树的根节点
//更新高度
node -> height = max(getHeight(node->lchild),getHeight(node->rchild)) + 1;
temp -> height = max(getHeight(temp -> lchild),getHeight(temp -> rchild)) + 1;
*root = temp;//左孩子作为根节点
}
图12 稍复杂情况下的RL型的调整
图13 稍复杂情况下LR调整
如图13-14所示,RL型很直观的可以看出,先对其最小不平衡子树的右孩子右旋,然后对最小不平衡子树的根左旋。LR型很直观的可以看出,先对其最小不平衡子树的左孩子左旋,然后对最小不平衡子树的根右旋。
平衡二叉树插入思路
平衡二叉树的插入基本原理与二叉搜索树相类似,只不过多了一个二叉树调整的步骤,每次插入完后向原路径返回,检查其节点的平衡因子的绝对值是否大于2,如果大于2,则去调整。
二叉树插入/删除的实现主要是难点是如何判断最小不平衡子树属于哪种类型,一般情况人眼看二叉树不平衡属于哪种类型该如何调整,一眼就能看出来,但是计算机不像人类一样,所以需要梳理好一套清晰的逻辑,才能编写程序来判断并作出相应调整。
观察图6-图13可知,LL型的新插入的节点总是在当前节点的左孩子的左边,再结合本文第一节所提到的特征,新插入的节点一定比当前不平衡节点的左孩子要小。如果是LR型,新插入的节点总是在当前节点的左孩子的右边,所以比当前节点的左孩子大。RR型新插入的节点一定比当前不平衡节点的右孩子要大,RL型则是相反的,只要利用这个特性就能判断出当前节点不平衡属于什么类型了。
平衡二叉树插入代码如下所示:
//插入
void avlInsert(TreeNode** T, int data)
{
if(*T == NULL)
{
*T = (TreeNode*)malloc(sizeof(TreeNode));
(*T) -> data = data;
(*T) -> height = 0;
(*T) -> lchild = NULL;
(*T) -> rchild = NULL;
}else if(data < (*T)->data){
avlInsert(&(*T)->lchild,data);
int lHeight = getHeight((*T) -> lchild);
int rHeight = getHeight((*T) -> rchild);
//计算平衡因子
if(lHeight - rHeight == 2)//LL or LR
{
if(data < (*T)->lchild->data)
{
//LL
llRotation(*T,T);
}else{
//LR
rrRotation((*T)->lchild,&(*T)->lchild);
llRotation(*T,T);
}
}
}else if(data > (*T) -> data){// RR or RL
avlInsert(&(*T) -> rchild, data);
int lHeight = getHeight((*T)->lchild);
int rHeight = getHeight((*T)->rchild);
//计算bf
if(rHeight - lHeight == 2)
{
if(data > (*T) -> rchild -> data)
{
//RR
rrRotation(*T,T);
}else{
//RL
llRotation((*T)->rchild,&(*T)->rchild);
rrRotation(*T,T);
}
}
}
(*T)->height = max(
getHeight((*T)->lchild),
getHeight((*T)->rchild)
) +1;
}
4.平衡二叉树的删除实现
平衡二叉树删除的基本原理也是类似的,删除的基本原理和二叉搜索树相同,如果不了解基本原理请参考下面这篇文章:
c语言递归实现二叉排序树的插入与删除https://blog.csdn.net/qq_21708039/article/details/130516473 平衡二叉树的插入和删除也是同样的,删除后需要沿着删除路径检查是否有节点不平衡,也就是计算BF,对此本文采用第二种方法来判断不平衡节点的类型。
通过观察图6-图13可知,当最小不平衡二叉树的BF是+2说明当前类型可能是LL型或者LR型,这时候我们需要进一步判断他的左子树BF值是多少,当左子树的BF=1或者0是说明是LL型,LR型BF = -1。当最小不平衡二叉树的BF是-2明当前类型可能是RR型或者RL型,这时候我们需要进一步判断他的右子树BF值是多少,当右子树的BF=-1或者0是说明是RR型,RL型BF = 1。
平衡二叉树删除代码如下所示:
//删除
void avlDelete(TreeNode** T, int data)
{
if(T == NULL) return;
if(*T == NULL) return;
if(data > (*T)->data)
{
avlDelete(&(*T)->rchild,data);
}else if(data < (*T)->data){
avlDelete(&(*T)->lchild,data);
}else{
if((*T)->lchild == NULL && (*T)->rchild == NULL)
{
free(*T);
*T = NULL;
}else if( (*T) -> lchild == NULL ){
struct TreeNode* temp = (*T)->rchild;
free(*T);
*T = temp;
}else if( (*T) -> rchild == NULL ){
struct TreeNode* temp = (*T)->lchild;
free(*T);
*T = temp;
}else{
struct TreeNode* temp = (*T)->lchild;
while(temp -> rchild != NULL)
{
temp = temp ->rchild;
}
(*T)->data = temp->data;
avlDelete(&(*T)->lchild,temp->data);
}
}
if(*T == NULL) return;//如果删除后是空指针就什么都不做
(*T)->height = max(
getHeight((*T)->lchild),
getHeight((*T)->rchild)
)+1;
int lheight = getHeight((*T)->lchild);
int rheight = getHeight((*T)->rchild);
//计算BF
if(lheight-rheight == 2)
{
//LL LR
//进一步判断,详见上文
if( getHeight((*T)->lchild->lchild) - getHeight((*T)->lchild->rchild) == 0 || getHeight((*T)->lchild->lchild) - getHeight((*T)->lchild->rchild) == 1 )
{
//LL
llRotation(*T,T);
}else{
//LR
rrRotation((*T)->lchild, &(*T)->lchild);
llRotation(*T,T);
}
}else if(lheight -rheight == -2)
{
//RR RL
if( getHeight((*T)->rchild->lchild) - getHeight((*T)->rchild->rchild) == 0 || getHeight((*T)->rchild->lchild) - getHeight((*T)->rchild->rchild) == -1 )
{
//RL
rrRotation(*T,T);
}else{
//RL
llRotation((*T)->rchild,&(*T)->rchild);
rrRotation(*T,T);
}
}
}
5.完整代码
#include<stdlib.h>
#include<stdio.h>
typedef struct TreeNode
{
int data;
int height;
struct TreeNode* lchild;
struct TreeNode* rchild;
}TreeNode;
int getHeight(TreeNode* node)
{
return node ? node->height : 0;
}
int max(int a, int b)
{
return a>b ? a : b;
}
void rrRotation(TreeNode *node, TreeNode** root)
{
TreeNode* temp = node -> rchild;
node -> rchild = temp -> lchild;
temp -> lchild = node;
node -> height = max(getHeight(node -> lchild),getHeight(node -> rchild)) + 1;
temp -> height = max(getHeight(temp -> lchild),getHeight(temp -> rchild)) + 1;
*root = temp;
}
void llRotation(TreeNode *node, TreeNode** root)
{
TreeNode *temp = node ->lchild;
node -> lchild = temp->rchild;
temp -> rchild = node;
node -> height = max(getHeight(node->lchild),getHeight(node->rchild)) + 1;
temp -> height = max(getHeight(temp -> lchild),getHeight(temp -> rchild)) + 1;
*root = temp;
}
void avlInsert(TreeNode** T, int data)
{
if(*T == NULL)
{
*T = (TreeNode*)malloc(sizeof(TreeNode));
(*T) -> data = data;
(*T) -> height = 0;
(*T) -> lchild = NULL;
(*T) -> rchild = NULL;
}else if(data < (*T)->data){
avlInsert(&(*T)->lchild,data);
int lHeight = getHeight((*T) -> lchild);
int rHeight = getHeight((*T) -> rchild);
//计算平衡因子
if(lHeight - rHeight == 2)
{
if(data < (*T)->lchild->data)
{
//LL
llRotation(*T,T);
}else{
//LR
rrRotation((*T)->lchild,&(*T)->lchild);
llRotation(*T,T);
}
}
}else if(data > (*T) -> data){
avlInsert(&(*T) -> rchild, data);
int lHeight = getHeight((*T)->lchild);
int rHeight = getHeight((*T)->rchild);
//计算bf
if(rHeight - lHeight == 2)
{
if(data > (*T) -> rchild -> data)
{
//RR
rrRotation(*T,T);
}else{
//RL
llRotation((*T)->rchild,&(*T)->rchild);
rrRotation(*T,T);
}
}
}
(*T)->height = max(
getHeight((*T)->lchild),
getHeight((*T)->rchild)
) +1;
}
void avlDelete(TreeNode** T, int data)
{
if(T == NULL) return;
if(*T == NULL) return;
if(data > (*T)->data)
{
avlDelete(&(*T)->rchild,data);
}else if(data < (*T)->data){
avlDelete(&(*T)->lchild,data);
}else{
if((*T)->lchild == NULL && (*T)->rchild == NULL)
{
free(*T);
*T = NULL;
}else if( (*T) -> lchild == NULL ){
struct TreeNode* temp = (*T)->rchild;
free(*T);
*T = temp;
}else if( (*T) -> rchild == NULL ){
struct TreeNode* temp = (*T)->lchild;
free(*T);
*T = temp;
}else{
struct TreeNode* temp = (*T)->lchild;
while(temp -> rchild != NULL)
{
temp = temp ->rchild;
}
(*T)->data = temp->data;
avlDelete(&(*T)->lchild,temp->data);
}
}
if(*T == NULL) return;
(*T)->height = max(
getHeight((*T)->lchild),
getHeight((*T)->rchild)
)+1;
int lheight = getHeight((*T)->lchild);
int rheight = getHeight((*T)->rchild);
if(lheight-rheight == 2)
{
//LL LR
if( getHeight((*T)->lchild->lchild) - getHeight((*T)->lchild->rchild) == 0 || getHeight((*T)->lchild->lchild) - getHeight((*T)->lchild->rchild) == 1 )
{
//LL
llRotation(*T,T);
}else{
//LR
rrRotation((*T)->lchild, &(*T)->lchild);
llRotation(*T,T);
}
}else if(lheight -rheight == -2)
{
//RR RL
if( getHeight((*T)->rchild->lchild) - getHeight((*T)->rchild->rchild) == 0 || getHeight((*T)->rchild->lchild) - getHeight((*T)->rchild->rchild) == -1 )
{
//RL
rrRotation(*T,T);
}else{
//RL
llRotation((*T)->rchild,&(*T)->rchild);
rrRotation(*T,T);
}
}
}
void preOrder(TreeNode *T)
{
if(!T) return;
printf("%d ", T->data);
preOrder(T -> lchild);
preOrder(T -> rchild);
}
void inOrder(TreeNode *T)
{
if(!T) return;
inOrder(T -> lchild);
printf("%d ", T->data);
inOrder(T -> rchild);
}
void bstFree(TreeNode** T)
{
if(*T == NULL) return;
bstFree(&(*T)->lchild);
bstFree(&(*T)->rchild);
free(*T);
*T=NULL;
}
int main()
{
TreeNode *T = NULL;
int nums[5] = {1,8,6,7,10};
for(int i = 0; i<5; i++)
{
avlInsert(&T,nums[i]);
}
preOrder(T);
printf("\t先\n");
inOrder(T);
printf("\t中\n");
avlDelete(&T,6);
preOrder(T);
printf("\t先\n");
inOrder(T);
printf("\t中\n");
bstFree(&T);
return 0;
}
运行结果:
6 1 8 7 10 先
1 6 7 8 10 中
8 1 7 10 先
1 7 8 10 中
图14 上面程序的平衡二叉树的调整过程