平衡二叉查找树(AVL)的查找、插入、删除
一.平衡二叉查找树
平衡二叉查找树是带有平衡条件的二叉查找树。平衡条件:每个节点的左子树和右子树的高度差最多为1二叉查找树(其中空树的高度为-1)。
二、平衡二叉树算法思想
若向平衡二叉树中插入一个新结点后破坏了平衡二叉树的平衡性。首先要找出插入新结点后失去平衡的最小子树根结点的指针。然后再调整这个子树中有关结点之间的链接关系,使之成为新的平衡子树。当失去平衡的最小子树被调整为平衡子树后,原有其他所有不平衡子树无需调整,整个二叉排序树就又成为一棵平衡二叉树。
失去平衡的最小子树是指以离插入结点最近,且平衡因子绝对值大于1的结点作为根的子树。假设用A表示失去平衡的最小子树的根结点,则调整该子树的操作可归纳为下列四种情况。
当对一颗AVL树进行插入操作,可能会导致AVL树不平衡,此时,我们就需要做平衡处理,假设重新平衡的节点为Q,则不平衡会下列四种情况:
在Q的左孩子的左子树插入 (LL)
在Q的左孩子的右子树插入 (LR)
在Q的右孩子的左子树插入 (RL)
在Q的右孩子的右子树插入 (RR)
旋转算法需要借助于两个功能的辅助,一个是求树的高度,一个是求两个高度的最大值。这里规定,一棵空树的高度为-1,只有一个根节点的树的高度为0,以后每多一层高度加1。为了解决指针NULL这种情况,写了一个求高度的函数,这个函数还是很有必要的。
代码:
//只有一个根节点的高度为0,空树的高度-1
int NodeHeight(PtrNode ptrTree){
return ptrTree==NULL ? -1 : ptrTree->height;
}
int max(int a,int b){
return a<b ? b : a;
}
(1)LL型平衡旋转法
由于在A的左孩子B的左子树上插入结点F,使A的平衡因子由1增至2而失去平衡。故需进行一次顺时针旋转操作。 即将A的左孩子B向右上旋转代替A作为根结点,A向右下旋转成为B的右子树的根结点。而原来B的右子树则变成A的左子树。
对应的代码:
思路:先把B的右子树变为A的左子树,在把A作为B的右子树
//LL旋转 void RotateWithLeft(PtrNode &node){ PtrNode tempNode = node->left; //保存节点的左子树 node->left = tempNode->right; tempNode->right = node; //到此树旋转完成,更新树的深度 node->height = max(NodeHeight(node->left),NodeHeight(node->right))+1; tempNode->height = max(NodeHeight(tempNode),NodeHeight(tempNode))+1; node = tempNode; //重置根节点 }
(2)RR型平衡旋转法
由于在A的右孩子C 的右子树上插入结点F,使A的平衡因子由-1减至-2而失去平衡。故需进行一次逆时针旋转操作。即将A的右孩子C向左上旋转代替A作为根结点,A向左下旋转成为C的左子树的根结点。而原来C的左子树则变成A的右子树。
思路:先把C的左子树作为A的右子树,在把A作为C的左子树。
代码:
//RR旋转 void RotateWithRight(PtrNode &node){ PtrNode tempNode= node->right; //保存节点的右子树 node->right=tempNode->left; tempNode->left=node; node->height= max(NodeHeight(node->left),NodeHeight(node->right))+1; tempNode->height= max(NodeHeight(tempNode->left),NodeHeight(tempNode->right))+1; node=tempNode; }
(3)LR型平衡旋转法
由于在A的左孩子B的右子数上插入结点F,使A的平衡因子由1增至2而失去平衡。故需进行两次旋转操作(先逆时针,后顺时针)。即先将A结点的左孩子B的右子树的根结点D向左上旋转提升到B结点的位置,然后再把该D结点向右上旋转提升到A结点的位置。即先使之成为LL型,再按LL型处理。
如图中所示,先将圈圈的部分进行逆时针旋转(RR旋转),使之转换为LL型,再进行LL旋转;(双旋转)
代码:
//LR旋转 void DoubleRotateWithLeft(PtrNode &node){ RotateWithRight(node->left); RotateWithLeft(node); }
(4)RL型平衡旋转法
由于在A的右孩子C的左子树上插入结点F,使A的平衡因子由-1减至-2而失去平衡。故需进行两次旋转操作(先顺时针,后逆时针),即先将A结点的右孩子C的左子树的根结点D向右上旋转提升到C结点的位置,然后再把该D结点向左上旋转提升到A结点的位置。即先使之成为RR型,再按RR型处理。
如图中所示,先将圈圈的部分进行顺时针旋转(LL旋转),使之转换为RR型,再进行RR旋转;(双旋转)
代码:
//RL旋转 void DoubleRotateWithRight(PtrNode &node){ RotateWithLeft(node->right); RotateWithRight(node); }
三、AVL的查找、删除、插入
1.AVL树的类型声明
typedef int elementType; typedef struct AVLNODE{ elementType data; struct AVLNODE* left; struct AVLNODE* right; int height; //以此节点为根,树的高度(即平衡因子) }AvlNode,*PtrNode;
2.插入
AVL树的插入和二叉查找树的插入相似,只是AVL树的插入可能会破坏树的平衡性。对AVL树而言,插入完成后,需要判断树的平衡性是否被破坏,然后进行相应的旋转处理使之成为平衡树。
(1)左平衡处理
所谓左平衡处理,就是某一根结点的左子树比右子树过高,从而失去了平衡。在节点的左子树进行插入操作使此节点失去平衡,需要左平衡处理。
//左平衡处理 void LeftBalance(PtrNode &node){ PtrNode ptrTmp=node->left; if(NodeHeight(ptrTmp->left)-NodeHeight(ptrTmp->right)==-1){ DoubleRotateWithLeft(node); //LR } else{ RotateWithLeft(node); //LL } }
需要判断是在失去平衡的节点的左孩子的左子树还右子树进行插入的,左子树插入(LL旋转),右子树插入(LR旋转)。
(2)右平衡处理
类似左平衡处理,所谓右平衡处理,就是某一根结点的右子树比左子树过高,从而失去了平衡。
//右平衡处理 void RightBalance(PtrNode &node){ PtrNode ptrTmp=node->right; if(NodeHeight(ptrTmp->right)-NodeHeight(ptrTmp->left)==-1){ DoubleRotateWithRight(node); //RL } else{ RotateWithRight(node); //RR } }
需要判断是在失去平衡的节点的右孩子的左子树还右子树进行插入的,左子树插入(RL旋转),右子树插入(RR旋转)。
(3) 插入函数的编写
/** * 平衡树插入节点 */ void AVL_Insert(PtrNode &node,elementType x){ if(NULL==node){ //找到插入的节点的位置 node = (PtrNode)malloc(sizeof(AvlNode)); node->data=x; node->height=0; node->left=NULL; node->right=NULL; } else if(x<node->data){ //在左子树插入 AVL_Insert(node->left,x); if (NodeHeight(node->left)-NodeHeight(node->right)==2){ //左平衡处理 LeftBalance(node); } }else if(node->data<x){ //在右子树插入 AVL_Insert(node->right,x); if (NodeHeight(node->right)-NodeHeight(node->left)==2){ //右平衡处理 RightBalance(node); } } //更新节点的高度 if(node){ node->height = max(NodeHeight(node->left),NodeHeight(node->right))+1; } }
2. 查找
由于AVL树是有序的二叉查找树,要查找的元素比节点的数据大,则在右子树查找;比节点的数据小,在左子树中查找
PtrNode AVL_Search(PtrNode &node,elementType x){ if(node == NULL){ return NULL; }else if(node->data>x){ //在左子树上递归查找 return AVL_Search(node->left,x); }else if(node->data<x){ //在右子树上递归查找 return AVL_Search(node->right,x); }else{ //相等 return node; } }
3.删除
对二叉查找树,我们知道删除的结点可能有三种情况:(1)为叶子结点,(2)左子树或右子树有一个为空,(3)左右子树都不空。假设删除节点为A。
对于(1):直接删除即可。
对于(2):删除的方法,A的父节点绕过A节点使其指向A左子树(右子树为空)、右子树(左子树为空时)。
对于(3):一般的删除策略:用A的左子树最大数据或右子树最小数据(假设B节点)代替A节点的数据,并递归地删除B节点。
AVL的树的删除策略与二叉查找树的删除策略相似,只是删除节点后造成树失去平衡性,需要做平衡处理。
/** * 平衡树删除元素(先递归查找,再删除释放节点) */ void AVL_Delete(PtrNode &node,elementType x){ if(node == NULL){ return; } if(node->data>x){ //递归在左子树中查找 AVL_Delete(node->left,x); //左子树上删除一个节点后,可能会导致在某个节点上的右子树比左子树的高度多2个 if(NodeHeight(node->right)-NodeHeight(node->left) == 2){ RightBalance(node); } }else if(node->data<x){ //递归在右子树中查找 AVL_Delete(node->right,x); if(NodeHeight(node->left)-NodeHeight(node->right) == 2){ LeftBalance(node); } }else{ //查找到了删除节点 if(node->left == NULL){ //左子树为空 PtrNode ptrTempNode = node; node = node->right; free(ptrTempNode); }else if(node->right == NULL){ //右子树为空 PtrNode ptrTempNode = node; node = node->left; free(ptrTempNode); }else{ //一般的删除策略是左子树的最大数据 或 右子树的最小数据 代替该节点(这里采用查找左子树最大数据来代替) PtrNode ptrTempNode = node->left; //从左子树开始,依次向右查找,找出左子树的最大值 if(ptrTempNode->right!=NULL){ ptrTempNode=ptrTempNode->right; } node->data = ptrTempNode->data; //找到了左子树的最大值,赋值给当前node节点 AVL_Delete(node->left,ptrTempNode->data); //递归的删除该节点 } } }
4.遍历打印输出(中序)
/** * 中序遍历 */ void midSearchPrint(PtrNode &root){ if(root == NULL){ return; } midSearchPrint(root->left); printf("%d ",root->data); midSearchPrint(root->right); }
5.测试代码
int main(int argc,char* argv[]){ PtrNode root =NULL; /**插入操作,构建平衡二叉树**/ AVL_Insert(root,4); AVL_Insert(root,1); AVL_Insert(root,6); AVL_Insert(root,9); AVL_Insert(root,3); AVL_Insert(root,7); AVL_Insert(root,8); AVL_Insert(root,15); AVL_Insert(root,13); /**中序遍历二叉树(测试数据)**/ midSearchPrint(root); fprintf(stdout,"\n"); if(root!=NULL){ fprintf(stdout,"树的深度:%d\n",root->height); } /**删除节点操作**/ AVL_Delete(root,15); AVL_Delete(root,4); AVL_Delete(root,2); AVL_Delete(root,6); AVL_Delete(root,8); /**测试数据**/ midSearchPrint(root); fprintf(stdout,"\n"); /**查找节点操作**/ PtrNode searchNode = AVL_Search(root,9); if(searchNode == NULL){ printf("没有查找到节点\n"); }else{ printf("所在节点的高度(以此节点为根,此高度即是该节点的平衡因子): %d\n",searchNode->height); if(searchNode->left != NULL){ printf("所在节点的左孩子: %d\n",searchNode->left->data); } if(searchNode->right != NULL){ printf("所在节点的右孩子: %d\n",searchNode->right->data); } } return 0; }
运行结果截图: