C平衡二叉树(AVL)创建和删除
AVL是最先发明的自平衡二叉查找树算法。在AVL中任何节点的两个儿子子树的高度最大差别为一,所以它也被称为高度平衡树,n个结点的AVL树最大深度约1.44log2n。查找、插入和删除在平均和最坏情况下都是O(log n)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。
定义
用LH,EH,RH分别表示左子树高,等高,右子树高,即平衡因子1、0、-1
#include <stdio.h> #include <stdlib.h> #include <stdbool.h> #define LH 1 // 左高 #define EH 0 // 等高 #define RH -1 // 右高 typedef struct TreeNode{ int data; int bf; struct TreeNode *left, *right; }TreeNode;
旋转处理
左旋和右旋,记住“左逆右顺”就可以
/************************************************ * 对以*p为根的二叉排序树作右旋处理,处理之后p指向新的树根结点, * A B * / / \ * B 旋转后变为 C A * / \ / * C D D * 即旋转处理之前的左子树的结点。 ************************************************/ void r_rotate(TreeNode **p){ TreeNode *l = (*p)->left; (*p)->left = l->right; l->right = (*p); *p = l; } /************************************************ * 对以*p为根的二叉排序树作左旋处理,处理之后p指向新的树根结点, * A B * \ / \ * B 旋转后变为 A D * / \ \ * C D C * 即旋转处理之前的右子树的结点。 ************************************************/ void l_rotate(TreeNode **p){ TreeNode *r = (*p)->right; (*p)->right = r->left; r->left = (*p); *p = r; }
左平衡处理
所谓左平衡处理,就是某一根结点的左子树比右子树高,从而失去了平衡。
(1)插入时如果需要左平衡处理,根结点左子树根平衡因子只可能为LH和RH。
(2)删除和插入不同,根结点左子树根的平衡因子三种情况都可能出现,因为是删除根结点右子树中的结点从而引起左子树过高,在删除前,根结点左子树根的平衡因子是可以为EH的,此种情况同样是对根结点做简单右旋处理。
/************************************************ * 对*t所指结点为根的二叉树作左平衡处理 ************************************************/ void left_balance(TreeNode **t){ TreeNode *l, *lr; l = (*t)->left; switch(l->bf){ case LH: (*t)->bf = l->bf = EH; r_rotate(t); break; case RH: lr = l->right; switch(lr->bf){ case LH: (*t)->bf = RH; l->bf = EH; break; case RH: (*t)->bf = EH; l->bf = LH; break; case EH: (*t)->bf = l->bf = EH; break; } lr->bf = EH; l_rotate(&(*t)->left); r_rotate(t); break; case EH: // 删除节点时用到 (*t)->bf = LH; l->bf = RH; r_rotate(t); break; } }
右平衡处理
类似左平衡处理,所谓右平衡处理,就是某一根结点的右子树比左子树高,从而失去了平衡。
(1)插入时如果需要右平衡处理,根结点右子树根平衡因子只可能为LH和RH。
(2)删除和插入不同,根结点右子树根的平衡因子三种情况都可能出现,因为是删除根结点左子树中的结点从而引起右子树过高,在删除前,根结点右子树根的平衡因子是可以为EH的,此种情况同样是对根结点做简单左旋处理。
/************************************************ * 对*t所指结点为根的二叉树作右平衡处理 ************************************************/ void right_balance(TreeNode **t){ TreeNode *r, *rl; r = (*t)->right; switch(r->bf){ case RH: (*t)->bf = r->bf = EH; l_rotate(t); break; case LH: rl = r->left; switch(rl->bf){ case RH: (*t)->bf = LH; r->bf = EH; break; case LH: (*t)->bf = EH; r->bf = RH; break; case EH: (*t)->bf = r->bf = EH; break; } rl->bf = EH; r_rotate(&(*t)->right); l_rotate(t); break; case EH: // 删除节点时用到 (*t)->bf = RH; r->bf = LH; l_rotate(t); break; } }
插入处理
在插入一个元素时,总是插入在一个叶子结点上。采用递归插入,也就是不断搜索平衡二叉树,找到一个合适的插入点(相同关键字不插入)。插入后,引起的第一个不平衡的子树的根结点,一定是在查找路径上离该插入点最近的。
/************************************************ * 若在平衡的二叉排序树t中不存在和e有相同关键字的结点,则插入一个 * 数据元素为e的新结点,并返回true,否则返回false。若因插入而使二叉排序树 * 失去平衡,则作平衡旋转处理,布尔变量taller反映t长高与否 ************************************************/ bool insertAVL(TreeNode **t,int e,bool *taller){ if( ! *t ){ *t = (TreeNode *)malloc(sizeof(TreeNode)); (*t)->data = e; (*t)->left = (*t)->right = NULL; (*t)->bf = EH; *taller = true; } else{ if( e == (*t)->data ){ *taller = false; return false; } if( e < (*t)->data ){ // 在左子树中查找插入点 if( ! insertAVL(&(*t)->left,e,taller)){ // 左子树插入失败 return false; } if( *taller ){ // 左子树插入成功,且树增高 switch( (*t)->bf ){ case LH: // 原来t的左子树高于右子树 left_balance(t);// 左平衡处理 *taller = false; break; case EH: // 原来t的左子树和右子树等高 (*t)->bf = LH; // 现在左子树高 *taller = true; // 整棵树增高 break; case RH: // 原来t的右子树高 (*t)->bf = EH; // 现在等高 *taller = false;// 树未增高 break; } } } else{ // 在右子树中查找插入点 if( ! insertAVL(&(*t)->right,e,taller)){ // 右子树插入失败 return false; } if( *taller ){ // 右子树插入成功,且树增高 switch( (*t)->bf ){ case RH: // 原来t的右子树高 right_balance(t);// 右平衡处理 *taller = false; break; case EH: // 原来t的左子树和右子树等高 (*t)->bf = RH; // 现在右子树高 *taller = true; break; case LH: // 原来t的左子树高 (*t)->bf = EH; // 现在等高 *taller = false; break; } } } } return true; }
删除处理
删除和插入不同的是,删除的结点不一定是叶子结点,可能是树中的任何一个结点。
在操作二叉查找树时,我们知道删除的结点可能有三种情况:(1)为叶子结点(2)左子树或右子树有一个为空(3)左右子树都不空。
对第三种情况的处理这里我们采用删除前驱的方式。递归删除,判断删除后树是否“变矮”了,然后进行相应的处理。对(1)(2)中情况,很好处理,树的确是“变矮”了。对于第(3)种情况,我们不能直接找到前驱结点,然后把数据拷贝到原本要删除的根结点,最后直接删除前驱结点。因为这么做,我们无法判断原先根结点子树高度的变化情况。所以我们在找到前驱结点后,不是直接删除,而是采用在根结点左子树中递归删除前驱的方式。
/************************************************ * 若在平衡的二叉排序树t中存在和e有相同关键字的结点,则删除 * 并返回true,否则返回false。若因删除而使二叉排序树 * 失去平衡,则作平衡旋转处理,布尔变量lower反映t变矮与否 ************************************************/ bool deleteAVL(TreeNode **t,int key,bool *lower){ if( ! *t ) return false; TreeNode *q = NULL; if( key == (*t)->data ){ if( NULL == (*t)->left ){ // 左子树为空,直接连接右节点 q = (*t); (*t) = q->right; free(q); *lower = true; } else if( NULL == (*t)->right ){ // 右子树为空,直接连接左节点 q = (*t); (*t) = q->left; free(q); *lower = true; } else{ q = (*t)->left; while( q->right ){ q = q->right; } (*t)->data = q->data; deleteAVL( &(*t)->left,q->data,lower); // 在左子树中递归删除前驱节点 } } else if( key < (*t)->data ){ if( !deleteAVL( &(*t)->left,key,lower) ){ return false; } if( *lower ){ switch( (*t)->bf ){ case LH: (*t)->bf = EH; *lower = true; break; case EH: (*t)->bf = RH; *lower = false; break; case RH: right_balance(t); if( EH == (*t)->right->bf ){ *lower = false; } else{ *lower = true; } break; } } } else{ if( !deleteAVL( &(*t)->right,key,lower) ){ return false; } if( *lower ){ switch( (*t)->bf ){ case RH: (*t)->bf = EH; *lower = true; break; case EH: (*t)->bf = LH; *lower = false; break; case LH: left_balance(t); if( EH == (*t)->left->bf ){ *lower = false; } else{ *lower = true; } break; } } } return true; }
遍历和查找
/************************************************ * 在*t所指平衡二叉树中递归查找等于key的数据元素, * 若查找成功,则返回true ************************************************/ bool searchAVL(TreeNode *t,int key,TreeNode *f,TreeNode **p){ if( !t ){ *p = f; return false; } else if( key == t->data ){ *p = t; return true; } else if( key < t->data ){ return searchAVL( t->left,key,t,p); } else{ return searchAVL( t->right,key,t,p); } } /************************************************ * 前序遍历 ************************************************/ void PreOrderTraverse(TreeNode *t){ //printf("in PreOrderTraverse\n"); if( NULL == t ) return; printf("%2d ",t->data); PreOrderTraverse(t->left); PreOrderTraverse(t->right); } /************************************************ * 前序遍历平衡因子 ************************************************/ void PreOrderTraverse_bf(TreeNode *t){ //printf("in PreOrderTraverse\n"); if( NULL == t ) return; printf("%2d ",t->bf); PreOrderTraverse_bf(t->left); PreOrderTraverse_bf(t->right); } /************************************************ * 中序遍历 ************************************************/ void InOrderTraverse(TreeNode *t){ if( NULL == t ) return; InOrderTraverse(t->left); printf("%2d ",t->data); InOrderTraverse(t->right); } /************************************************ * 后序遍历 ************************************************/ void PostOrderTraverse(TreeNode *t){ if( NULL == t ) return; PostOrderTraverse(t->left); PostOrderTraverse(t->right); printf("%2d ",t->data); }
测试代码和用例
int main(){ int i = 0, key = 1; int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -2, }; TreeNode *t = NULL, *p = NULL; bool taller = false; bool lower = false; for( ; i < sizeof(arr)/sizeof(int); i++ ){ insertAVL(&t,arr[i],&taller); } printf("value %d is%s in the tree\n",key,( searchAVL(t,key,NULL,&p) ) ? "" : " not"); key = 15; printf("value %d is%s in the tree\n",key,( searchAVL(t,key,NULL,&p) ) ? "" : " not"); printf("\nPreOrderTraverse:\n"); PreOrderTraverse(t); printf("\nPreOrderTraverse_bf:\n"); PreOrderTraverse_bf(t); printf("\nInOrderTraverse:\n"); InOrderTraverse(t); printf("\nafter delete 3:\n"); deleteAVL( &t,3,&lower); PreOrderTraverse(t); printf("\n"); }
value 1 is in the tree
value 15 is not in the tree
PreOrderTraverse:
4 2 1 -2 3 8 6 5 7 9 10
PreOrderTraverse_bf:
0 1 1 0 0 0 0 0 0 -1 0
InOrderTraverse:
-2 1 2 3 4 5 6 7 8 9 10
after delete 3:
4 1 -2 2 8 6 5 7 9 10