平衡二叉查找树(AVL)的查找,插入,删除

一.平衡二叉查找树

平衡二叉查找树是带有平衡条件的二叉查找树。平衡条件:每个节点的左子树和右子树的高度差最多为1二叉查找树(其中空树的高度为-1)。

二、平衡二叉树算法思想


    若向平衡二叉树中插入一个新结点后破坏了平衡二叉树的平衡性。首先要找出插入新结点后失去平衡的最小子树根结点的指针。然后再调整这个子树中有关结点之间的链接关系,使之成为新的平衡子树。当失去平衡的最小子树被调整为平衡子树后,原有其他所有不平衡子树无需调整,整个二叉排序树就又成为一棵平衡二叉树。

      失去平衡的最小子树是指以离插入结点最近,且平衡因子绝对值大于1的结点作为根的子树。假设用A表示失去平衡的最小子树的根结点,则调整该子树的操作可归纳为下列四种情况。

当对一颗AVL树进行插入操作,可能会导致AVL树不平衡,此时,我们就需要做平衡处理,假设重新平衡的节点为Q,则不平衡会下列四种情况:

在Q的左孩子的左子树插入 (LL)

在Q的左孩子的右子树插入 (LR)

在Q的右孩子的左子树插入  (RL)

在Q的右孩子的右子树插入  (RR)
 旋转算法需要借助于两个功能的辅助,一个是求树的高度,一个是求两个高度的最大值。这里规定,一棵空树的高度为-1,只有一个根节点的树的高度为0,以后每多一层高度加1。为了解决指针NULL这种情况,写了一个求高度的函数,这个函数还是很有必要的。

代码:

[cpp] 
view plain
copy
《平衡二叉查找树(AVL)的查找,插入,删除》
《平衡二叉查找树(AVL)的查找,插入,删除》

  1. // return the height of node ptrTree or -1 if NULL;  
  2. //只有一个根节点的高度为0,空树的高度-1  
  3. int NodeHeight(PtrToNode  ptrTree)  
  4. {  
  5.     return ptrTree==NULL ? -1 :ptrTree->height;  
  6. }  
  7. int max(int a,int b)  
  8. {  
  9.     return a<b ? b : a;  
  10. }  

 (1)LL型平衡旋转法

由于在A的左孩子B的左子树上插入结点F,使A的平衡因子由1增至2而失去平衡。故需进行一次顺时针旋转操作。 即将A的左孩子B向右上旋转代替A作为根结点,A向右下旋转成为B的右子树的根结点。而原来B的右子树则变成A的左子树。

《平衡二叉查找树(AVL)的查找,插入,删除》


对应的代码:

思路:先把B的右子树变为A的左子树,在把A作为B的右子树


[cpp] 
view plain
copy
《平衡二叉查找树(AVL)的查找,插入,删除》
《平衡二叉查找树(AVL)的查找,插入,删除》

  1. //LL旋转  
  2. void RotateWithLeft(PtrToNode &k1)  
  3. {  
  4.     PtrToNode k= k1->left;  //保存节点的左子树  
  5.     k1->left = k->right;   //k节点的右子树作为k1的左子树  
  6.     k->right =k1;          //把k1的k的右子树  
  7.     //到此树旋转完成,更新树的深度,以k,k1为节点的树的深度发生了变化;  
  8.     k1->height= max(NodeHeight(k1->left),NodeHeight(k1->right))+1;  
  9.     k->height= max(NodeHeight(k->left),NodeHeight(k->right))+1;  
  10.     //根节点发生了变化,右k1变为了k,因为传的参数是引用,程序默认k1为根节点  
  11.     //k为局部变量,离开作用域,变量就会销毁,因此需要返回根节点,只不过是通过引用的方式罢了;  
  12.     k1=k;  
  13. }  

(2)RR型平衡旋转法

由于在A的右孩子C 的右子树上插入结点F,使A的平衡因子由-1减至-2而失去平衡。故需进行一次逆时针旋转操作。即将A的右孩子C向左上旋转代替A作为根结点,A向左下旋转成为C的左子树的根结点。而原来C的左子树则变成A的右子树。

《平衡二叉查找树(AVL)的查找,插入,删除》

思路:先把C的左子树作为A的右子树,在把A作为C的左子树。

代码:

[cpp] 
view plain
copy
《平衡二叉查找树(AVL)的查找,插入,删除》
《平衡二叉查找树(AVL)的查找,插入,删除》

  1. void RotateWithRight(PtrToNode &k2)  
  2. {  
  3.     PtrToNode k= k2->right;  //保存节点的右子树  
  4.     k2->right=k->left;  
  5.     k->left=k2;  
  6.     //到此树旋转完成,更新树的深度,以k,k1为节点的树的深度发生了变化;  
  7.     k2->height= max(NodeHeight(k2->left),NodeHeight(k2->right))+1;  
  8.     k->height= max(NodeHeight(k->left),NodeHeight(k->right))+1;  
  9.     //根节点发生了变化,右k1变为了k,因为传的参数是引用,程序默认k1为根节点  
  10.     //k为局部变量,离开作用域,变量就会销毁,因此需要返回根节点,只不过是通过引用的方式罢了;  
  11.     k2=k;  
  12. }  

(3)LR型平衡旋转法

由于在A的左孩子B的右子数上插入结点F,使A的平衡因子由1增至2而失去平衡。故需进行两次旋转操作(先逆时针,后顺时针)。即先将A结点的左孩子B的右子树的根结点D向左上旋转提升到B结点的位置,然后再把该D结点向右上旋转提升到A结点的位置。即先使之成为LL型,再按LL型处理。


《平衡二叉查找树(AVL)的查找,插入,删除》

    如图中所示,先将圈圈的部分进行逆时针旋转(RR旋转),使之转换为LL型,再进行LL旋转;(双旋转)

代码:

[cpp] 
view plain
copy
《平衡二叉查找树(AVL)的查找,插入,删除》
《平衡二叉查找树(AVL)的查找,插入,删除》

  1. void DoubleRotateWithLeft(PtrToNode &k3)  
  2. {  
  3.     RotateWithRight(k3->left);  
  4.     RotateWithLeft(k3);  
  5. }  

(4)RL型平衡旋转法  

由于在A的右孩子C的左子树上插入结点F,使A的平衡因子由-1减至-2而失去平衡。故需进行两次旋转操作(先顺时针,后逆时针),即先将A结点的右孩子C的左子树的根结点D向右上旋转提升到C结点的位置,然后再把该D结点向左上旋转提升到A结点的位置。即先使之成为RR型,再按RR型处理。

《平衡二叉查找树(AVL)的查找,插入,删除》

    如图中所示,先将圈圈的部分进行顺时针旋转(LL旋转),使之转换为RR型,再进行RR旋转;(双旋转)

代码:

[cpp] 
view plain
copy
《平衡二叉查找树(AVL)的查找,插入,删除》
《平衡二叉查找树(AVL)的查找,插入,删除》

  1. void DoubleRotateWithRight(PtrToNode &k3)  
  2. {  
  3.     RotateWithLeft(k3->right);  
  4.     RotateWithRight(k3);  
  5. }  

三、AVL的查找、删除、插入

1.AVL树的类型声明

[html] 
view plain
copy
《平衡二叉查找树(AVL)的查找,插入,删除》
《平衡二叉查找树(AVL)的查找,插入,删除》

  1. //平衡二叉树的结构体  
  2. typedef int elementType;  
  3. typedef struct AVLNODE   
  4. {  
  5.     elementType data;  
  6.     struct AVLNODE * left;  
  7.     struct AVLNODE * right;  
  8.     int height;  //以此节点为根,树的高度;  
  9.     unsigned int freq;//此节点保存的数据出现的频率  
  10. }AvlNode,*PtrToNode;</span>  

2.插入

AVL树的插入和二叉查找树的插入相似,只是AVL树的插入可能会破坏树的平衡性。对AVL树而言,插入完成后,需要判断树的平衡性是否被破坏,然后进行相应的旋转处理使之成为平衡树。

(1)左平衡处理

所谓左平衡处理,就是某一根结点的左子树比右子树过高,从而失去了平衡。在节点的左子树进行插入操作使此节点失去平衡,需要左平衡处理。

[cpp] 
view plain
copy
《平衡二叉查找树(AVL)的查找,插入,删除》
《平衡二叉查找树(AVL)的查找,插入,删除》

  1. //左平衡处理  
  2. void LeftBalance(PtrToNode &node)  
  3. {  
  4.     PtrToNode ptrTmp=node->left;  
  5.     if(NodeHeight(ptrTmp->left)-NodeHeight(ptrTmp->right)==-1)   
  6.     {  
  7.              //右子树高于左子树,在右子树插入的  
  8.         DoubleRotateWithLeft(node); //LR  
  9.     }  
  10.     else  
  11.     {      
  12.         RotateWithLeft(node); //LL  
  13.     }  
  14. }  

需要判断是在失去平衡的节点的
左孩子

左子树

右子树
进行插入的,左子树插入(LL旋转),右子树插入(LR旋转)。

(2)右平衡处理

类似左平衡处理,所谓右平衡处理,就是某一根结点的右子树比左子树过高,从而失去了平衡。

[cpp] 
view plain
copy
《平衡二叉查找树(AVL)的查找,插入,删除》
《平衡二叉查找树(AVL)的查找,插入,删除》

  1. //右平衡处理  
  2. void RightBalance(PtrToNode &node)  
  3. {  
  4.     PtrToNode ptrTmp=node->right;  
  5.     if(NodeHeight(ptrTmp->right)-NodeHeight(ptrTmp->left)==-1)  
  6.     {  //左子树比右子树高,说明在左子树插入的  
  7.         DoubleRotateWithRight(node); //RL  
  8.     }  
  9.     else  
  10.     {  
  11.         RotateWithRight(node);  //RR  
  12.     }  
  13. }  

需要判断是在失去平衡的节点的右孩子左子树右子树进行插入的,左子树插入(RL旋转),右子树插入(RR旋转)。

(3) 插入函数的编写

[cpp] 
view plain
copy
《平衡二叉查找树(AVL)的查找,插入,删除》
《平衡二叉查找树(AVL)的查找,插入,删除》

  1. void AVL_Insert(PtrToNode &node,elementType x)  
  2. {  
  3.     if(NULL==node) //找到插入的节点的位置  
  4.     {  
  5.         node =(struct AVLNODE *) malloc(sizeof(struct AVLNODE ));  
  6.         node->data=x;  
  7.         node->height=0;  
  8.         node->freq = 1;  
  9.         node->left=NULL;  
  10.         node->right=NULL;  
  11.     }  
  12.     else if(x<node->data)  //在左子树插入  
  13.     {  
  14.         AVL_Insert(node->left,x);  
  15.         //判断是否破坏AVL树的平衡性  
  16.         if (NodeHeight(node->left)-NodeHeight(node->right)==2)   
  17.             LeftBalance(node);  //左平衡处理  
  18.         }  
  19.     else if(node->data<x)   //在右子树插入  
  20.     {  
  21.         AVL_Insert(node->right,x);  
  22.         //判断是否破坏AVL树的平衡性  
  23.         if (NodeHeight(node->right)-NodeHeight(node->left)==2)   
  24.             RightBalance(node); //右平衡处理  
  25.   
  26.     }  
  27.     else  
  28.     {node->freq++;}  
  29.   
  30.     node->height = max(NodeHeight(node->left),NodeHeight(node->right)) +1 ;  //跟新树的高度  
  31.           
  32. }  

2. 查找

由于AVL树是有序的二叉查找树,要查找的元素比节点的数据大,则在右子树查找;比节点的数据小,在左子树中查找;与节点的数据相等,返回该节点。

[cpp] 
view plain
copy
《平衡二叉查找树(AVL)的查找,插入,删除》
《平衡二叉查找树(AVL)的查找,插入,删除》

  1. PtrToNode AVL_Find(PtrToNode & node,elementType x)  
  2. {  
  3.     if (node==NULL)  //没找到元素  
  4.     {  
  5.         return NULL;  
  6.     }  
  7.     else if(x<node->data)  
  8.     {  
  9.         return AVL_Find(node->left,x); //在左子树里面查找  
  10.     }  
  11.     else if(node->data<x)  
  12.     {  
  13.         return AVL_Find(node->right,x); //在右子树里面查找  
  14.     }  
  15.     else //相等  
  16.         return node;  
  17. }  

3.删除

对二叉查找树,我们知道删除的结点可能有三种情况:(1)为叶子结点,(2)左子树或右子树有一个为空,(3)左右子树都不空。假设删除节点为A。

对于(1):直接删除即可。

对于(2):删除的方法,A的父节点绕过A节点使其指向A左子树(右子树为空)、右子树(左子树为空时)。

对于(3):一般的删除策略:用A的左子树最大数据或右子树最小数据(假设B节点)代替A节点的数据,并递归地删除B节点。

AVL的树的删除策略与二叉查找树的删除策略相似,只是删除节点后造成树失去平衡性,需要做平衡处理

[cpp] 
view plain
copy
《平衡二叉查找树(AVL)的查找,插入,删除》
《平衡二叉查找树(AVL)的查找,插入,删除》

  1. void AVL_Delete(PtrToNode &node,elementType x)  
  2. {  
  3.     if(NULL==node)  //空树直接返回  
  4.         return;  
  5.     if(x<node->data)  //在左子树中查找  
  6.     {  
  7.          AVL_Delete(node->left,x);     
  8.          if(NodeHeight(node->right)-NodeHeight(node->left)==2)  //树左平衡处理  
  9.           RightBalance(node);  
  10.     }  
  11.     else if(node->data<x)  //在右子树中查找  
  12.     {  
  13.         AVL_Delete(node->right,x);  
  14.          if(NodeHeight(node->left)-NodeHeight(node->right)==2)  //树右平衡处理  
  15.           LeftBalance(node);  
  16.     }  
  17.     else //找到要删除的元素节点  
  18.     {  
  19.         if(node->left==NULL) //左子树为空  
  20.         {  
  21.             PtrToNode ptrTmp = node;  
  22.             node=node->right;         //用右孩子代替此节点  
  23.             free(ptrTmp);            //释放内存  
  24.         }  
  25.         else if(node->right==NULL)  //右子树为空  
  26.         {  
  27.             PtrToNode ptrTmp = node;  
  28.             node=node->left;       //用左孩子代替此节点  
  29.             free(ptrTmp);   
  30.         }  
  31.         else   //左右子树都不为空  
  32.         {  
  33.             //一般的删除策略是左子树的最小数据 或 右子树的最小数据 代替该节点  
  34.             PtrToNode ptrTmp=node->left;  //从左子树中查找  
  35.             while(ptrTmp->right!=NULL)  ptrTmp=ptrTmp->right;  
  36.             //此时的ptrTmp指向左子树中的最大元素  
  37.             node->data =  ptrTmp->data;  
  38.             AVL_Delete(node->left,ptrTmp->data);  //递归的删除该节点  
  39.         }  
  40.     }  
  41.   
  42.     //更新节点的高度  
  43.     if(node)  
  44.         node->height = max(NodeHeight(node->left),NodeHeight(node->right));  
  45. }  

4.遍历打印输出(中序)

[cpp] 
view plain
copy
《平衡二叉查找树(AVL)的查找,插入,删除》
《平衡二叉查找树(AVL)的查找,插入,删除》

  1. //中序遍历  
  2. void print(PtrToNode & root)    
  3. {    
  4.     if (NULL == root)     
  5.     {    
  6.         return ;    
  7.     }   
  8.     print(root->left);  
  9.     printf(“%d “,root->data);   
  10.     print(root->right);         
  11. }   

5.测试代码

[cpp] 
view plain
copy
《平衡二叉查找树(AVL)的查找,插入,删除》
《平衡二叉查找树(AVL)的查找,插入,删除》

  1. int main()  
  2. {  
  3.     //C++引入的引用概念,可以直接对树的节点进行插入操作,而不用返回树的根节点  
  4.     PtrToNode root =NULL;  
  5. /*  for(int i=0;i<5;i++) 
  6.     { 
  7.         AVL_Insert(root,i); 
  8.     } 
  9.     */  
  10.     AVL_Insert(root,4);  
  11.     AVL_Insert(root,2);  
  12.     AVL_Insert(root,6);  
  13.     AVL_Insert(root,1);  
  14.     AVL_Insert(root,3);  
  15.     AVL_Insert(root,5);  
  16.     AVL_Insert(root,7);  
  17.     AVL_Insert(root,16);  
  18.     AVL_Insert(root,15);  
  19.     print(root);  
  20.   
  21.     printf(“\n%d\n”,root->height);  
  22.     AVL_Delete(root,15);  
  23.     AVL_Delete(root,5);  
  24.     print(root);  
  25.     PtrToNode y=AVL_Find(root,15);  
  26.     if (y==NULL)  
  27.     {  
  28.         printf(“没有查找到15\n”);  
  29.     }  
  30.     else  
  31.     {  
  32.         printf(“所在节点的高度:%d\n”,y->height);  
  33.         if (NULL!=y->left)  
  34.         {  
  35.             printf(“所在节点的左孩子:%d\n”,y->left->data);  
  36.         }  
  37.         if (NULL!=y->right)  
  38.         {  
  39.             printf(“所在节点的右孩子:%d\n”,y->right->data);  
  40.         }  
  41.           
  42.     }  
  43. }  

平衡二叉树性能分析


平衡二叉树的性能优势:

      很显然,平衡二叉树的优势在于不会出现普通二叉查找树的最差情况。其查找的时间复杂度为O(logN)。

 

平衡二叉树的缺陷:

      (1) 很遗憾的是,为了保证高度平衡,动态插入和删除的代价也随之增加。红黑树是更加高效的查找结构。

 

      (2) 所有二叉查找树结构的查找代价都与树高是紧密相关的,能否通过减少树高来进一步降低查找代价呢。我们可以通过多路查找树的结构来做到这一点。

 

      (3) 在大数据量查找环境下(比如说系统磁盘里的文件目录,数据库中的记录查询 等),所有的二叉查找树结构(BST、AVL、RBT)都不合适。如此大规模的数据量(几G数据),全部组织成平衡二叉树放在内存中是不可能做到的。那么把这棵树放在磁盘中吧。问题就来了:假如构造的平衡二叉树深度有1W层。那么从根节点出发到叶子节点很可能就需要1W次的硬盘IO读写。大家都知道,硬盘的机械部件读写数据的速度远远赶不上纯电子媒体的内存。 查找效率在IO读写过程中将会付出巨大的代价。在大规模数据查询这样一个实际应用背景下,平衡二叉树的效率就很成问题了。

    原文作者:平衡二叉树
    原文地址: https://blog.csdn.net/zhang19900402/article/details/39395021
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞