上一节介绍如何使用二叉排序树实现动态查找表,本节介绍另外一种实现方式——平衡二叉树。

平衡二叉树,又称为 AVL 树。实际上就是遵循以下两个特点的二叉树:

  • 每棵子树中的左子树和右子树的深度差不能超过 1;
  • 二叉树中每棵子树都要求是平衡二叉树;

其实就是在二叉树的基础上,若树中每棵子树都满足其左子树和右子树的深度差都不超过 1,则这棵二叉树就是平衡二叉树。


图 1 平衡与不平衡的二叉树及结点的平衡因子
 

平衡因子:每个结点都有其各自的平衡因子,表示的就是其左子树深度同右子树深度的差。平衡二叉树中各结点平衡因子的取值只可能是:0、1 和 -1。

如图 1 所示,其中 (a) 的两棵二叉树中由于各个结点的平衡因子数的绝对值都不超过 1,所以 (a) 中两棵二叉树都是平衡二叉树;
而 (b) 的两棵二叉树中有结点的平衡因子数的绝对值超过 1,所以都不是平衡二叉树。

二叉排序树转化为平衡二叉树

为了排除动态查找表中不同的数据排列方式对算法性能的影响,需要考虑在不会破坏二叉排序树本身结构的前提下,将二叉排序树转化为平衡二叉树。

例如,使用上一节的算法在对查找表{13,24,37,90,53}构建二叉排序树时,当插入 13 和 24 时,二叉排序树此时还是平衡二叉树:


图 2 平衡二叉树

当继续插入 37 时,生成的二叉排序树如图 3(a),平衡二叉树的结构被破坏,此时只需要对二叉排序树做“旋转”操作(如图 3(b)),

即整棵树以结点 24 为根结点,二叉排序树的结构没有破坏,同时将该树转化为了平衡二叉树:


图 3 二叉排序树变为平衡二叉树的过程

当二叉排序树的平衡性被打破时,就如同扁担的两头出现了一头重一头轻的现象,如图3(a)所示,此时只需要改变扁担的支撑点(树的树根),就能使其重新归为平衡。实际上图 3 中的 (b) 是对(a) 的二叉树做了一个向左逆时针旋转的操作。

继续插入 90 和 53 后,二叉排序树如图 4(a)所示,导致二叉树中结点 24 和 37 的平衡因子的绝对值大于 1 ,整棵树的平衡被打破。此时,需要做两步操作:

  1. 如图 4(b) 所示,将结点 53 和 90 整体向右顺时针旋转,使本该以 90 为根结点的子树改为以结点 53 为根结点;
  2. 如图 4(c) 所示,将以结点 37 为根结点的子树向左逆时针旋转,使本该以 37 为根结点的子树,改为以结点 53 为根结点;

图 4 二叉排序树转化为平衡二叉树

做完以上操作,即完成了由不平衡的二叉排序树转变为平衡二叉树。

当平衡二叉树由于新增数据元素导致整棵树的平衡遭到破坏时,就需要根据实际情况做出适当的调整,假设距离插入结点最近的“不平衡因子”为 a。

则调整的规律可归纳为以下 4 种情况:

  • 单向右旋平衡处理:若由于结点 a 的左子树为根结点的左子树上插入结点,导致结点 a 的平衡因子由 1 增至 2,致使以 a 为根结点的子树失去平衡,
  • 则只需进行一次向右的顺时针旋转,如下图这种情况:

图 5 单向右旋
  • 单向左旋平衡处理:如果由于结点 a 的右子树为根结点的右子树上插入结点,导致结点 a 的平衡因子由 -1变为 -2,则以 a 为根结点的子树需要进行一次
  • 向左的逆时针旋转,如下图这种情况:

图 6 单向左旋
  • 双向旋转(先左后右)平衡处理:如果由于结点 a 的左子树为根结点的右子树上插入结点,导致结点 a 平衡因子由 1 增至 2,
  • 致使以 a 为根结点的子树失去平衡,则需要进行两次旋转操作,如下图这种情况:

图 7 双向旋转(先左后右)
注意:图 7 中插入结点也可以为结点 C 的右孩子,则(b)中插入结点的位置还是结点 C 右孩子,(c)中插入结点的位置为结点 A 的左孩子。
  • 双向旋转(先右后左)平衡处理:如果由于结点 a 的右子树为根结点的左子树上插入结点,导致结点 a 平衡因子由 -1 变为 -2,
  • 致使以 a 为根结点的子树失去平衡,则需要进行两次旋转(先右旋后左旋)操作,如下图这种情况:

图 8 双向旋转(先右后左)
注意:图 8 中插入结点也可以为结点 C 的右孩子,则(b)中插入结点的位置改为结点 B 的左孩子,(c)中插入结点的位置为结点 B 的左孩子。

在对查找表{13,24,37,90,53}构建平衡二叉树时,由于符合第 4 条的规律,所以进行先右旋后左旋的处理,最终由不平衡的二叉排序树转变为平衡二叉树。

构建平衡二叉树的代码实现

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. //分别定义平衡因子数
  4. #define LH +1
  5. #define EH 0
  6. #define RH -1
  7. typedef int ElemType;
  8. typedef enum
    {
      false,
      true
    } bool;
  9. // 定义二叉排序树
  10. typedef struct BSTNode
    {
  11. ElemType data;
  12. int bf;  //balance flag
  13. struct BSTNode *lchild, *rchild;
  14. }*BSTree, BSTNode;
  15. // 对以 p 为根结点的二叉树做右旋处理,令 p 指针指向新的树根结点
  16. void R_Rotate(BSTree* p)
  17. {
  18. // 借助文章中的图 5 所示加以理解,其中结点 A 为 p 指针指向的根结点
  19. BSTree lc = (*p)->lchild;
  20. (*p)->lchild = lc->rchild;
  21. lc->rchild = *p;
  22. *p = lc;
  23. }
  24. // 对以 p 为根结点的二叉树做左旋处理,令 p 指针指向新的树根结点
  25. void L_Rotate(BSTree* p)
  26. {
  27. // 借助文章中的图 6 所示加以理解,其中结点 A 为 p 指针指向的根结点
  28. BSTree rc = (*p)->rchild;
  29. (*p)->rchild = rc->lchild;
  30. rc->lchild = *p;
  31. *p = rc;
  32. }
  33. // 对以指针 T 所指向结点为根结点的二叉树作左子树的平衡处理,令指针 T 指向新的根结点
  34. void LeftBalance(BSTree* T)
  35. {
  36. BSTree lc,rd;
  37. lc = (*T)->lchild;
  38. // 查看以 T 的左子树为根结点的子树,失去平衡的原因,如果 bf 值为 1 ,则说明添加在左子树为根结点的左子树中,需要对其进行右旋处理;
  39.    // 反之,如果 bf 值为 -1,说明添加在以左子树为根结点的右子树中,需要进行双向先左旋后右旋的处理
  40. switch (lc->bf)
  41. {
  42. case LH:
  43. (*T)->bf = lc->bf = EH;
  44. R_Rotate(T);
  45. break;
  46. case RH:
  47. rd = lc->rchild;
  48. switch(rd->bf)
  49.   {
  50. case LH:
  51. (*T)->bf = RH;
  52. lc->bf = EH;
  53. break;
  54. case EH:
  55. (*T)->bf = lc->bf = EH;
  56. break;
  57. case RH:
  58. (*T)->bf = EH;
  59. lc->bf = LH;
  60. break;
  61.    }
  62. rd->bf = EH;
  63. L_Rotate(&(*T)->lchild);
  64. R_Rotate(T);
  65. break;
  66. }
  67. }
  68. // 右子树的平衡处理同左子树的平衡处理完全类似
  69. void RightBalance(BSTree* T)
  70. {
  71. BSTree lc, rd;
  72. lc = (*T)->rchild;
  73. switch (lc->bf)
  74. {
  75. case RH:
  76. (*T)->bf = lc->bf = EH;
  77. L_Rotate(T);
  78. break;
  79. case LH:
  80. rd = lc->lchild;
  81. switch(rd->bf)
  82.    {
  83. case LH:
  84. (*T)->bf = EH;
  85. lc->bf = RH;
  86. break;
  87. case EH:
  88. (*T)->bf = lc->bf = EH;
  89. break;
  90. case RH:
  91. (*T)->bf = EH;
  92. lc->bf = LH;
  93. break;
  94.   }
  95. rd->bf = EH;
  96. R_Rotate(&(*T)->rchild);
  97. L_Rotate(T);
  98. break;
  99. }
  100. }
  101.  
  102. int InsertAVL(BSTree* T, ElemType e, bool* taller)
  103. {
  104. // 如果本身为空树,则直接添加 e 为根结点
  105. if ((*T) == NULL)
  106. {
  107. (*T) = (BSTree)malloc(sizeof(BSTNode));
  108. (*T)->bf = EH;
  109. (*T)->data = e;
  110. (*T)->lchild = NULL;
  111. (*T)->rchild = NULL;
  112. *taller = true;
  113. }
  114. else if (e == (*T)->data) // 如果二叉树排序中已经存在e,则不做任何处理
  115. {
  116. *taller = false;
  117. return ;
  118. }
  119. //如果 e 小于结点 T 的数据域,则插入到 T 的左子树中
  120. else if (e < (*T)->data)
  121. {
  122. // 如果插入过程,不会影响树本身的平衡,则直接结束
  123. if(!InsertAVL(&(*T)->lchild,e,taller))
  124. return ;
  125. // 判断插入过程是否会导致整棵树的深度 +1
  126. if(*taller)
  127. {
  128. // 判断根结点 T 的平衡因子是多少,由于是在其左子树添加新结点的过程中导致失去平衡,所以当 T 结点的平衡因子本身为 1 时,需要进行左子树的平衡处理,
  129.        // 否则更新树中各结点的平衡因子数
  130. switch ((*T)->bf)
  131. {
  132. case LH:
  133. LeftBalance(T);
  134. *taller = false;
  135. break;
  136. case EH:
  137. (*T)->bf = LH;
  138. *taller = true;
  139. break;
  140. case RH:
  141. (*T)->bf = EH;
  142. *taller = false;
  143. break;
  144. }
  145. }
  146. }
  147. else  // 同样,当e>T->data时,需要插入到以T为根结点的树的右子树种,同样需要和以上同样的操作
  148. {
  149. if(!InsertAVL(&(*T)->rchild, e, taller))
  150. return ;
  151. if (*taller)
  152. {
  153. switch ((*T)->bf)
  154. {
  155. case LH:
  156. (*T)->bf = EH;
  157. *taller = false;
  158. break;
  159. case EH:
  160. (*T)->bf = RH;
  161. *taller = true;
  162. break;
  163. case RH:
  164. RightBalance(T);
  165. *taller = false;
  166. break;
  167. }
  168. }
  169. }
  170. return ;
  171. }
  172. // 判断现有平衡二叉树中是否已经具有数据域为 e 的结点
  173. bool FindNode(BSTree root, ElemType e, BSTree* pos)
  174. {
  175. BSTree pt = root;
  176. (*pos) = NULL;
  177. while(pt)
  178. {
  179. if (pt->data == e)
  180. {
  181. // 找到节点,pos指向该节点并返回true
  182. (*pos) = pt;
  183. return true;
  184. }
  185. else if (pt->data>e)
  186. {
  187. pt = pt->lchild;
  188. }
  189. else
  190. pt = pt->rchild;
  191. }
  192. return false;
  193. }
  194. //中序遍历平衡二叉树
  195. void InorderTra(BSTree root)
  196. {
  197. if(root->lchild)
  198. InorderTra(root->lchild);
  199.  
  200. printf("%d ",root->data);
  201.  
  202. if(root->rchild)
  203. InorderTra(root->rchild);
  204. }
  205.  
  206. int main()
  207. {
  208. int i,nArr[] = {,,,,,,,,};
  209. BSTree root = NULL, pos;
  210. bool taller;
  211. // 用 nArr查找表构建平衡二叉树(不断插入数据的过程)
  212. for (i=; i<; i++)
  213. {
  214. InsertAVL(&root, nArr[i], &taller);
  215. }
  216. // 中序遍历输出
  217. InorderTra(root);
  218. // 判断平衡二叉树中是否含有数据域为 103 的数据
  219. if(FindNode(root, , &pos))
  220. printf("\n%d\n", pos->data);
  221. else
  222. printf("\nNot find this Node\n");
  223. return ;
  224. }
  225. 运行结果
  226.  
  227. Not find this Node

总结

使用平衡二叉树进行查找操作的时间复杂度为O(logn)。在学习本节内容时,紧贴本节图示比较容易理解。

数据结构54:平衡二叉树(AVL树)的更多相关文章

  1. 【数据结构】平衡二叉树—AVL树

    (百度百科)在计算机科学中,AVL树是最先发明的自平衡二叉查找树.在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树.查找.插入和删除在平均和最坏情况下都是O(log n).增 ...

  2. 二叉查找树(BST)、平衡二叉树(AVL树)(只有插入说明)

    二叉查找树(BST).平衡二叉树(AVL树)(只有插入说明) 二叉查找树(BST) 特殊的二叉树,又称为排序二叉树.二叉搜索树.二叉排序树. 二叉查找树实际上是数据域有序的二叉树,即对树上的每个结点, ...

  3. 数据结构与算法——AVL树类的C++实现

    关于AVL树的简单介绍能够參考:数据结构与算法--AVL树简单介绍 关于二叉搜索树(也称为二叉查找树)能够參考:数据结构与算法--二叉查找树类的C++实现 AVL-tree是一个"加上了额外 ...

  4. Java 树结构实际应用 四(平衡二叉树/AVL树)

    平衡二叉树(AVL 树) 1 看一个案例(说明二叉排序树可能的问题) 给你一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在.  左边 BST 存在的问题分析: ...

  5. 平衡二叉树,AVL树之图解篇

    学习过了二叉查找树,想必大家有遇到一个问题.例如,将一个数组{1,2,3,4}依次插入树的时候,形成了图1的情况.有建立树与没建立树对于数据的增删查改已经没有了任何帮助,反而增添了维护的成本.而只有建 ...

  6. 数据结构(三)实现AVL树

    AVL树的定义 一种自平衡二叉查找树,中面向内存的数据结构. 二叉搜索树T为AVL树的满足条件为: T是空树 T若不是空树,则TL.TR都是AVL树,且|HL-HR| <= 1 (节点的左子树高 ...

  7. 二叉查找树(BST)、平衡二叉树(AVL树)

    二叉查找树(BST) 特殊的二叉树,又称为排序二叉树.二叉搜索树.二叉排序树. 二叉查找树实际上是数据域有序的二叉树,即对树上的每个结点,都满足其左子树上所有结点的数据域均小于或等于根结点的数据域,右 ...

  8. 图解:平衡二叉树,AVL树

    学习过了二叉查找树,想必大家有遇到一个问题.例如,将一个数组{1,2,3,4}依次插入树的时候,形成了图1的情况.有建立树与没建立树对于数据的增删查改已经没有了任何帮助,反而增添了维护的成本.而只有建 ...

  9. 数据结构与算法分析-AVL树

    1.AVL树是带有平衡条件的二叉查找树. 2.AVL树的每个节点高度最多相差1. 3.AVL树实现的难点在于插入或删除操作.由于插入和删除都有可能破坏AVL树高度最多相差1的特性,所以当特性被破坏时需 ...

  10. 数据结构——二叉查找树、AVL树

    二叉查找树:由于二叉查找树建树的过程即为插入的过程,所以其中序遍历一定为升序排列! 插入:直接插入,插入后一定为根节点 查找:直接查找 删除:叶子节点直接删除,有一个孩子的节点删除后将孩子节点接入到父 ...

随机推荐

  1. solrserver实例化

    以下是httpClient实例化方式,需要tomcat运行Solr服务 1.ConcurrentUpdateSolrServer实例化SolrServer,该类实例化多用于更新删除索引操作 Concu ...

  2. 二项分布 , 多项分布, 以及与之对应的beta分布和狄利克雷分布

    1. 二项分布与beta分布对应 2. 多项分布与狄利克雷分布对应 3. 二项分布是什么?n次bernuli试验服从 二项分布 二项分布是N次重复bernuli试验结果的分布. bernuli实验是什 ...

  3. R dataframe 遗忘, which 矩阵搜索

    A data frame is used for storing data tables. It is a list of vectors of equal length. For example, ...

  4. PHP+SOCKET 模拟HTTP请求

    HTTP消息结构 客户端请求包括四部份:请求行(状态行).请求头.空行.请求主体(数据),如下图: 服务端响应包括四部份:响应行(状态行).响应头.空行.响应主体(数据),如图: HTTP请求方法: ...

  5. float在内存中的存取方法

    今天做了一些题目,想到float数据如何在内存中的形式.不知道一个浮点数是如何存成32位01字符串的.下面是查找的一些资料. 我们先通过java获取这些数的二进制表示. public class De ...

  6. myeclipse 8.0 注册码

    Subscriber:accptechSubscription Code:nLR8ZC-855550-6765855429037911 Subscriber:Hello World Subscript ...

  7. 关于在datepicker中,只选年月

    有这么个需求,datepicker默认是选某个具体的日子的,但是现在只选到年月为止, solution: html如下: <div> <label for="startDa ...

  8. POJ - 2965 The Pilots Brothers' refrigerator(压位+bfs)

    The game “The Pilots Brothers: following the stripy elephant” has a quest where a player needs to op ...

  9. DPF.Android.Native.Components.v2.8.1 for delphi xe6 使用DPFJAlertDialog遇到的问题

    使用DPFJAlertDialog控件时发现DPFJAlertDialog1Click不能捕获到对话框到底按了那个按键,上网搜索后找到了解决方法: 打开DPF.Android.JAlertDialog ...

  10. java实现链式队列

    java实现链式队列...比较简单 package datastruct; public class QueueLink implements Queue { // 定义一个节点内部类 class N ...