一、平衡二叉树的定义和概念:
平衡二叉树的概念基于先前学习的查找二叉树的概念。
之前提到过,由于查找二叉树存在建树或者删除节点顺序的的问题,会导致链式二叉树的存在。这样查找的时间复杂对就会变为O(n)。而为了让时间复杂度仍然保持O(logn)级别,所以加上了平衡的要求,这样的查找二叉树被称为平衡二叉树,英文缩写为AVL树;
二、平衡二叉树的数据结构:
平衡二叉树附加了一个平衡因子,用于衡量平衡二叉树的相关指标;对于每一个节点来说,其左右子树的高度之差称为该节点的平衡因子。对于平衡二叉树来说,要求每一个节点的平衡因子都为小于等于1;
为了能够方便的计算相关的参数,所以在通用的树节点中设置一个变量height,用于标记该节点在平衡二叉树的高度;
struct node{
int v,height;
node *lchild,*rchild;
};
新建节点的建立函数:
node* newNode(int v){
node* Node=new node;
Node->v=v;
Node->height=1;
Node->lchild=Node->rchild=NULL;
return Node;
}
获得节点当前所在子树的高度:
int getHeight(node* root){
if(root==NULL)
return 0;
return root->height;
}
计算给出节点的平衡因子:
int getBalanceFactor(node* root){
return getHeight(root->lchild)-getHeight(root->rchild);
}
更新操作:
目的是重新计算给出节点的高度。可以看出高度是个通过子节点推算的过程;
void updateHeight(node* root){
root->height=max(getHeight(root->lchild),getHeight(root->rchild))+1;
}
三、平衡二叉树的基本操作:
平衡二叉树和二叉查找树基本相同,也有基本的查找、插入、建树以及删除操作;
1.查找操作:
查找操作和查找二叉树完全相同,思想也一样。
void search(node* root,int x){
if(root==NULL){
printf("empty tree");
return;
}
if(x==root->data){
printf("%d",root->data);
}else if(x<root->data){
search(root->lchild,x);
}else{
search(root->rchild,x);
}
}
2.插入操作:
对于插入操作,我们应该极力避免像查找二叉树那样可能会导致链式的操作。
对于平衡二叉树,首先要介绍左旋和右旋的相关概念。
(上图源于书上实例)
左旋右旋本质上就是对一个确定的查找二叉树的几个节点的旋转。对于上图的左边二叉树,可以旋转为右边的二叉树,而保证仍然符合查找二叉树的定义;
对于左旋来说,调整步骤如下:
首先:让B的左子树变成A的右子树;
其次:让A称为B的左子树;
最后:让B变为根节点;
代码化流程如下所示:
void L(node* &root){
node* temp=root->rchild;
root->rchild=temp->lchild;
temp->lchild=root;
updateHeight(root);
updateHeight(temp);
root=temp;
}
右旋情况也和左旋大致相同,可以认为就是一个逆操作;
大致流程:
首先:让A的右子树变成B的左子树;
其次:让B变成A的右子树;
最后:将根节点变为A;
代码化流程如下所示:
void R(node* &root){
node* temp=root->lchild;
root->lchild=temp->rchild;
temp->rchild=root;
updateHeight(root);
updateHeight(temp);
root=temp;
}
这里需要注意更新节点操作,也就是updateHeight()操作;该操作是针对新调整的节点,而且对于新调整的节点,一定要注意要从下向上计算,由于未调整的节点高度不变,仍需要计算的就只有旋转过后的两个节点;
理解了左旋和右旋之后,接着来理解如何在插入的时候对二叉树进行操作,使得能够满足平衡二叉树的基本要求,也就是平衡因子始终小于等于1;
平衡二叉树和查找二叉树本质上不同的地方就是在于平衡因子的界定。对于一个已经平衡的二叉树,我们插入一个节点的结果必然有两种情形:
其一:插入之后所有节点,仍未平衡因子等于1或者-1或者等于0;
(同样注意,这里平衡因子取得是绝对值)
其二:插入之后所有节点,平衡因子等于等于2或者-2;
对于第一种情况,我们可以认为插入正确。对于第二种情况,就是我们需要insert所需要修改的;
对于如何平衡二叉树,我们都是建立在左旋和右旋上。
首先介绍当插入节点平衡因子打破的四种情况,其中两种和另外两种就是镜像模式;
第一种是当插入平衡因子等于二的场景:
可以看到,当A节点下的左右子树插入节点之后,平衡因子变为2。插入节点导致平衡因子变为2有且只有这两种情况,手画推到也可以得出;
这两种形式,我们称之为LL型和LR型。
对于LL型,其实我们进行一次右旋就可以解决:
此时,就可以使得平衡因子变为0;
对于LR型,我们进行一次左旋再进行一次右旋:
此时,B作为根节点,平衡因子归于正常;
唯一要注意的就是节点高度的更新问题,整个过程是递归进行,当节点插入之后,就会从下向上挨个更新节点高度值。并且会同步的进行左右子树查询以判断平衡因子,从而判断是否需要进行调整;
对于另外两种情况,也是镜像:
两种情况分别称为RR和RL;
调整方式和LR和LL也是大相径庭;
对于RR,直接左旋便可以完成任务:
对于RL型,可以采用先右旋在左旋的形式进行调整:
以上就是插入之后破坏平衡因子的四种情况;
以上四种操作不理解,归根结底还是L和R型左旋右旋操作;
对于AVL要注意的是递归从下向上的判定条件,整个过程一定是从插入节点到根节点一步一步回溯判定并且更新的;
这里我们和查找二叉树相似,仍然使用insert()函数来完成节点的插入和辅助建树操作;
当然还要注意一下LR LL的判别,一定是通过左子树的平衡因子来进行判别;
RR RL同理;
插入的代码如下所示:
void insert(node* &root,int v){
if(root==NULL){
root=nowNode(v);
//找到合适的位置,进行节点的插入
return;
}
if(v<root->v){
//向左子树插入
insert(root->child,v);
updateHeight(root);
//这里更新操作在递归的前提下,是从下向上的
if(getBalanceFactor(root)==2){
if(getBlanceFactor(root->lchild)==1){
//LL型
R(root);
}else if(gatBlanceFactor(root->rchild)==-1){
//LR型
L(root->lchild);
R(root);
}
}
}else{
insert(root->rchild,v);
updateHeight(root);
if(getBalanceFactor(root)==-2){
if(getBalanceFactor(root->rchild)==-1){
L(root);
}else if(getBalanceFactor(root->rchild)==-1){
R(root->rchild);
L(root);
}
}
}
}
所以,和二叉查找树类似,相应的构建操作也借助insert函数来进行:
如下所示:
node* Create(int data[],int n){
node* root=NULL;
for(int i=0;i<n;i++){
insert(root,data[i]);
}
return root;
}