===================================================================

AVL树的概念

      在说AVL树的概念之前,我们需要清楚二茬搜索树的概念。对于二叉搜索树,我们知道它可以降低查找速率,但是如果一个二叉搜索树退化成一棵只剩单支的搜索树,此时的查找速率就相当于顺序表中查找元素,效率变低,时间复杂度由原来的O(logN)变为O(N)。
        此时就有了AVL(高度平衡二叉搜索树),从它的名字就能知道它也是一棵二叉搜索树,只是它在插入元素的时候,每插入一个新节点的时候就会调整整棵树的结构,从而来保证这课搜索树的平衡,即每一个节点的左右子树高度差的绝对值不超过1.
 
那么AVL树的概念就可以总结如下:
  满足以下性质的二叉搜索树:
  1、左右子树都是AVL树
  2、左右子树的高度之差的绝对值不超过1
     
那么对于像这样的AVL树,如果它有n个节点,则它的高度可以保持在log(n),那么它的平均搜索时间复杂度也就是O(log(n)了。
================================================================

AVL树的插入

1、平衡因子
      AVL树也是一棵二叉搜索树,所以它的插入是和二叉搜索树的插入操作类似的,只是需要加上调整高度的操作,那么就需要在节点的那个结构体类型中加一个用来反应这个节点的左右孩子高度的变量,平衡因子bf。
      定义bf的值等于节点右孩子的高度减去左孩子的高度。如果节点的左右孩子高度相等,则bf等于0;如果左孩子比右孩子高度大1,则bf取-1;如果右孩子高度比左孩子高度大1,则bf取1.那么如果不是上面的这三种情况,就不满足AVL树的定义了,即出现bf的绝对值大于1的时候就要进行高度调整了,所以就是当bf等与2或者-2的时候就要进行平衡化。
       那么当给一棵本来就平衡的AVL树中插入一个新节点P的时候,从节点P到根节点的路径上,每个节点为根的子树的高度都可能增加1,即平衡因子发生改变,所以执行一次插入操作后,都需要沿路径向根节点回溯,修改各节点的平衡因子,而如果遇到了哪一个节点的bf变成2或-2的时候就要进行平衡化处理,即调整棵树的高度。
2、节点类型的结构体
     我们已经知道在结构体中需要加一个变量 bf,而且我们在修改bf的时候是需要回溯的,所以如果我们还存放了每个节点的父节点就比较方便了,那么可以设计如下的节点结构体类型:
  1. template<class K>
  2. struct AVLTreeNode
  3. {
  4. K _key;
  5. int _bf;
  6. AVLTreeNode<K, V>* _left;
  7. AVLTreeNode<K, V>* _right;
  8. AVLTreeNode<K, V>* _parent;
  9.  
  10. AVLTreeNode(const K& key, const V& value)
  11. :_key(key),
  12. _bf(),
  13. _left(NULL),
  14. _right(NULL),
  15. _parent(NULL)
  16. {}
  17. };
3、平衡化处理
       在前面已经说了当更新到某个节点的平衡因子变成-2或者2的时候就需要进行平衡化处理,我们在AVL树中的平衡化处理采用的是旋转。对于平衡化处理中的旋转分为以下几种情况:
      》左单旋:
      》右单旋
      》左右双旋
      》右左双旋
下面是对于上述四种情况的图解:
     》【左单旋】当前节点较高右子树的右侧插入一个新节点,该子树高度增加1导致节点平衡因子从1变为变为2而不平衡
            
     》【右单旋当前节点较高的左子树左侧插入一个新节点,该子树的高度增加1,导致该节点的平衡因子从-1变为-2而不平衡
 
            
     》【左右双旋当前节点的平衡因子为-1,在当前节点较高的左子树的右子树b或者c中插入一个节点,该子树的高度增加1,使当前节点的左孩子的平衡因子变为1,当前节点的平衡因子变成-2,导致AVL树不再平衡,需要进行调整,采用先左后右双旋
(PPNode是10的父节点)
         
       可以看到在这里插入的节点是插在了6节点的左子树,那么它当然也可以插入到6的右子树中,而且还可以是上图中的方框代表的子树都是空这种情况,此时就相当于是插入6这个节点。这样的话,最后更新节点的平衡因子就要注意了,我们稍后再分析;
 
     》【右左双旋】当前节点的平衡因子为1,在当前节点较高的右子树的左子树b或者c插入新节点该子树的高度增加1,当前节点的右孩子的平衡因子变成-1,当前节点的平衡因子变成2,导致AVL树不再平衡,需要进行调整,采用先右后左双旋
      (PPNode是5的父节点)
           
》》》》注意:上面两个双旋的图解只是其中的一种情况,在上面左右双旋的下面我已经提出来了,这里需要注意非常重要的一点,就是你插入了新节点之后会改变哪几个节点的平衡因子,显然插入的地方不一样没得到的结果也会有差异;
       因为每插入一个节点都要向上更新bf,我们总结一下遇到什么情况应该旋转,采用什么旋转:
若parent->bf=-2或者2的时候需要进行调整
》parent->bf=2,说明它的右树比左树高,设它的右孩子为pcur
    >若pcur->bf==1,执行左单旋
    >若pcur->bf==-1,执行右左双旋旋
》parent->bf=-2,说明它的左树比右树高,设它的左孩子为pcur
    >若pcur->bf==-1,执行右单旋
    >若pcur->bf==1,执行左右双旋单旋
 
我们可以看到,在旋转过程中平衡因子发生改变的节点只有parent和pcur这两个节点,那么旋转之后该怎样修改这两个节点的平衡因子呢。
     >对于左单旋和右单旋的情况,parent和pcur的平衡因子都会变为0,所以在旋转完成后把它们的平衡因子置成0
     >对于双旋,我们最后更新平衡因子的时候是需要分情况的
        
     
     那么通过上图的说明,我们就可以看出来,最终影响parent和subR节点平衡因子的是subRL这个节点,主要就是看新插入的节点到底是插在它的左子树还是右子树,当然还有一种情况就是图中矩形代表的子树都为空的情况,也就是subRL就是要插入的节点,那么总共就是这三种情况,对应出来如下:
          》subRL的平衡因子是0(插入的节点就是它本身)
          》subRL的平衡因子是-1(新节点插在subRL的左子树)
          》subRL的平衡因子是1 (新节点插在subRL的右子树)
 对应的最终subR和parent的平衡因子调整我就不再赘述了,画一画很容易明白的。    
================================================================

程序代码:

        >>插入 insert函数
  1. bool Insert(const K& key, const V& value)
  2. {
  3. if (_root == NULL)
  4. {
  5. _root = new Node(key, value);
  6. return true;
  7. }
  8. Node* pcur = _root;
  9. Node* parent = NULL;
  10. while (pcur)
  11. {
  12. if (pcur->_key == key)
  13. return false;
  14. else if (pcur->_key < key)
  15. {
  16. parent = pcur;
  17. pcur = pcur->_right;
  18. }
  19. else
  20. {
  21. parent = pcur;
  22. pcur = pcur->_left;
  23. }
  24. }
  25. if (parent->_key < key)
  26. {
  27. pcur = parent->_right = new Node(key, value);
  28. pcur->_parent = parent;
  29. }
  30. else
  31. {
  32. pcur = parent->_left = new Node(key, value);
  33. pcur->_parent = parent;
  34. }
  35.  
  36. while (parent)
  37. {
  38. //修正平衡因子
  39. if (pcur == parent->_left)
  40. {
  41. parent->_bf--;
  42. }
  43. if (pcur == parent->_right)
  44. {
  45. parent->_bf++;
  46. }
  47. //
  48. if (parent->_bf == )
  49. break;
  50. else if (parent->_bf == - || parent->_bf == )
  51. {
  52. pcur = parent;
  53. parent = pcur->_parent;
  54. }
  55. else //parent->bf -2 || 2
  56. {
  57.  
  58. if (parent->_bf == -)
  59. {
  60. if (pcur->_bf == -) //右单旋
  61. RotateR(parent);
  62. else //左右双旋
  63. RotateLR(parent);
  64. }
  65. else
  66. {
  67. if (pcur->_bf == ) //左单旋
  68. RotateL(parent);
  69. else //右左双旋
  70. RotateRL(parent);
  71. }
  72. break;
  73. }
  74. }
  75. return true;
  76. }

>>旋转

  1. void RotateR(Node* parent)
  2. {
  3. Node* subL = parent->_left;
  4. Node* subLR = subL->_right;
  5. //换指向
  6. parent->_left = subLR;
  7. subL->_right = parent;
  8.  
  9. if (subLR)
  10. {
  11. subLR->_parent = parent;
  12. }
  13.  
  14. Node* PParent = parent->_parent; //判断parent是否有父节点
  15. if (PParent)
  16. {
  17. if (parent == PParent->_left)
  18. {
  19. PParent->_left = subL;
  20. subL->_parent = PParent;
  21. }
  22. else
  23. {
  24. PParent->_right = subL;
  25. subL->_parent = PParent;
  26. }
  27. }
  28. else
  29. {
  30. _root = subL;
  31. subL->_parent = NULL;
  32. }
  33. parent->_parent = subL;
  34. //修改bf
  35. subL->_bf = ;
  36. parent->_bf = ;
  37. }
  38.  
  39. //
  40. void RotateL(Node* parent)
  41. {
  42. Node* subR = parent->_right;
  43. Node* subRL = subR->_left;
  44. //调整指向
  45. subR->_left=parent;
  46. parent->_right = subRL;
  47.  
  48. if (subRL) //如果subRL非空
  49. {
  50. subRL->_parent = parent;
  51. }
  52.  
  53. Node* PPNode = parent->_parent;
  54. if (PPNode)
  55. {
  56. if (PPNode->_left == parent)
  57. PPNode->_left = subR;
  58. else
  59. PPNode->_right = subR;
  60.  
  61. //subR的父节点改变
  62. subR->_parent = PPNode;
  63. }
  64. else
  65. {
  66. _root = subR; //根节点
  67. subR->_parent = NULL;
  68. }
  69. parent->_parent = subR;
  70. /*修改bf*/
  71. parent->_bf = subR->_bf = ;
  72. }
  73.  
  74. //双旋(左右、、右左)
  75. void RotateRL(Node* parent)
  76. {
  77. Node* subR = parent->_right;
  78. Node* subRL = subR->_left;
  79. int bf = subRL->_bf;
  80.  
  81. RotateR(parent->_right);
  82. RotateL(parent);
  83.  
  84. //调整subR和parent的平衡因子
  85. if (bf == -)
  86. subR->_bf = ; // subR的bf在左旋中已经置0了,这里就没有再写
  87. else if (bf == )
  88. parent->_bf = -;
  89.  
  90. else
  91. {
  92. parent->_bf = ;
  93. subRL->_bf = ;
  94. }
  95. }
  96.  
  97. void RotateLR(Node* parent)
  98. {
  99. Node* subL = parent->_left;
  100. Node* subLR = subL->_right;
  101. int bf = subLR->_bf;
  102. RotateL(parent->_left);
  103. RotateR(parent);
  104.  
  105. //调整parent和subL的平衡因子
  106. if (bf == -)
  107. parent->_bf = ; //subL的bf在左旋中已经置0了,这里就没有再写
  108. else if (bf == )
  109. subL->_bf = -; //parent的bf在左旋中已经置0了
  110. else
  111. {
  112. subL->_bf = ;
  113. parent = ;
  114. }
  115. }

AVL树的插入操作(旋转)图解的更多相关文章

  1. AVL树的插入和删除

    一.AVL 树 在计算机科学中,AVL树是最早被发明的自平衡二叉查找树.在AVL树中,任一节点对应的两棵子树的最大高度差为 1,因此它也被称为高度平衡树.查找.插入和删除在平均和最坏情况下的时间复杂度 ...

  2. AVL 树的插入、删除、旋转归纳

    参考链接: http://blog.csdn.net/gabriel1026/article/details/6311339   1126号注:先前有一个概念搞混了: 节点的深度 Depth 是指从根 ...

  3. AVL树的单双旋转操作

    把必须重新平衡的节点称为å.对于二叉树,å的两棵子树的高度最多相差2,这种不平衡可能有四种情况: 对å的左儿子的左子树进行插入节点(左-左) 对å的左儿子的右子树进行插入节点(左-右) 对å的右儿子的 ...

  4. AVL树的插入删除查找算法实现和分析-1

    至于什么是AVL树和AVL树的一些概念问题在这里就不多说了,下面是我写的代码,里面的注释非常详细地说明了实现的思想和方法. 因为在操作时真正需要的是子树高度的差,所以这里采用-1,0,1来表示左子树和 ...

  5. AVL树的插入与删除

    AVL 树要在插入和删除结点后保持平衡,旋转操作必不可少.关键是理解什么时候应该左旋.右旋和双旋.在Youtube上看到一位老师的视频对这个概念讲解得非常清楚,再结合算法书和网络的博文,记录如下. 1 ...

  6. 第七章 二叉搜索树 (d2)AVL树:插入

  7. 创建AVL树,插入,删除,输出Kth Min

    https://github.com/TouwaErioH/subjects/tree/master/C%2B%2B/PA2 没有考虑重复键,可以在结构体内加一个int times. 没有考虑删除不存 ...

  8. 数据结构--Avl树的创建,插入的递归版本和非递归版本,删除等操作

    AVL树本质上还是一棵二叉搜索树,它的特点是: 1.本身首先是一棵二叉搜索树.   2.带有平衡条件:每个结点的左右子树的高度之差的绝对值最多为1(空树的高度为-1).   也就是说,AVL树,本质上 ...

  9. AVL树(查找、插入、删除)——C语言

    AVL树 平衡二叉查找树(Self-balancing binary search tree)又被称为AVL树(AVL树是根据它的发明者G. M. Adelson-Velskii和E. M. Land ...

随机推荐

  1. ES 基础

    You Know, for Search 安装es时 , jdk最低版本需要 jdk7 默认端口 : 9200 启动后浏览器访问 : localhost:9200 角色关系对照 elasticsear ...

  2. 探索ORACLE之ASM概念

    一.     ASM(自动存储管理)的来由: ASM是Oracle 10g R2中为了简化Oracle数据库的管理而推出来的一项新功能,这是Oracle自己提供的卷管理器,主要用于替代操作系统所提供的 ...

  3. 转】Maven学习总结(四)——Maven核心概念

    原博文出自于: http://www.cnblogs.com/xdp-gacl/p/4051819.html 感谢! 一.Maven坐标 1.1.什么是坐标? 在平面几何中坐标(x,y)可以标识平面中 ...

  4. SSH原理与运用一:远程登录(转)

    原文:http://www.ruanyifeng.com/blog/2011/12/ssh_remote_login.html 作者: 阮一峰 SSH是每一台Linux电脑的标准配置. 随着Linux ...

  5. 修改Map中确定key对应的value问题

    今天在码代码的时候出现一个没有预料的问题: 先看下面的代码: public static void main(String[] args) { String[] files=new String[]{ ...

  6. 轻松学习 red5 教程 像视频一样很详细还有代码直接可Copy

    转载自:http://blog.csdn.net/hongdianking/archive/2009/11/12/4804339.aspx 最近要做一个流媒体服务器,在网上逗留了好久决定选择 red5 ...

  7. CodeForces 455A Boredom (DP)

    Boredom 题目链接: http://acm.hust.edu.cn/vjudge/contest/121334#problem/G Description Alex doesn't like b ...

  8. jquery easyui防止超出浏览器边界

    var easyuiPanelOnMove=function(left,top){ if(left<0){ $(this).window('move',{ left:1 }); } if(top ...

  9. POJ 2763 Housewife Wind (树链剖分 有修改单边权)

    题目链接:http://poj.org/problem?id=2763 n个节点的树上知道了每条边权,然后有两种操作:0操作是输出 当前节点到 x节点的最短距离,并移动到 x 节点位置:1操作是第i条 ...

  10. JVM 关闭前执行命令的钩子

    Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { System.out.prin ...