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

posted @ 2011-08-25 12:55  xiaodongrush  阅读(1738)  评论(1编辑  收藏  举报