一,平衡二叉树的概念
平衡二叉树是一种二叉排序树,其中每个节点的左子树和右子树的高度差最多等于1。
就是左边子树的深度减去右子树的深度的值成为平衡因子 ---- BF( balance factor),它只能是-1,0,1;
注意平衡二叉树的前提条件它必须是一颗二叉排序树。
二,平衡二叉树的一些操作
在构造平衡二叉树的时候,一但发现失衡,也就是BF不满足条件的时候就得进行调整了,发现不平衡就立即改正。
所以先得知道如何调整,而调整也就四种情况,其实懂得原理之后,也就只有两种情况了。
(1)右旋
p节点为失衡节点,lchild 为失衡节点的左孩子
- 图一发现 p 失衡
- 图二把lchild 的左孩子接到失衡节点的右孩子上去
- 图三将左孩子的右孩子变成失衡节点。
- 最后 p 改为新的根节点即可。
//右旋
void RRoate(AVLTree* p)
{
AVLTree left = (*p) -> lchild;
//将左孩子的右子树接到失衡节点的左子树去
(*p) -> lchild = left -> rchild;
left -> rchild = *p;
*p = left;
}
(2)左旋
其实左旋和右旋也是十分相似的
- 图1发现p节点失衡
- 图2将rchild的右孩子接到失衡因子的左孩子上
- 图3将right 的左孩子等于了失衡因子
- 最后p变成新的根节点
//左旋
void LRoate(AVLTree* p)
{
AVLTree right = (*p) -> rchild;
//将右孩子的左子树接到失衡节点的右子树去
(*p) -> rchild = right -> lchild;
right -> lchild = (*p);
*p = right;
}
(3)右左型
下面两张图,仔细观看,同样都是p失衡,但是rchild 所对应的孩子不同,对于图一来说,直接进行一个左旋就好了,但对于图2来说肯定不行,所以可以将图2变成图1。
直接对rchild进行一个右旋就好了。他就会变成图1。那么转成图1之后,不就是上面提到的左旋么。
//右左型的
void RL(AVLTree* p)
{
//需要先对它的右孩子进行右旋
RRoate(&(*p)->rchild);
//然后再对它进行左旋
LRoate(p);
}
(4)左右型
有了右左型,那肯定也得有左右型。
两者的p都是失衡的,而图1直接进行右旋即可,但是图2确不行,想一下上边的右左型的处理方式,这里也一样,先对lchild进行一个左旋使自己下去,孩子上来就旋转成图1的样子了。然后再对p进行右旋即可。
//左右型的
void LR(AVLTree* p)
{
//需要先对它的左孩子进行左旋
LRoate(&(*p)->lchild);
//然后再对它进行右旋
RRoate(p);
}
(5)总结
这就是关于AVL树的所有调整失衡的操作。
前两张图最简单,直接进行左旋或者右旋即可,而后两张图,得先对孩子进行旋转,在对自己进行旋转。
三,构造平衡二叉树
这代码只有左子树的插入,在此先说左子树的插入,因为左子树的插入和右子树的插入其实差不都的。
因为代码有点长,我一节一节分开了。
(1)插入
//插入数据
void ALVInsert(AVLTree* T, ElemType key,bool* taller)
{
if(*T == NULL)
{
*T = BuyNode(key);
*taller = true;
}
else
{
if( (*T) -> data == key)
{
printf("重复插入节点\n");
exit(-1);
}
if(key < (*T) -> data)
{
ALVInsert(&(*T)->lchild,key,taller);
//去左子树插入
//.............
//.............
}
else
{
ALVInsert(&(*T)->rchild,key,taller);
//去右子树插入
//.............
//.............
}
这段代码也就是一个平衡二叉树的插入代码而已,唯一不一样的就是加入了一个taller
而这个变量是为了在以后的调整平衡时候用到的。
(2)判断是否失衡
当你找到合适的位置,并且插入成功后就该判断新插入的节点是否导致二叉树失衡了。
下面是左子树的判断失衡
#define LH +1 //左高
#define RH -1 //右高
#define EH 0 //等高
//去左子树插入
if(key < (*T) -> data)
{
ALVInsert(&(*T)->lchild,key,taller);
if(*taller == true)
{
//判断平衡
switch ((*T) -> bf)
{
//无非就这三种情况么
case LH: //新节点加入进来,发现原来是左边已经是 +1 了,再加入就成 +2 了所以的调整一下
LeftBalance(T);
*taller = false;
break;
case EH: //原来是等高因为新节点的加入到了左边,所以得左边的+1
(*T) -> bf = LH;
*taller = true;
break;
case RH:
(*T) -> bf = EH; //原来是右高,但是新节点加入到了左边,所以等高了
*taller = false;
break;
}
}
}
(3)调整
进行调整就是刚开始提到那四种情况,因为这边是左调整,所以只有左左型和左右型的调整。
void LeftBalance(AVLTree *T)
{
AVLTree Lchild = NULL; //失衡因子的左孩子
AVLTree LRchild = NULL; //失衡因字的左孩子的右孩子 ····· 左右情况时候用到
Lchild = (*T) -> lchild;
switch (Lchild -> bf)
{
case LH: //左高(+1)进来的 ,发现符号还是+1 属于 左左型的
//调整平衡因子
//。。。。。。。。。。。
RRoate(T); //进行右旋
break;
case RH: //左高(+1)进来的 ,发现符号是 右高(-1) 左右型的
LRchild = Lchild -> rchild;
//调整平衡因子
//。。。。。。。。。。。
//进行左右型旋转
LR(T);
break;
}
}
可并不是简简单单的进行旋转就完事了,必须得对他们所涉及到的平衡因子也进行调整
下面才是整个左调整的全部代码
void LeftBalance(AVLTree *T)
{
AVLTree Lchild = NULL; //失衡因子的左孩子
AVLTree LRchild = NULL; //失衡因字的左孩子的右孩子 ····· 左右情况时候用到
Lchild = (*T) -> lchild;
switch (Lchild -> bf)
{
case LH: //左高(+1)进来的 ,发现符号还是+1 属于 左左型的
//调整平衡因子
(*T) -> bf = Lchild -> bf = EH; //平衡了
RRoate(T); //进行右旋
break;
case RH: //左高(+1)进来的 ,发现符号是 右高(-1) 左右型的
LRchild = Lchild -> rchild;
//调整平衡因子
switch (LRchild->bf)
{
case LH:
(*T)->bf = RH;
Lchild->bf = EH;
break;
case EH:
(*T)->bf = Lchild->bf = EH;
break;
case RH:
(*T)->bf = EH;
Lchild->bf = LH;
break;
}
LRchild -> bf = EH;
//至此调整完毕
//进行左右型旋转
LR(T);
break;
}
}
(4)总结
右子树的插入和调整与左子树非常相似。结尾处有整个AVL树的源码。
四,结尾
平衡二叉树就是将所有坏事直接扼杀在开始的时候,整篇的代码很长,很复杂,需要多多练习。
参考: 大话数据结构中的AVL树,以及这篇博客。
源码链接