【大话数据结构C语言】57 平衡二叉树(AVL树)

举报
CodeAllen 发表于 2021/10/29 22:55:01 2021/10/29
【摘要】 欢迎关注我的公众号是【CodeAllen】,关注回复【1024】获取精品学习资源 程序员技术交流①群:736386324 ,程序员技术交流②群:371394777     平衡二叉排序树 平衡二叉树是一种二叉排序树,其中每一个结点的左子树和右子树的高度差至多等于1   有两位俄罗...

欢迎关注我的公众号是【CodeAllen】,关注回复【1024】获取精品学习资源
程序员技术交流①群:736386324 ,程序员技术交流②群:371394777    

平衡二叉排序树

平衡二叉树是一种二叉排序树,其中每一个结点的左子树和右子树的高度差至多等于1

 

有两位俄罗斯数学家 G.M. Adelson-VelskyE.M. Landis发明一种解决平衡二叉树的算法,所以很多资料称这样的平衡二叉树为AVL树

 

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

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

 

 

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

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

 

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

当继续插入 37 时,生成的二叉排序树如图 3(a),平衡二叉树的结构被破坏,此时只需要对二叉排序树做“旋转”操作(如图 3(b)),即整棵树以结点 24 为根结点,二叉排序树的结构没有破坏,同时将该树转化为了平衡二叉树:

当二叉排序树的平衡性被打破时,就如同扁担的两头出现了一头重一头轻的现象,如图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 为根结点;

 

 

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

 

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

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

 

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

 

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

 

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

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

 

文章来源: allen5g.blog.csdn.net,作者:CodeAllen的博客,版权归原作者所有,如需转载,请联系作者。

原文链接:allen5g.blog.csdn.net/article/details/117002112

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。