简介
平衡二叉树,又称为AVL树,为了优化排序二叉树极端情况(从小到大或从大到小插入,呈现链状)而衍生出来的结构
特点
1.基于排序二叉树
2.任意节点的左右子树的高度差都小于2
因此,同排序二叉树一样,平衡二叉树只需要在插入和删除时,进行平衡,使得左右子树高度差均小于1即可
查询
所有二叉排序树的查询过程都是一致的,包括平衡二叉树和红黑树,都是通过二分法向下查询,可以参考二叉排序树的查询
节点基本数据结构
typedef struct TreeNode {
int data;
struct TreeNode *parentNode;
struct TreeNode *l, *r;
}LSTreeNode;
复制代码
旋转
旋转是当增删处理完成之后,无法自平衡的最终解决手段,平衡二叉树和红黑树都会用到
因此旋转是了解插入和删除之前,必须要知道的知识点,可参考上一章二叉树的旋转
左旋代码
void swapLeftRotation(LSTreeNode *parentNode, LSTreeNode *rightNode) {
int data = parentNode->data;
parentNode->data = rightNode->data;
rightNode->data = data;
parentNode->r = rightNode->r;
rightNode->r->parentNode = parentNode;
rightNode->r = rightNode->l;
rightNode->l = parentNode->l;
if (parentNode->l) parentNode->l->parentNode = rightNode;
parentNode->l = rightNode;
}
复制代码
右旋代码
void swapRightRotation(LSTreeNode *parentNode, LSTreeNode *leftNode) {
int data = parentNode->data;
parentNode->data = leftNode->data;
leftNode->data = data;
parentNode->l = leftNode->l;
leftNode->l->parentNode = parentNode;
leftNode->l = leftNode->r;
leftNode->r = parentNode->r;
if (parentNode->r) parentNode->r->parentNode = leftNode;
parentNode->r = leftNode;
}
复制代码
失衡类型与平衡手段
平衡二叉树插入和删除之前还必须要知道的知识点:失衡类型和平衡手段
平衡二叉树一旦处于失衡状态,会出现四种平衡类型:LL型、LR型、RR型、RL型
其中LL型、LR型分别于RR型、RL型为对称关系
LL型
即:指定节点的左子节点的左子节点分支,比指定节点的右分支高度要高,从而导致的失衡状态
如下图所示,则为LL型失衡状态常见的几种状态
如上图所示,他们的失衡对照点均在A节点处,左右子树高度差达到2,其中子节点为空的(A的右子节点),高度则为0
注意:如果B、F、G节点同时不存在,那么结果是一样的只不过是携带空节点的旋转而已
处理方案:直接右旋(即:对AC进行右旋)
依次的变换结果如下图所示
LR型
即:指定节点的左子节点的右子节点分支,比指定节点的右分支高度要高,从而导致的失衡状态
如下图所示,则为LR型失衡状态常见的几种状态
如上图所示,他们的失衡对照点均在A节点处,左右子树高度差达到2,(发现直接对AC右旋无法平衡该状态)
注意:如果B、D、G节点同时不存在,那么结果是一样的只不过是携带空节点的旋转而已,这里就不做介绍了
处理方案:先左旋后右旋(即:先对CF进行左旋,在对AF进行右旋)
依次的变换结果如下图所示
RR型
即:指定节点的有子节点的有子节点分支,比指定节点的左分支高度要高,从而导致的失衡状态
如下图所示,则为RR型失衡状态常见的几种状态
如上图所示,他们的失衡对照点均在A节点处,左右子树高度差达到2,其中子节点为空的(A的左子节点),高度则为0
注意:如果B、F、G节点同时不存在,那么结果是一样的只不过是携带空节点的旋转而已
处理方案:直接左旋(即:对AC进行左旋)
依次的变换结果如下图所示
RL型
即:指定节点的右子节点的左子节点分支,比指定节点的左分支高度要高,从而导致的失衡状态
如下图所示,则为RL型失衡状态常见的几种状态
如上图所示,他们的失衡对照点均在A节点处,左右子树高度差达到2,(发现直接对AC左旋无法平衡该状态)
注意:如果B、D、G节点同时不存在,那么结果是一样的只不过是携带空节点的旋转而已,这里就不做介绍了
处理方案:先右旋后左旋(即:先对CF进行右旋,在对AF进行左旋)
依次的变换结果如下图所示
获取高度
平衡二叉树通过检测左右分支高度差来判别是否失衡,因此需要获取节点的高度(深度)
int getHeight(LSTreeNode *node) {
int heightX = 0;
while (node) {
heightX++;
node = node->l ? node->l : node->r;
}
return heightX;
}
复制代码
插入
平衡二叉树插入的过程,依赖排序二叉树的插入,即:插入后维持平衡
正常插入一个元素后,参考失衡的四种状态,对二叉树进行检测和平衡
常见的插入后失衡结构(以插入左侧黑色节点I为例,局部相似记得忽略,避免陷入过多情形陷阱)如下:
平衡步骤
1.正常往排序二叉树插入一个元素,并标记好父节点
2.从插入节点开始,查看祖父点是否存在(父节点一定存在,否则为根节点插入),如果不存在结束(到达根节点),
3.对比祖父节点左右分支是否高度差达到失衡条件,该祖父节点这里成为称为失衡对照点
4.如果祖父节点不存在失衡状态,则父节点变成插入节点,祖父节点变成父节点,继续自下向上查找(保证本次插入节点与其他分支节点不失衡,直到根节点),重复步骤2; 否则进行下一步
5.通过比较的祖父节点的左右分差结果,判断哪边高则是插入边
6.插入点在失衡点左侧:则查看是LL型,还是LR型,此时通过插入点的父节点,查看右孩子是否是插入节点,如果是则为LR型、否则为LL型(可以参考上面的图);
7.如果为LL型,直接对插入节点的父节点和插入节点的祖父节点进行右旋即可;如果为LR型,先对插入节点的父节点和插入节点进行左旋,然后在对父节点和祖父节点右旋即可
8.插入点在失衡点右侧:则查看是RR型,还是RL型,此时通过插入点的父节点,查看左孩子是否是插入节点,如果是则为RL型、否则为RR型(可以参考上面的图);
9.如果为RR型,直接对插入节点的父节点和插入节点的祖父节点进行左旋即可;如果为RL型,先对插入节点的父节点和插入节点进行右旋旋,然后在对点和祖父节点左旋即可
代码实现
#pragma mark --平衡二叉树的插入(再二叉排序树的基础上进行改进)
LSTreeNode *insertData(LSTreeNode *root, int data) {
LSTreeNode *node = (LSTreeNode *)malloc(sizeof(LSTreeNode));
node->data = data;
if (!root) return node;
LSTreeNode *p = root, *s = NULL;//s为插入节点的位置
while (p) {
s = p;
if (p->data == data) {
free(node);
return root; //重复可以进行替换值结束
}
if (p->data > data) {
p = p->l;
}else {
p = p->r;
}
}
_Bool isLeft = s->data > data;
if (isLeft) {
s->l = node;
}else {
s->r = node;
}
node->parentNode = s; //设置父节点
//正常的插入一个新节点到此结束
//此时需要开始平衡二叉树
insertBalanceNode(root, s, node);
return root;
}
//平衡插入的新节点, 以插入节点父节点为基准
LSTreeNode *insertBalanceNode(LSTreeNode *root, LSTreeNode *parentNode, LSTreeNode *insertNode) {
LSTreeNode *grandNode = parentNode->parentNode;
//不存在则到达根节点,不满足两层,不需要平衡
if (!grandNode) return root;
//计算左右分支差
int heightX = getHeight(grandNode->l) - getHeight(grandNode->r);
//分支高度差小于2才需要直接平衡,否则一直平衡到树根
if (abs(heightX) < 2) {
//需要自下而上平衡到根节点,只平衡一个树是不行的,可能当前树内部本身就是平衡的,但是和另外一个数出现了高度差
//父节点代替当前节点继续向上平衡,毕竟要一层一层向上走
return insertBalanceNode(root, grandNode, parentNode);
}
//此时需要直接平衡平衡
//注意细节:因为是插入节点,node一定是指向插入的分支方向的那个根节点的子节点
if (heightX > 0) {
//插入分支属于左分支,查看左分支是插入到了node左边还是右边
//处理不平衡
if (parentNode->r == insertNode) {
//如果是从右侧插入,先左旋
swapLeftRotation(parentNode, insertNode);
}
//直接整体右旋即可平衡
swapRightRotation(grandNode, parentNode);
}else {
//右侧分支较多
if (parentNode->l == insertNode) {
//如果是从左侧插入,先左旋
swapRightRotation(parentNode, insertNode);
}
swapLeftRotation(grandNode, parentNode);
}
//平衡完一次后消除当前插入造成的结果,直接结束即可
return root;
}
复制代码
删除
平衡二叉树插删除的过程,依赖排序二叉树的删除,即:插入后维持平衡
正常删除一个元素后,需要处理好其父子节点的交接(尤其是相比排序二叉树新增加的父节点的交接),再参考失衡的四种状态,对二叉树进行检测和平衡
常见的删除结构(以删除右侧黑色节点X为例,局部相似记得忽略,避免陷入过多情形陷阱)如下:
平衡步骤
1.正常往排序二叉树中删除该元素,并正确维护好父子节点交接
2.查看删除节点的父节点是否存在,如果不存在则删除的纯根节点,结束,否则进行下一步
3.保存删除节点的兄弟节点和父节点,开始平衡排序二叉树
4.如果父节点不存在,则已经平衡或者对比到根节点了,则不需要平衡直接结束即可
5.对比父节点左右分支是否高度差达到失衡条件,该父节点这里成为称为失衡对照点
6.如果父节点不存在失衡状态,则父节点变成兄弟节点,祖父节点变成父节点,继续自下向上查找(保证本次删除与其他分支节点不失衡,直到根节点),重复步骤2; 否则进行下一步
7.删除节点的兄弟节点在失衡点左侧:则查看是LL型,还是LR型,由于无法直接确定失衡的类型,因此需要通过兄弟节点的左右分支高度差,来确定失衡类型(可对比插入),如果左边高度小于右侧,则为LR型,否则默认为LL型(一样高度的情况怎么处理都行,以LL最简单)
7.如果为LL型,直接对插入节点的父节点和插入节点的祖父节点进行右旋即可;如果为LR型,先对插入节点的父节点和插入节点进行左旋,然后在对父节点和祖父节点右旋即可
8.删除节点的兄弟节点在失衡点右侧:则查看是RR型,还是RL型,由于无法直接确定失衡的类型,因此需要通过兄弟节点的左右分支高度差,来确定失衡类型(可对比插入),如果左边高度大于右侧,则为RL型,否则默认为RR型(一样高度的情况怎么处理都行,以LL最简单)
9.如果为RR型,直接对插入节点的父节点和插入节点的祖父节点进行左旋即可;如果为RL型,先对插入节点的父节点和插入节点进行右旋旋,然后在对点和祖父节点左旋即可
#pragma --mark --平衡二叉树的删除(在删除的基础上进行平衡)
LSTreeNode *deleteData(LSTreeNode *root, int data) {
if (!root) return NULL;
LSTreeNode *p = root, *s = NULL;
while (p) {
if (p->data == data) break;
s = p;
if (p->data < data) {
p = p->r;
}else {
p = p->l;
}
}
if (!p) return root; //没找到删除失败
if (!p->l && !p->r) {
//为叶子节点
if (p == root) root = NULL;
else if (s->l == p) s->l = NULL;
else s->r = NULL;
}else if (!p->l && p->r) {
//只有右节点
if (p == root) root = s->r;
else if (s->l == p) s->l = p->r;
else s->r = p->r;
if (p->r) p->r->parentNode = s;
}else if (p->l && !p->r) {
//只有左节点
if (p == root) root = s->l;
else if (s->l == p) s->l = p->l;
else s->r = p->l;
if (p->l) p->l->parentNode = s;
}else {
//找到左侧最后一个节点替换掉删除节点内容,然后删除左节点最后一个内容
LSTreeNode *q = p; //记录被删除的及诶单
s = p; //保存删除点的父节点
p = p->l; //找到要替换的删除节点的节点
while (p->r) {
q = p;
p = p->r;
}
q->data = p->data;
if (s == q) {
s->l = p->l;
if (p->l) p->l->parentNode = s;
}
else {
q->r = p->l;
if (p->l) p->l->parentNode = q;
}
}
//父节点存在才有平衡必要(不存在说明删除的为根节点)
if (s) {
LSTreeNode *brotherNode = s->l == p ? s->r : s->l;
root = deleteBalanceNode(root, s, brotherNode);
}
free(p);
return root;
}
//平衡二叉树的删除(在删除的基础上进行平衡),从删除节点向上平衡,那么与插入相似,当前分支作为缺少的分支,找另外一个分支对比平衡,如果平衡,那么当前分支取父节点继续向上递归平衡即可
LSTreeNode *deleteBalanceNode(LSTreeNode *root, LSTreeNode *parentNode, LSTreeNode *brotherNode) {
//不存在则到达根节点,不满足两层,不需要平衡
if (!parentNode) return root;
int heightX = getHeight(parentNode->l) - getHeight(parentNode->r);
if (abs(heightX) < 2) {
//平衡这的直接向上对比一直倒根节点方可确认没有失衡
//注意:可以通过父节点和删除节点自动排除同时存在两个节点情况(同时左右孩子删除完不影响平衡),其实起始处判断一次,可以优化效率
return deleteBalanceNode(root, parentNode->parentNode, parentNode);
}
//出现了不平衡开始平衡,部分同插入一致
if (heightX > 0) {
//右侧为删除节点所在树枝,左侧偏高,从左侧平衡
if (getHeight(brotherNode->l) - getHeight(brotherNode->r) < 0) {
//此时为LR型,先局部左旋,此时右侧节点一定存在
swapLeftRotation(brotherNode, brotherNode->r);
}
//直接整体右旋即可平衡
swapRightRotation(parentNode, brotherNode);
}else {
//左侧为删除节点所在树枝,右侧偏高,从右侧平衡
if (getHeight(brotherNode->l) - getHeight(brotherNode->r) > 0) {
//此时为RL型,先局部右旋,此时左侧节点一定存在
swapRightRotation(brotherNode, brotherNode->l);
}
//直接整体左旋即可平衡
swapLeftRotation(parentNode, brotherNode);
}
//平衡完毕后即可直接结束,每一次删除最多只会出现一次失衡
return root;
}