平衡二叉树(AVL树)深入解读

https://blog.****.net/qpzkobe/article/details/81611486

平衡二叉树又称AVL树


性质:

它或者是颗空树,或者是具有下列性质的二叉树:

  • 它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1。
  • 若将二叉树节点的平衡因子BF定义为该节点的左子树的深度减去它的右子树的深度,则平衡二叉树上所有节点的平衡因子只可能为-1,0,1.
  • 只要二叉树上有一个节点的平衡因子的绝对值大于1,那么这颗平衡二叉树就失去了平衡。

平衡二叉树(AVL树)深入解读 
根据上述性质我们可以发现图(a)是一棵平衡二叉树,而图(b)是一棵不平衡二叉树。图中结点的数值代表的就是当前结点的平衡因子。也验证了上述性质,一棵平衡二叉树的所有结点的平衡因子只可能是-1、0、1三种。


为什么需要平衡二叉树?

当然,我们都希望所有的二叉排序树的初始序列都是平衡的,因为平衡二叉树上的任何一个结点左右字数的深度之差都不会超过1,则可以证明它的深度和logn是同数量级的,所以其平均查找长度也和logn同数量级。但是事于愿违有些二叉排序树的插入,或者初始序列由于其插入的先后顺序等缘故,将导致我们的二叉排序树的效率大大降低。如下图 
平衡二叉树(AVL树)深入解读 
为了避免这种情况的发生,我们希望可以有一种算法,将我们的不平衡的二叉排序树转化为平衡二叉排序树。这样就可以让我们的二叉排序树结构最优化。


平衡二叉树的算法

看到上述例子,我们就慢慢有点感觉了,至少知道了为什么会需要平衡二叉树,接下来我们看一下平衡二叉树是怎样将我们不平衡的树转换为平衡二叉树。 
如何时构成的二叉排序树编程平衡二叉树呢?先看一个具体的例子 
平衡二叉树(AVL树)深入解读

  • 图a 一颗空树也算是平衡二叉树
  • 图b 只有一个结点13的树也算是平衡二叉树
  • 图c 在图b的基础上插入新的结点24之后,仍然是平衡二叉树,只是根结点的平衡因子从0变到了-1(左子树的深度为0减去右子树的深度1等于-1)
  • 图d 在图c的基础上再插入一个结点37,这个时候整棵树出现了不平衡现象,根结点13的平衡因子从-1变成了-2。我们想要让这课树平衡,而且要保证该树二叉排序树的性质,那么我们只要将根结点13换为24结点13作为结点24的左子树,这棵树就又会回到平衡状态,如图e。我们把这种对树做向左逆时针“旋转”的操作称为单向左旋平衡处理。左旋之后,我们发现13、24、37结点的平衡因子都变为0。而且仍然保持着二叉排序树的特性
  • 图f当我们继续插入结点90之后,二叉树仍然平衡,只是24、37两个结点的平衡因子变为了-1,再次插入53结点之后,结点37的平衡因子BF由-1变为-2,这意味着该排序树中出现了新的不平衡现象,需要进行调整。但此时由于结点53插在结点90的左子树上,因此不能如上面一样作简单的调整。对于以上结点37为根的子树来说,既要保持二叉排序树的特性,又要平衡,则必须以53结点作为根结点,而使3**7结点成为它左子树的根,**90结点称为它右子树的根。这就好比做了两次旋转,首先我们让37、53、90这棵树单先向右顺时针转变成图g,再像左逆时针变成图h,这样我们的二叉树就能够再次回到平衡状态。对于以上旋转操作我们称为双向旋转(先右后左)平衡处理

平衡算法总结

看完了上面的例子,我们总结一下二叉排序树的不平衡情况以及如何将其转化为平衡情况。 
一般情况下,假设由于在二叉排序树上插入结点而失去平衡的最小子树根结点的指针为a(即a是离插入结点最近,且平衡因子绝对值不超过1的祖先结点),则失去平衡后进行调整的规律可以归纳为一下4种情况:

  1. 单向右旋平衡处理:由于在a的左子树根结点的左子树上插入结点,a的平衡因子由1增加到2,致使以a为根结点的子树失去平衡,则需要进行一次右向顺时针旋转操作。简称LL型旋转 
    平衡二叉树(AVL树)深入解读
  2. 单想左旋平衡处理:由于在a的右子树根结点的右子树上插入结点,a的平衡因子由-1增加到-2,致使以a为根结点的子树失去平衡,则需要进行一次左向逆时针旋转操作。简称RR型旋转 
    平衡二叉树(AVL树)深入解读
  3. 双向旋转(先左后右)平衡处理:由于在a的左子树的根结点的右子树上插入结点,a的平衡因子由1增加到2,致使a为根结点的子树失去平衡,则需要进行两次旋转(先左旋后右旋)操作。简称LR型旋转 
    平衡二叉树(AVL树)深入解读
  4. 双向旋转(先右后左)平衡处理:由于在a的右子树的根结点的左子树上插入结点,a的平衡因子由1增加到2,致使a为根结点的子树失去平衡,则需要进行两次旋转(先右旋后左旋)操作。简称RL型旋转 
    平衡二叉树(AVL树)深入解读

如何证明我们插入的正确性:中序遍历所得关键字的值序列从小到大即可(二叉排序树的性质)


那么如何创建一颗平衡二叉树呢?

创建平衡二叉树,我们采用依次插入节点的方式进行。而平衡二叉树上插入节点采用递归的方式进行。递归算法如下:

(1)若该树为一空树,那么插入一个数据元素为e的新节点作为平衡二叉树的根节点,树的高度增加1。

(2)若待插入的数据元素e和平衡二叉树(BBST)的根节点的关键字相等,那么就不需要进行插入操作。

(3) 若待插入的元素e比平衡二叉树(BBST)的根节点的关键字小,而且在BBST的左子树中也不存在和e有相同关键字的节点,则将e插入在BBST的左子树上,并且当插入之后的左子树深度增加1时,分别就下列情况处理之。

  • BBST的根节点的平衡因子为-1(右子树的深度大于左子树的深度):则将根节点的平衡因子更改为0(左旋),BBST的深度不变;

  • BBST的根节点的平衡因子为0(左右子树的深度相等):则将根节点的平衡因子修改为1(不用旋),BBST的深度增加1;

  • BBST的根节点的平衡因子为1(左子树的深度大于右子树的深度):若BBST的左子树根节点的平衡因子为1,则需要进行单向右旋转平衡处理,并且在右旋处理后,将根节点和其右子树根节点的平衡因子更改为0,树的深度不变; 
    若BBST的左子树根节点的平衡因子为-1,则需进行先向左,后向右的双向旋转平衡处理,并且在旋转处理之后,修改根节点和其左,右子树根节点的平衡因子,树的深度不变;

(4)若e的关键字大于BBST的根节点的关键字,而且在BBST的右子树中不存在和e有相同关键字的节点,则将e插入到BBST的右子树上,并且当插入之后的右子树深度加1时,分别就不同的情况处理之。

  • BBST的根节点的平衡因子是1(左子树的深度大于右子树的深度):则将根节点的平衡因子修改为0(右旋),BBST的深度不变;

  • BBST的根节点的平衡因子是0(左右子树的深度相等):则将根节点的平衡因子修改为-1(不用旋),树的深度加1;

  • BBST的根节点的平衡因子为-1(右子树的深度大于左子树的深度):若BBST的右子树根节点的平衡因子为1,则需要进行两次选择,第一次先向右旋转,再向左旋转处理,并且在旋转处理之后,修改根节点和其左,右子树根节点的平衡因子,树的深度不变; 
    若BBST的右子树根节点的平衡因子为1,则需要进行一次单向左的旋转处理,并且在左旋之后,更新根节点和其左,右子树根节点的平衡因子,树的深度不变;

代码实现

 
  1. /*********************************************************

  2. - Copyright (C): 2016

  3. - File name : bbst.c

  4. - Author : - zxn -

  5. - Date : 2016年08月06日 星期六 15时45分15秒

  6. - Description : 平衡二叉树

  7. * *******************************************************/

  8. #include <stdio.h>

  9. #include <stdlib.h>

  10. #include <stdbool.h>

  11.  
  12. #define LH +1 //左高

  13. #define EH 0 //等高

  14. #define RH -1 //右高

  15.  
  16. //二叉树类型定义

  17. struct tree

  18. {

  19. int data; //数据域

  20. int bf; //平衡因子

  21. struct tree *left; //左孩子

  22. struct tree *right; //右孩子

  23. };

  24.  
  25. typedef struct tree treenode;

  26. typedef treenode *btree;

  27.  
  28. //右旋

  29. //对以*ptr为根的二叉排序树做右旋处理,处理之后p指向新的树根结点,即旋转

  30. //处理之前左子树的根结点

  31. void R_Rotate(btree *ptr)

  32. {

  33. btree lc = (*ptr)->left; //lc指向的*ptr的左孩子的根结点

  34. (*ptr)->left = lc->right; //lc的右子树挂接为*ptr的左子树

  35. lc->right = *ptr;

  36. *ptr = lc; //ptr指向新的结点"

  37. }

  38.  
  39. //左旋

  40. //对以*ptr为根的二叉排序树做左旋处理,处理之后p指向新的树根结点,即旋转

  41. //处理之前右子树的根结点

  42. void L_Rotate(btree *ptr)

  43. {

  44. btree rc = (*ptr)->right; //rc指向的*ptr的由孩子的根结点

  45. (*ptr)->right = rc->left; //rc的左子树挂接为*ptr的右子树

  46. rc->left = *ptr;

  47. *ptr = rc; //ptr指向新的结点

  48. }

  49.  
  50. /*

  51. 以指针root所指结点为根结点的二叉树作左平衡旋转处理,本算法结束时,指针root指向新的结点

  52. */

  53. void LeftBalance(btree *root)

  54. {

  55.  
  56. btree lc;

  57. btree rd;

  58.  
  59. lc = (*root)->left; //ls指向*root的左根结点

  60.  
  61. //检测*root的左子树的平衡度,并作相应处理

  62. switch (lc->bf)

  63. {

  64. case LH:

  65. {

  66. //新结点插入在*root的左孩子的左子树上,要做单右旋处理

  67. (*root)->bf = lc->bf = EH;

  68. R_Rotate(root);

  69. break;

  70. }

  71. case RH:

  72. {

  73. //新结点插入在*root左孩子的右子树上要做双旋处理

  74. //rd指向*t的左孩子的右子树根上

  75. rd = lc->right;

  76. switch(rd->bf)

  77. {

  78. //修改*root及其左孩子的平衡因子

  79. case LH:

  80. {

  81. (*root)->bf = RH;

  82. lc->bf = EH;

  83. break;

  84. }

  85. case EH:

  86. {

  87. (*root)->bf = lc->bf = EH;

  88. break;

  89. }

  90. case RH:

  91. {

  92. (*root)->bf = EH;

  93. lc->bf = LH;

  94. break;

  95. }

  96. }

  97. rd->bf = EH;

  98. //对*root的左子树左左旋平衡处理

  99. L_Rotate(&(*root)->left);

  100. //对*root做右旋平衡处理

  101. R_Rotate(root);

  102. break;

  103. }

  104. }

  105. }

  106.  
  107. void RightBalance(btree *root)

  108. {

  109. btree lc;

  110. btree rd;

  111.  
  112. lc = (*root)->right;

  113.  
  114. switch (lc->bf)

  115. {

  116. case RH:

  117. {

  118. (*root)->bf = lc->bf = EH;

  119. L_Rotate(root);

  120. break;

  121. }

  122. case LH:

  123. {

  124. rd = lc->left;

  125. switch(rd->bf)

  126. {

  127. case LH:

  128. {

  129. (*root)->bf = EH;

  130. lc->bf = RH;

  131. break;

  132. }

  133. case EH:

  134. {

  135. (*root)->bf = lc->bf = EH;

  136. break;

  137. }

  138. case RH:

  139. {

  140. (*root)->bf = LH;

  141. lc->bf = EH;

  142. break;

  143. }

  144. }

  145. rd->bf = EH;

  146. R_Rotate(&(*root)->right);

  147. L_Rotate(root);

  148. break;

  149. }

  150. }

  151. }

  152.  
  153. /*

  154. 平衡二叉排序树的插入

  155. 若在平衡的二叉树root中不存在和e相同关键字的结点,则插入一个数据元素为e的新结点,并返回1,否则返回0,若因插入而使二叉树失去平衡,则作平衡处理,taller变量反应T长高与否

  156. 若树长高,则置taller为ture

  157. */

  158. int InsertAVL(btree *root, int e, bool *taller)

  159. {

  160. if ((*root) == NULL)

  161. {

  162. //该树为一棵空树,创建一个新节点作为根节点

  163. (*root) = (btree)malloc(sizeof(treenode));

  164. (*root)->bf = EH;

  165. (*root)->data = e;

  166. (*root)->left = NULL;

  167. (*root)->right = NULL;

  168. *taller = true;

  169. }

  170. else if (e == (*root)->data)

  171. {

  172. //关键字相同,则不再继续插入

  173. *taller = false;

  174. return 0;

  175. }

  176. else if (e < (*root)->data)

  177. {

  178. //应该继续在*root的左子树进行搜索

  179. if (!InsertAVL(&(*root)->left, e, taller))

  180. {

  181. //未插入

  182. return 0;

  183. }

  184. //已插入到*root的左子树中并且左子树长高

  185. if (*taller)

  186. {

  187. //检查*root的平衡度

  188. switch ((*root)->bf)

  189. {

  190. //原本左子树比右子树高

  191. case LH:

  192. {

  193. //平衡因子为-1

  194. //左旋

  195. LeftBalance(root);

  196. *taller = false;

  197. break;

  198. }

  199. //原本左右树一样高,现在因为左子树长高树长高

  200. case EH:

  201. {

  202. //平衡因子为0

  203. (*root)->bf = LH;

  204. *taller = true;

  205. break;

  206. }

  207. //原本右子树比左子树高,现在等高

  208. case RH:

  209. {

  210. //平衡因子为1

  211. (*root)->bf = EH;

  212. *taller = false;

  213. break;

  214. }

  215. }

  216. }

  217. }

  218. else

  219. {

  220. //应继续在*root的右子树中进行搜索

  221. if (!InsertAVL(&(*root)->right, e, taller))

  222. {

  223. //未插入

  224. return 0;

  225. }

  226. //已插入到*root的右子树且右子树长高

  227. if (*taller)

  228. {

  229. //检查*root的平衡度

  230. switch((*root)->bf)

  231. {

  232. case LH:

  233. {

  234. //原本左子树比右子树高,现在相等

  235. (*root)->bf = EH;

  236. *taller = false;

  237. break;

  238. }

  239. case EH:

  240. {

  241. //原来左右子树登高,现在因为右子树长高树长高

  242. (*root)->bf = RH;

  243. *taller = true;

  244. break;

  245. }

  246. case RH:

  247. {

  248. //原本右子树比左子树高,需要做右旋平衡处理

  249. RightBalance(root);

  250. *taller = false;

  251. break;

  252. }

  253. }

  254. }

  255. }

  256. return 1;

  257. }

  258.  
  259. /*

  260. 二叉树的中序遍历

  261. */

  262. void inorder(btree root)

  263. {

  264. if (root->left)

  265. {

  266. inorder(root->left);

  267. }

  268. printf("%d ", root->data);

  269. if (root->right)

  270. {

  271. inorder(root->right);

  272. }

  273. }

  274.  
  275. /*

  276. 平衡二叉树的查找

  277. */

  278.  
  279. bool FindNode(btree root, int value, btree *pos)

  280. {

  281. btree ptr = root;

  282.  
  283. (*pos) = NULL;

  284.  
  285. while (ptr)

  286. {

  287. if (ptr->data == value)

  288. {

  289. //找到了

  290. (*pos) = ptr;

  291. return true;

  292. }

  293. else if (ptr->data > value)

  294. {

  295. ptr = ptr->left;

  296. }

  297. else

  298. {

  299. ptr = ptr->right;

  300. }

  301. }

  302. //没有找到

  303. return false;

  304. }

  305.  
  306. int main()

  307. {

  308. int i;

  309. int value;

  310. int nArr[] = {1,23,45,34,98,9,4,35,23};

  311.  
  312. bool taller;

  313.  
  314. btree root = NULL;

  315. btree pos = NULL;

  316.  
  317. for (i = 0; i < 9; i++)

  318. {

  319. //插入数据

  320. InsertAVL(&root, nArr[i], &taller);

  321. }

  322. printf("---------中序遍历结果---------\n");

  323. inorder(root);

  324.  
  325. printf("\n请输入查找的数:");

  326. scanf("%d", &value);

  327.  
  328. if (FindNode(root, value, &pos))

  329. {

  330. printf("\n找到了:%d\n", pos->data);

  331. }

  332. else

  333. {

  334. printf("\nNot find this node\n");

  335. }

  336.  
  337. return 0;

  338. }

插入过程 
平衡二叉树(AVL树)深入解读
平衡二叉树(AVL树)深入解读


代码结果 
平衡二叉树(AVL树)深入解读

平衡二叉树(AVL树)深入解读