博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
AVL树-查找-插入
阅读量:4687 次
发布时间:2019-06-09

本文共 4695 字,大约阅读时间需要 15 分钟。

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树   

    百度百科_AVL树   

转载于:https://www.cnblogs.com/pangxiaodong/archive/2011/08/25/2152916.html

你可能感兴趣的文章
【C++算法与数据结构学习笔记------单链表实现多项式】
查看>>
关于ProjectServer定制化项目中心页面
查看>>
使用Collectd + InfluxDB + Grafana进行JMX监控
查看>>
Linux下tar,zip命令详解
查看>>
C#垃圾回收机制
查看>>
31、任务三十一——表单联动
查看>>
python之hasattr、getattr和setattr函数
查看>>
maven使用阿里镜像配置文件
查看>>
Copy code from eclipse to word, save syntax.
查看>>
arguments.callee的作用及替换方案
查看>>
23 Java学习之RandomAccessFile
查看>>
P2709 小B的询问
查看>>
润乾报表 动态控制文本的显示
查看>>
[oracle] 如何使用myBatis在数据库中插入数据并返回主键
查看>>
PHP echo 和 print 语句
查看>>
第一讲 一个简单的Qt程序分析
查看>>
Centos 6.5下的OPENJDK卸载和SUN的JDK安装、环境变量配置
查看>>
poj 1979 Red and Black(dfs)
查看>>
【.Net基础03】HttpWebRequest模拟浏览器登陆
查看>>
UML-画类图与交互图的顺序
查看>>