首页 > 其他分享 >平衡二叉树(AVL)的插入和删除

平衡二叉树(AVL)的插入和删除

时间:2022-09-25 18:13:54浏览次数:53  
标签:case 左子 bf EH 结点 AVL 插入 二叉树 break

在计算机科学中,AVL树是最先发明的自平衡二叉查找树。

在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。

查找、插入和删除在平均和最坏情况下都是O(log n)。

增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。

平衡因子pf  等于左子树深度减右子树深度

 

性质:

它或者是颗空树,或者是具有下列性质的二叉树:

  • 它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1。
  • 若将二叉树节点的平衡因子BF定义为该节点的左子树的深度减去它的右子树的深度,则平衡二叉树上所有节点的平衡因子只可能为-1,0,1.
  • 只要二叉树上有一个节点的平衡因子的绝对值大于1,那么这颗平衡二叉树就失去了平衡。

平衡算法总结

看完了上面的例子,我们总结一下二叉排序树的不平衡情况以及如何将其转化为平衡情况。
一般情况下,假设由于在二叉排序树上插入结点而失去平衡的最小子树根结点的指针为a(即a是离插入结点最近,且平衡因子绝对值不超过1的祖先结点),则失去平衡后进行调整的规律可以归纳为一下4种情况:
1、单向右旋平衡处理:由于在a的左子树根结点的左子树上插入结点,a的平衡因子由1增加到2,致使以a为根结点的子树失去平衡,则需要进行一次右向顺时针旋转操作。简称LL型旋转

  左左型
2、单向左旋平衡处理:由于在a的右子树根结点的右子树上插入结点,a的平衡因子由-1增加到-2,致使以a为根结点的子树失去平衡,则需要进行一次左向逆时针旋转操作。简称RR型旋转
  右右型
3、双向旋转(先左后右)平衡处理:由于在a的左子树的根结点的右子树上插入结点,a的平衡因子由1增加到2,致使a为根结点的子树失去平衡,则需要进行两次旋转(先左旋后右旋)操作。简称LR型旋转
  左右型
4、双向旋转(先右后左)平衡处理:由于在a的右子树的根结点的左子树上插入结点,a的平衡因子由1增加到2,致使a为根结点的子树失去平衡,则需要进行两次旋转(先右旋后左旋)操作。简称RL型旋转 (上图F)
  右左型

 

总结图

 
c语言代码分析:

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <stdbool.h>

#define EQ(a,b) ((a)==(b))
#define LT(a,b) ((a)<(b))
#define LQ(a,b) ((a)>(b))
#define LH +1 //左高
#define EH 0 //等高
#define RH -1 //右高

typedef struct BiTNode{
int data;
int bf; //节点平衡因子
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;


//右旋操作
/*
对p为根的二叉排序树做右旋处理
处理后p指向新的树根节点
*/
void R_Rotate(BiTree *p)
{
BiTNode *L = NULL;
L=(*p)->lchild; //L指向p的左子树根节点
(*p)->lchild=L->rchild; //L的右子树挂接为p的左子树
L->rchild=(*p); //
*p=L; //p指向新的的根节点
}

//左旋操作
void L_Rotate(BiTree *p)
{
BiTNode *R;
R=(*p)->rchild;
(*p)->rchild=R->lchild;
R->lchild=(*p);
*p=R;
}

/*
对以指针t所指结点为根的二叉树作左平衡旋转处理
包含LL旋转和LR旋转两种情况
平衡因子的改变其实很简单,自己画图就出来了
*/
void LeftBalance(BiTree *T)
{
BiTNode *lc = NULL;
BiTNode *rd = NULL;
lc = (*T)->lchild;
switch(lc->bf)
{
case LH: //LL旋转
(*T)->bf = EH;
lc->bf = EH;
R_Rotate(T);
break;

case EH: //deleteAVL需要,insertAVL用不着
(*T)->bf = LH;
lc->bf = RH;
R_Rotate(T);
break;

case RH: //LR旋转
rd = lc->rchild;
switch(rd->bf)
{
case LH:
(*T)->bf = RH;
lc->bf = EH;
break;
case EH:
(*T)->bf = EH;
lc->bf = EH;
break;
case RH:
(*T)->bf = EH;
lc->bf = LH;
break;
}
rd->bf = EH;
L_Rotate(&(*T)->lchild);//不能写L_Rotate(lc);采用的是引用参数
R_Rotate(T);
break;
}
}

/*
对以指针t所指结点为根的二叉树作右平衡旋转处理
包含RR旋转和RL旋转两种情况
*/
void RightBalance(BiTree *T)
{
BiTNode *rc = NULL;
BiTNode *ld = NULL;

rc = (*T)->rchild;
switch(rc->bf)
{
case LH: //RL旋转
ld = rc->lchild;
switch(ld->bf)
{
case LH:
(*T)->bf = EH;
rc->bf = RH;
break;
case EH:
(*T)->bf = EH;
rc->bf = EH;
break;
case RH:
(*T)->bf = LH;
rc->bf = EH;
break;
}
ld->bf = EH;
R_Rotate(&(*T)->rchild);//不能写R_Rotate(rc);采用的是引用参数
L_Rotate(T);
break;

case EH: //deleteAVL需要,insertAVL用不着
(*T)->bf = RH;
rc->bf = LH;
L_Rotate(T);
break;

case RH: //RR旋转
(*T)->bf = EH;
rc->bf = EH;
L_Rotate(T);
break;
}
}

/*
若在平衡的二叉排序树T中不存在和e有相同关键字的结点,则插入一个
数据元素为e的新结点,并返回true,否则返回false。若因插入而使二叉排序树
失去平衡,则作平衡旋转处理,布尔变量taller反映T长高与否
*/
bool InsertAVL(BiTree *T,int e,bool *taller)
{
if(!(*T)){//插入新结点,树“长高”,置taller为true
printf("e=%d\n",e);
(*T) = (BiTree)malloc(sizeof(BiTNode));
(*T)->data = e;
(*T)->lchild = (*T)->rchild = NULL;
(*T)->bf = EH;
*taller = true;
}else{
if(EQ(e,(*T)->data)) //树中已存在和有相同关键字的结点
{
*taller = false;
printf("已存在相同关键字的结点\n");
return false;
}//则不再插入
if(LT(e,(*T)->data)) //应继续在*T的左子树中进行搜索
{
if(!InsertAVL(&(*T)->lchild,e,taller))
return false;//未插入
if(*taller)
{//已插入到*T的左子树中且左子树“长高”
switch((*T)->bf) //检查*T的平衡度
{
case LH: //原本左子树比右子树高,需要作左平衡处理
LeftBalance(T);
*taller = false;
break;
case EH: //原本左子树、右子等高,现因左子树增高而使树增高
(*T)->bf = LH;
*taller = true;
break;
case RH: //原本右子树比左子树高,现左、右子树等高
(*T)->bf = EH;
*taller = false;
break;
}
}
}
else //应继续在*T的右子树中进行搜索
{
if(!InsertAVL(&(*T)->rchild,e,taller))
return false;//未插入
if(*taller)
{//已插入到*T的右子树中且右子树“长高”
switch((*T)->bf) //检查*T的平衡度
{
case LH: //原本左子树比右子树高,现左、右子树等高
(*T)->bf = EH;
*taller = false;
break;
case EH: //原本左子树、右子等高,现因右子树增高而使树增高
(*T)->bf = RH;
*taller = true;
break;
case RH: //原本右子树比左子树高,需要作右平衡处理
RightBalance(T);
*taller = false;
break;
}
}
}
}
return true;
}

/*
若在平衡的二叉排序树T中存在和e有相同关键字的结点,则删除之
并返回true,否则返回false。若因删除而使二叉排序树
失去平衡,则作平衡旋转处理,布尔变量shorter反映T变矮与否
*/
bool deleteAVL(BiTree *T, int key, bool *shorter)
{
if((*T) == NULL) //不存在该元素
{
return false; //删除失败
}
else if(EQ(key, (*T)->data)) //找到元素结点
{
BiTNode *q = NULL;
if((*T)->lchild == NULL) //左子树为空
{
q = (*T);
(*T) = (*T)->rchild;
free(q);
q = NULL;
*shorter = true;
}
else if((*T)->rchild == NULL) //右子树为空
{
q = (*T);
(*T) = (*T)->lchild;
free(q);
q = NULL;
*shorter = true;
}
else //左右子树都存在,
{
q = (*T)->lchild;
while(q->rchild)
{
q = q->rchild;
}
(*T)->data = q->data;//这部分应该是把q的数据复制给(*T)
deleteAVL(&(*T)->lchild, q->data, shorter); //在左子树中递归删除前驱结点
}
}
else if(LT(key, (*T)->data)) //左子树中继续查找
{
if(!deleteAVL(&(*T)->lchild, key, shorter))
{
return false;
}
if(*shorter)
{
switch((*T)->bf)
{
case LH:
(*T)->bf = EH;
*shorter = true;
break;
case EH:
(*T)->bf = RH;
*shorter = false;
break;
case RH:
if((*T)->rchild->bf == EH)//注意这里,画图思考一下
*shorter = false;
else
*shorter = true;
RightBalance(T); //右平衡处理
break;
}
}
}
else //右子树中继续查找
{
if(!deleteAVL(&(*T)->rchild, key, shorter))
{
return false;
}
if(*shorter)
{
switch((*T)->bf)
{
case LH:
if((*T)->lchild->bf == EH)//注意这里,画图思考一下
*shorter = false;
else
*shorter = true;
LeftBalance(T); //左平衡处理
break;
case EH:
(*T)->bf = LH;
*shorter = false;
break;
case RH:
(*T)->bf = EH;
*shorter = true;
break;
}
}
}
return true;
}


/*
*Description: 销毁平衡二叉树
*/
void destroyAVL(BiTree *T)
{
if((*T))
{
destroyAVL(&(*T)->lchild);
destroyAVL(&(*T)->rchild);
free((*T));
(*T) = NULL;
}
}


//前序遍历
void preOrderTraverse(BiTree t)
{
if(t)
{
printf("t->data=%d\n",t->data);
preOrderTraverse(t->lchild);
preOrderTraverse(t->rchild);
}
}

//中序遍历
void inOrderTraverse(BiTree t)
{
if(t)
{
inOrderTraverse(t->lchild);
printf("t->data=%d\n",t->data);
inOrderTraverse(t->rchild);
}
}

//后续遍历
void postOrderTraverse(BiTree t)
{
if(t)
{
postOrderTraverse(t->lchild);
postOrderTraverse(t->rchild);
printf("t->data=%d\n",t->data);
}
}
/*
Description:
在根指针t所指平衡二叉树中递归地查找某关键字等于key的数据元素,
若查找成功,则返回指向该数据元素结点的指针,否则返回空指针。
根据需要,也可以返回一个bool值
*/
BiTNode* searchAVL(BiTree *t, int key)
{
if(((*t) == NULL)||EQ(key,(*t)->data))
return (*t);
else if LT(key,(*t)->data) /* 在左子树中继续查找 */
return searchAVL(&(*t)->lchild,key);
else
return searchAVL(&(*t)->rchild,key); /* 在右子树中继续查找 */
}

//按树状打印输出二叉树的元素,m表示结点所在层次,初次调用时m=0
void PrintBST(BiTree *T,int m)
{
int i;
if((*T)->rchild)
PrintBST(&(*T)->rchild,m+1);
for(i = 1; i<=m; i++)
printf(" ");//打印 i 个空格以表示出层次
printf("%d\n",(*T)->data);//打印 T 元素,换行
if((*T)->lchild)
PrintBST(&(*T)->lchild,m+1);
}


int main(int argc ,char *argv[])
{
BiTree T = NULL;
bool taller;
int i = 0;
int xx_t[] = {1 ,2, 3, 5 ,7, 8 ,10 ,13 ,15 ,17 ,20 ,23 ,30 ,31 ,50 ,60 ,0};
for(i = 0;i < sizeof(xx_t)/sizeof(int);i++){
InsertAVL(&T,xx_t[i],&taller);
}
inOrderTraverse(T);
printf("++++\n");
destroyAVL(&T);
printf("++++\n");
inOrderTraverse(T);
return 0;
}

 

标签:case,左子,bf,EH,结点,AVL,插入,二叉树,break
From: https://www.cnblogs.com/ink-white/p/16728389.html

相关文章

  • LeetCode2096 从二叉树一个节点到另一个节点每一步的方向
    LeetCode2096从二叉树一个节点到另一个节点每一步的方向最近公共祖先的变形题.#Definitionforabinarytreenode.#classTreeNode:#def__init__(self,val......
  • 二叉树遍历
    应用实例代码实现publicclassBinaryTreeDemo{ publicstaticvoidmain(String[]args){ //先需要创建一颗二叉树 BinaryTreebinaryTree=newBinary......
  • 二叉树
    数组存储方式的分析优点:通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度。缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较......
  • 二叉树查找和删除指定结点
    二叉树查找指定的节点前序查找的思路1.先判断当前节点的no是否等于要查找的2.如果是相等,则返回当前节点3.如果不等,则判断当前节点的左子节点是否为空,如果不为空,则递归......
  • 力扣-226-翻转二叉树/剑指Offer-27-二叉树的镜像
    直达链接直观的想法:我可以遍历并重建,但也很明显效率低下,可能达到O(N2),但或许可能拿来当作练习,检查自己遍历/重建二叉树的基本功不过现在先想想有没有效率更高的解法,我觉......
  • 力扣106 构造二叉树
      class Solution {public:    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {    return reBuild(inorder, postorder);......
  • 力扣101 对称二叉树
        class Solution {public:    bool isSymmetric(TreeNode* root) {    if (root == nullptr)        return true;    retur......
  • 离线维护 支持插入数 的序列
    论离线维护插入单点碰到过好多类似的题,都在维护这个序列中卡住了,这是个简单易懂\(O(nlog^2_2n)\)我们考虑从后往前维护序列对于第n个插入的数,它最后所在的位置p就是预......
  • vector大小、数据存取、插入删除操作
    #include<iostream>#include<vector>usingnamespacestd;/*size();//返回容器中元素的个数empty();//判断容器是否为空resize(intnum);//重新指定容器的长度为......
  • 插入排序
    简介插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的插入排序(InsertionSorting)的基本思想是:把n个待排序的元素看成为......