AVL树-查找-插入

1. 简述

    AVL树要求每个结点的左右子树高度相差绝对值小于2。查找、插入和删除在平均和最坏情况下都是O(log n)。

2. 查找

    查找比较简单与普通二叉搜索树的查找是一样的。从根开始,要么向左,要么向右,要么找到,如果到达了NULL,则表示查找失败。   

AVLNode *  avl_search(AVLNode *  node,  int  value) {
  
while (node  !=  NULL) {
    
if (value  <  node -> value)  //  向左 
      node  =  node -> left;
    
else   if (value  >  node -> value)  //  向右 
      node  =  node -> right;
    
else   //  找到 
       return  node;
  }
  
return  NULL;  //  失败 
}

3. 插入

    插入元素可能导致从根到被插入结点的这条路径的平衡会被打破,如果平衡被打破,高度差超过了1,那么需要旋转。基本流程是首先在合适位置插入结点,然后从插入位置沿着路径倒回去,逐个更新平衡度并且对需要旋转的进行合适的选择。
    旋转分四类:设当前结点为A,插入结点为C
    RR型:A的平衡度为-2,C->value > A->value并且C->value > A->right->value。C在A的右孩子的右子树中,需要一次左旋。
    LL型:A的平衡度为2,C->value < A->value并且C->value < A->left->value。C在A的左孩子的左子树中,需要一次右旋。
    RL型:A的平衡度为-2,C->value > A->value并且C->value<A->left->value。C在A的右孩子的左子树中,需要先进行一次右旋,转化为LL型,再进行一次左旋。
  LR型:A的平衡度为2,C->value  < A->value并且C->value>A->right->value。C在A的左孩子的右子树中,需要先进行一次左旋,转化为RR型,再进行一次右旋。
    注:LL,LR这些符号,在百度百科和维基百科中的含义不一致,本文与维基百科是一致的,因为感觉维基百科的解释直观理解更清楚。LL表明C与A的位置关系是左左,LR表明C与A的位置关系时左右。
    对于双旋以前一直记不住,其实很好记,对于RR型需要左旋;对于LL型需要右旋;对于RL型,先解决后面的L,右旋,然后解决R,左旋;对于LR型,先解决后面的R,左旋,然后解决前面的L右旋。
    关于代码,网上很多都是递归的代码,我看了几篇博客文章,没发现简洁且正确的,旋转一般都没问题,但是一般旋转之后,所谓的A结点就不再是这个子树的根了,因此需要让A的父结点指向新的根,有的代码实现没有考虑到这个情况,有的代码实现虽然考虑到了这个情况,但是没有在递归的过程中,没有注意如果A的结点为空的情况,即A是根节点,根节点变了,而根是没有父结点的,此时要更新根指针。
    这里我尝试给出一个非递归的实现,不保证正确,如有错误,希望指出。   

#include < iostream >
#include
< stack >
using   namespace  std;

struct  AVLNode {
  AVLNode
*  left;
  AVLNode
*  right;
  
int  height;
  
int  value;
}; 

//  更新某个结点的高度,对于多个结点分别更新,需要从叶子向根的方向调用
//  否则可能出现更新错误 
void  update_height(AVLNode  * node) {
  assert(NULL 
!=  node);
  
int  lheight  =  node -> left == NULL  ?   0 :(node -> left -> height + 1 );
  
int  rheight  =  node -> right == NULL  ?   0 :(node -> right -> height + 1 );
  node
-> height  =  lheight  >  rheight  ?  lheight : rheight;
}

//  左左情况右旋,返回子树旋转后的根 
AVLNode *  LL(AVLNode *  A) { 
  assert(NULL 
!=  A);
  AVLNode
*  B  =  A -> left;
  A
-> left  =  B -> right;
  B
-> right  =  A;
  update_height(A);
  update_height(B);
  
return  B;
}
//  右右情况左旋,返回子树旋转后的根 
AVLNode *  RR(AVLNode *  A) {
  assert(NULL 
!=  A);
  AVLNode
*  B  =  A -> right;
  A
-> right  =  B -> left;
  B
-> left  =  A;
  update_height(A);
  update_height(B);
  
return  B;
}
//  左右情况,先左旋后右旋,返回子树旋转后的根
AVLNode *  LR(AVLNode *  A) {
  assert(NULL 
!=  A);
  A
-> left  =  RR(A -> left);  //  左旋并更新A的父结点指针
   return  LL(A);  
}
//  右左情况,先右旋后左旋,返回子树旋转后的根
AVLNode *  RL(AVLNode *  A) {
  assert(NULL 
!=  A);
  A
-> right  =  LL(A -> right);
  
return  RR(A);
}

bool  avl_insert(AVLNode *&  root,  int  value) {
  stack
< AVLNode *>  store;
  AVLNode
*  node, * A, * B, * C,  * tmp;
  tmp 
=  root;
  
//  寻找插入位置,并且保证路径 
   while (tmp  !=  NULL) {
    store.push(tmp);
    
if (value  <  tmp -> value) 
      tmp 
=  tmp -> left;
    
else   if (value  >  tmp -> value)
      tmp 
=  tmp -> right;
    
else   //  插入识别 
       return   false ;
  }
  
//  建立新结点
  node  =   new  AVLNode;
  node
-> left  =  node -> right  =  NULL; 
  node
-> height  =   0 ;
  node
-> value  =  value;
  
//  插入新结点 
   if (store.empty()) {  //  根结点为空 
    root  =  node;
    
return   true ;
  }
  
else  {
    tmp 
=  store.top();
    node
-> value < tmp -> value  ?  (tmp -> left = node):(tmp -> right = node);
  }
  
//  对打破平衡的结点进行旋转,并且更新所有需要改变高度的结点高度 
  C  =  node;
  
while ( ! store.empty()) {
    A 
=  store.top();
    store.pop();
    
int  lheight  =  A -> left == NULL  ?   0 :(A -> left -> height + 1 );
    
int  rheight  =  A -> right == NULL  ?   0 :(A -> right -> height + 1 );
    
switch (lheight - rheight) {
      
case   - 1 :
      
case   1 :
      
case   0 :
        A
-> height  =  lheight  >  rheight  ?  lheight : rheight;
        
break ;
      
case   2 //  L
        
//  旋转 
         if (C -> value  <  A -> left -> value)  //  LL
          A  =  LL(A);        
        
else   //  LR
          A  =  LR(A);
        
//  更新父结点 
         if (store.empty())
          root 
=  A;
        
else  
          A
-> value  <  store.top() -> value  ?  store.top() -> left  =  A : store.top() -> right  =  A;
        
break ;
      
case   - 2 //  R
        
//  旋转 
         if (C -> value  >  A -> right -> value)  //  RR
          A  =  RR(A);        
        
else   //  RL
          A  =  RL(A);        
        
//  更新父结点
         if (store.empty())
          root 
=  A;
        
else  
          A
-> value  <  store.top() -> value  ?  store.top() -> left  =  A : store.top() -> right  =  A;
        
break ;
    }
  }
}

void  print_father_child(AVLNode *  root) {
  stack
< AVLNode *>  store;
  store.push(root);
  
while ( ! store.empty()) {
    root 
=  store.top();
    store.pop();    
    cout 
<<  root -> value  <<   "  :  " ;
    
if (root -> left == NULL)
      cout 
<<   " NULL " ;
    
else  
      cout 
<<  root -> left -> value;
    cout 
<<   "  ,  " ;
    
if (root -> right == NULL)
      cout 
<<   " NULL " ;
    
else  
      cout 
<<  root -> right -> value;
    cout 
<<  endl;
    
if (root -> right)
      store.push(root
-> right);
    
if (root -> left)
      store.push(root
-> left);
  }
}

int  main() {
  AVLNode
*  root  =  NULL;
  
for ( int  i = 0 ; i < 10 ; i ++ )
    avl_insert(root, i);
  print_father_child(root);
  system(
" PAUSE " );
  
return   0 ;
}

   输出结果如下: 
  

4. 删除

    写插入写了半天,删除懒得写了,就先这样的,思路是按照二叉排序树删除的方法先删除该结点,并且用栈记录从该结点到根的路径上所有结点,然后从下往上依次更新高度、检查是否平衡,对于需要旋转的情况进行旋转。
    注意更新父结点的指向以及没有父结点的情况,还有就是对于既有左孩子又有右孩子的结点,普通二叉排序树会将待删除结点交换到左子树的最右结点(或者右子树的最左结点),因此入栈记录路径时,需要分情况对待。
    代码基本上大致是普通二叉排序树的删除代码,然后加上更新检查路径的代码。

5. 参考

    维基百科_AVL树    http://zh.wikipedia.org/wiki/AVL%E6%A0%91
    百度百科_AVL树    http://baike.baidu.com/view/671745.html?tp=6_01

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值