平衡二叉树(AVL树)小结
一、定义概览
AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下都是O(log n)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。
节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。带有平衡因子1、0或 -1的节点被认为是平衡的。带有平衡因子 -2或2的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。
一般我们所看见的都是排序平衡二叉树
二、一般性质
AVL树具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。在平衡二叉搜索树中,我们可以看到,其高度一般都良好地维持在O(log2n),其各操作的时间复杂度(O(log2n))同时也由此而决定,大大降低了操作的时间复杂度。另外,最小二叉平衡树的节点的公式如下 F(n)=F(n-1)+F(n-2)+1 这个类似于一个递归的数列。
三、一般操作
AVL树的基本操作一般涉及运作同在不平衡的二叉查找树所运作的同样的算法。但是要进行预先或随后做一次或多次所谓的"AVL旋转"。
1.插入
向AVL树插入可以通过如同它是未平衡的二叉查找树一样把给定的值插入树中,接着自底向上向根节点折回,于在插入期间成为不平衡的所有节点上进行旋转来完成。因为折回到根节点的路途上最多有1.5乘log n个节点,而每次AVL旋转都耗费恒定的时间,插入处理在整体上耗费O(log n) 时间
2.删除
从AVL树中删除可以通过把要删除的节点向下旋转成一个叶子节点,接着直接剪除这个叶子节点来完成。因为在旋转成叶子节点期间最多有log n个节点被旋转,而每次AVL旋转耗费恒定的时间,删除处理在整体上耗费O(log n) 时间。
3.查找
可以像普通二叉查找树一样的进行,所以耗费O(log n)时间,因为AVL树总是保持平衡的。不需要特殊的准备,树的结构不会由于查找而改变。(这是与伸展树查找相对立的,它会因为查找而变更树结构。)
四、左右旋操作
1.单向右旋平衡处理LL:由于在*a的左子树根节点的左子树上插入节点,*a的平衡因子由1增至2,致使以*a为根的子树失去平衡,则需进行一次右旋转操作;
2.单向左旋平衡处理RR:由于在*a的右子树根节点的右子树上插入节点,*a的平衡因子由-1变为-2,致使以*a为根的子树失去平衡,则需进行一次左旋转操作
3.双向旋转(先左后右)平衡处理LR:由于在*a的左子树根节点的右子树上插入节点,*a的平衡因子由1增至2,致使以*a为根的子树失去平衡,则需进行两次旋转(先左旋后右旋)操作
4.双向旋转(先右后左)平衡处理RL:由于在*a的右子树根节点的左子树上插入节点,*a的平衡因子由-1变为-2,致使以*a为根的子树失去平衡,则需进行两次旋转(先右旋后左旋)操作。
五、相关代码实现(代码参考网络资料,有修改,未测试)
1.基本结构体及变量
#define LH +1 // 左高 #define EH 0 // 等高 #define RH -1 // 右高 // 平衡二叉树的类型 struct AVLNode { int data; int bf; //bf结点的平衡因子,只能够取0,-1,1,它是左子树的深度减去右子树的深度得到的 struct AVLNode *lchild,*rchild; // 左、右孩子指针 };
2.右旋操作:
void R_Rotate(AVLNode *&p) { AVLNode *lc=p->lchild; // lc指向p的左子树根结点 p->lchild=lc->rchild; // lc的右子树挂接为p的左子树 lc->rchild=p; p=lc; // p指向新的根结点 }
3.左旋操作:
void L_Rotate(AVLNode *&p) { AVLNode *rc=p->rchild; // rc指向p的右子树根结点 p->rchild=rc->lchild; // rc的左子树挂接为p的右子树 rc->lchild=p; p=rc; // p指向新的根结点 }
4.对树进行左平衡操作:
void LeftBalance(AVLNode *&T) { AVLNode *lc,*rd; lc=T->lchild; // lc指向T的左子树根结点 switch(lc->bf) { case LH: // 1 新结点插入在*T的左孩子的左子树上,要作单右旋处理 T->bf=lc->bf=EH; R_Rotate(T); break; case RH: // -1 新结点插入在*T的左孩子的右子树上,要作双旋处理 rd=lc->rchild; // rd指向*T的左孩子的右子树根 switch(rd->bf) { // 根据旋转后的效果去修改T及其左孩子的平衡因子 以下右旋转类似 case LH: T->bf=RH; lc->bf=EH; break; case EH: T->bf=lc->bf=EH; break; case RH: T->bf=EH; lc->bf=LH; } rd->bf=EH; L_Rotate(T->lchild); R_Rotate(T); } }
5.对树进行右平衡操作:
void RightBalance(AVLNode *&T) { AVLNode *rc,*rd; rc=T->rchild; switch(rc->bf) { case RH: T->bf=rc->bf=EH; L_Rotate(T); break; case LH: rd=rc->lchild; switch(rd->bf) { case RH: T->bf=LH; rc->bf=EH; break; case EH: T->bf=rc->bf=EH; break; case LH: T->bf=EH; rc->bf=RH; } rd->bf=EH; R_Rotate(T->rchild); L_Rotate(T); } }
6.插入操作:
int InsertAVL(AVLNode *&T,int data,int *taller) { if(!T) //此时为初始情况 或已找到适当位置插入新数据 { T=(AVLNode *)malloc(sizeof(AVLNode)); T->data=data; T->lchild=T->rchild=NULL; T->bf=EH; *taller=1; } else { if(data == T->data) { *taller=0; return 0; } if(data < T->data) { if(!InsertAVL(T->lchild,data,taller)) return 0; if(*taller) switch(T->bf) { case LH: //插入前做左子树比右子树高,插入后,左子树已经长高, 排序树失去平衡 LeftBalance(T); //对排序树进行右平衡操作 *taller=0; break; case EH: T->bf=LH; *taller=1; //这里标识有所长高 实际上此时父节点或者祖父结点的平衡因子的绝对值已经大于1 break; case RH: //插入前右子树比左子树高,插入后左右子树深度相等 T->bf=EH; *taller=0; //标志没长高 } } else { if(!InsertAVL(T->rchild,data,taller)) return 0; if(*taller) switch(T->bf) { //插入前左子树比右子树高,插入后左右子树深度相等 case LH: T->bf=EH; //标志没长高 *taller=0; break; case EH: //这里标识有所长高 实际上此时父节点或者祖父节点的平衡因子的绝对值已经大于1 T->bf=RH; *taller=1; break; case RH: //插插入前做右子树比左子树高,插入后,右子树已经长高, 排序树失去平衡 RightBalance(T); //对排序树进行右平衡操作 *taller=0; } } } return 1; }
7.删除操作。。待续