封装基于 BinaryTreeOperations 的 AVL 树(一种自平衡的二叉查找树)。

除了提供 BinaryTreeOperations 中的部分基础接口外,增加按键的插入 和 按键或节点指针的删除操作。

在阅读本文前,您应该先了解二叉树中的旋转是怎么回事(相关文章很多且简单,笔者不再赘述)。

一、节点定义:

  1. struct Node
  2. {
  3. _Ty key;
  4. ;
  5. Node* left = nullptr;
  6. Node* right = nullptr;
  7. Node* parent = nullptr;
  8. Node(const _Ty& _key) :key(_key) {}
  9. };

二、AVL 树的规则:

  任何节点的两个子树最大高度差为 1。

根据 AVL 树的规则,可见在插入和删除节点后,可能会打破 AVL 树的平衡,因此在插入和删除时应进行调整,使得 AVL 树能够自平衡。

因此在节点定义中加入平衡因子 bf 用于记录节点的平衡状态。

注:bf == 0 时,节点左右子树等高;bf == -1 时,左子树高一层;bf == 1 时,右子树高一层。bf 绝对值大于 1 时应该进行调整。

调整时需要进行旋转,旋转规则(令失衡节点为 cur):

  ① 左左_右旋:cur 左子树比右子树高两层(bf == -2)且 cur 的左子树 bf == -1 时:以 cur 右旋。

  ② 右右_左旋:cur 右子树比左子树高两层(bf == 2)且 cur 的左子树 bf == 1时:以 cur 左旋。

  ③ 左右_左右旋:cur 左子树比右子树高两层(bf == -2)且 cur 的左子树 bf == 1时:先以 cur 的左孩子左旋,再以 cur 右旋。

  ④ 右左_右左旋:cur 右子树比左子树高两层(bf == 2)且 cur 的左子树 bf == -1时:先以 cur 的右孩子右旋,再以 cur 左旋。

  注意 bf 值的含义,左子树高还是右子树高或者等高。

单旋转方法已经在 BinaryTreeOperations.h 中给出。

三、现对旋转(单旋转和双旋转)后相关节点的平衡因子 bf 的变化进行说明(很多教程并未明确提出 导致读者不能明确 bf 的变化)。

  一、以左旋为例(单旋),右旋与左旋类似。

  二、以 右左旋为例(双旋),左右旋与右左旋类似。

  先看 一,与下图结合看(假设应该调整的节点是 X):

  发现:

  ① Z 节点孩子未发生变动,因此 Z 节点的平衡因子不需要改变。

  ② 若 a 为空,则 b,c,d 必定为空(看 AVL 树的规则),因此,X,Y 的平衡因子均应该置零。(此时插入的节点为 Z)。

  ③ 若 a 不为空,则 b 一定不为空,c,d 一定其一为空,另一个不为空。(此时插入的节点为 c,d 之一,另一个为空)。可见,X,Y 节点任然平衡,平衡因子应置零。

  再看 二,与下图结合看(假设应该调整的节点是 X):

  发现:

  ① Z 一定平衡(见下分析),其平衡因子 bf 应置零。

  ② 若 a 为空,则 b,c,d 必定为空(看 AVL 树的规则),X,Y,Z 的平衡因子均应该置零。(此时插入的节点为 Z)。

  ③ 若 a 不为空,则 b 一定不为空,c,d 一定其一为空,另一个不为空。(此时插入的节点为 c,d 之一,另一个为空)。

    1)若插入为 c,则 X 平衡,其 bf 应置零,Y 右子树高,其 bf 应置 1。

    2)若插入为 d,则 X 左子树高,其 bf 应置 -1,Y 平衡,其 bf 应置零。

  至此,单旋双旋 旋转后旋转因子的变化说明清楚(调用 BinaryTreeOperations.h 中的旋转方法后应修改相关节点的 bf)。

四、插入操作:

  可知,新插入的节点(令为 cur)必定是空位置,令插入后其父亲节点为 par(考虑成功插入的情况,只有成功插入才可能打破平衡)。

  插入后的自平衡很简单,首先要知道,插入后 par 一定不会失去平衡(不用解释吧)。

  插入后分为两种情况讨论即可(令 par 的父亲节点为 gpa,也就是新加节点的祖父节点):

    一、par 有两个孩子。

    二、par 只有一个孩子。

  先看 一:par 有两个孩子,说明 cur 的插入没有给 par 或者 par 的祖先带来高度变化(没有新加层),此时直接返回即可。

  再看二:par 只有一个孩子,说明 cur 的插入带来了高度变化,可能需要调整,此时应该怎能做呢?

    ① 先根据 par 和 gpa 的关系(即 par 是 gpa 的左孩子还是右孩子)来更新 gpa 的 bf。(默认插入时已经对 par 的平衡因子进行更新)

      注:如何更新 par 和 gpa 的平衡因子不用说吧?(看是左孩子还是右孩子,右孩子 则 bf 自增,左孩子 则 bf 自减)。

    ② 根据 gpa 的平衡因子判断是否需要调整(即是否需要旋转)。

      1)gpa 不需要旋转,则 par,gpa 分别指向各自父节点,继续 ① 操作,直到 2)或者 gpa 为空。

      2)gpa 要旋转,则根据 par,gpa 的 bf 判断旋转方法进行调整,然后返回即可(请看上文 旋转规则)。

      注:2)之后需要继续向上操作吗?(当然不需要!gpa 旋转后,对 gpa 的祖先而言没有高度的增加和减小)。

    注意插入空树的情况。

  至此,插入操作讲解完成,后序进行删除操作讲解,这会比插入操作稍加困难。

五、删除操作:

  删除操作会比插入操作稍加复杂,下面先分析其原因所在(这里令 待删除节点为 cur,其父节点为 par)。

  注:红黑树亦是如此,RBTree 的 删除比 AVLTree 的删除更加复杂,所以对于 AVLTree 的删除不用慌。

  ① 插入时, 新插入节点的 父亲节点 在插入前 一定:没有孩子 或 有一个孩子(该孩子一定没有孩子),而删除时 cur 可能没有孩子或有一个或两个孩子。

  ② 删除时有无孩子都需要判断是否 cur 是根节点。

  ③ 应该如何删除 cur 且如何判断是否调整。

  针对上述问题,我们可将其简化。

    ① 删除节点是否为根节点的问题比较简单,只需做一下判断即可,后文不讨论该情况。

    ② 删除节点有两个孩子时:如二叉查找树一般,用后继节点代替并调用函数自身去删除后继结点(也可用前驱节点代替)。

    ③ 删除节点没有孩子。

    ④ 删除节点只有一个孩子。

    注:② 中的后继结点(或前驱节点)一定属于 ③ 或 ④ 情况。

  因此,将重点讨论 ③ ④ 情况。

  即:

  一、删除节点没有孩子(是叶子节点)。

  二、删除节点只有一个孩子。

  注:令 cur 为待删除节点,par 为其父亲节点,gpa 为 par 的父亲节点。

  先看 一(cur 是叶子节点):

    ① 若 cur 不存在兄弟节点,则删除 cur 会造成高度变化,gpa 是第一个可能失衡的节点(不用图也能想清楚吧)。

    ② 若 cur 存在兄弟节点,则 par 可能失衡(比如 par 右子树深度为2,左子树深度为一,删除左子树会失衡)。

    OK,既然知道第一个可能失去平衡的节点,那只需要检测并调整就好了。

  注意:什么情况需要继续向根节点回溯呢?即:通过旋转操作后 需要继续向上 还是 没有旋转操作 才需要继续向上?

  当然是:par 经过旋转后才需要继续向上,若 par 不需要旋转,则 gpa 没有发生左右子树高度差变化,反之,par 经过旋转后 gpa 对应 par 子树的高度减小,则可能失衡。

  所以 par 为经旋转是退出循环的条件。

  继续回溯时,注意改变父节点的 bf。

  再看 二(cur 只有一个孩子):

    ① cur 只有一个孩子,则该孩子一定没有孩子(否则 cur 不是平衡的)。

    ② 删除 cur,par 在 cur 的子树上高度一定减小,par 则有可能失衡,即 par 是第一个可能失衡点。

    ③ 删除 cur 时,用 cur 的孩子替代 cur 并删除 cur 的孩子,然后从 par 开始回溯判断是否需要调整,退出循环条件与前文一致。

  至此,删除说明到此为止,AVL 树的说明也到此为止,最后给出示例代码。

测试代码 main.cpp:

  1. #include <iostream>
  2. #include "AVLTree.h"
  3.  
  4. using std::cout;
  5. using std::endl;
  6.  
  7. int main()
  8. {
  9. AVLTree<int> avl;
  10.  
  11. auto Add = [&avl](int _key)
  12. {
  13. cout << "Add " << _key << ":" << endl;
  14. avl.insert(_key);
  15. avl.levelTraversal();
  16. cout << endl << "size: " << avl.size() << " level: " << avl.level() << endl << endl;
  17. };
  18. auto il = { , , , , , , };
  19. for (auto& x : il) Add(x);
  20. auto Del = [&avl](int _key)
  21. {
  22. cout << "Del " << _key << ":" << endl;
  23. avl.erase(_key);
  24. avl.levelTraversal();
  25. cout << endl << "size: " << avl.size() << " level: " << avl.level() << endl << endl;
  26. };
  27. Del(); Del(); Del();
  28.  
  29. ;
  30. }

头文件 AVLTree.h:

  1. #pragma once
  2. #ifndef __AVLTREE_H__
  3. #define __AVLTREE_H__
  4.  
  5. #include"BinaryTreeOperations.h"
  6. template<typename _Ty>
  7. class AVLTree
  8. {
  9. private:
  10. struct Node
  11. {
  12. _Ty key;
  13. ;
  14. Node* left = nullptr;
  15. Node* right = nullptr;
  16. Node* parent = nullptr;
  17. Node(const _Ty& _key) :key(_key) {}
  18. };
  19.  
  20. public:
  21. AVLTree() = default;
  22. ~AVLTree() { BTO::clear(root); size_n = ; }
  23. ; }
  24.  
  25. void preorderTraversal() { BTO::preorderTraversal(root, drawData); }
  26. void inorderTraversal() { BTO::inorderTraversal(root, drawData); }
  27. void postorderTraversal() { BTO::postorderTraversal(root, drawData); }
  28. void iterativePreorderTraversal() { BTO::iterativePreorderTraversal(root, drawData); }
  29. void iterativeInorderTraversal() { BTO::iterativeInorderTraversal(root, drawData); }
  30. void iterativePostorderTraversal() { BTO::iterativePostorderTraversal(root, drawData); }
  31. void levelTraversal() { BTO::levelTraversal(root, drawData); }
  32.  
  33. Node* find(const _Ty& _key) { return BTO::find(root, _key); }
  34. Node* iterativeFind(const _Ty& _key) { return BTO::iterativeFind(root, _key); }
  35. Node* findMaximum() { return BTO::findMaximum(root); }
  36. Node* findMinimum() { return BTO::findMinimum(root); }
  37. size_t level() { return BTO::getLevel(root); }
  38. size_t size() const { return size_n; }
  39.  
  40. void insert(const _Ty&);
  41. bool erase(const _Ty&);
  42. void erase(Node*);
  43.  
  44. private:
  45. void LL_R(Node*);
  46. void RR_L(Node*);
  47. void LR_LR(Node*);
  48. void RL_RL(Node*);
  49. static void drawData(const Node* _node) { std::cout << _node->key << " "; }
  50.  
  51. private:
  52. size_t size_n = ;
  53. Node* root = nullptr;
  54. };
  55.  
  56. template<typename _Ty>
  57. void AVLTree<_Ty>::LL_R(Node* _node)
  58. {
  59. _node = BTO::rightRotate(_node);
  60. _node->bf = _node->right->bf = ;
  61. if (_node->parent == nullptr) root = _node;
  62. }
  63.  
  64. template<typename _Ty>
  65. void AVLTree<_Ty>::RR_L(Node* _node)
  66. {
  67. _node = BTO::leftRotate(_node);
  68. _node->bf = _node->left->bf = ;
  69. if (_node->parent == nullptr) root = _node;
  70. }
  71.  
  72. template<typename _Ty>
  73. void AVLTree<_Ty>::LR_LR(Node* _node)
  74. {
  75. BTO::leftRotate(_node->left);
  76. _node = BTO::rightRotate(_node);
  77. if (_node->right->right == nullptr)
  78. _node->left->bf = _node->right->bf = ;
  79. else
  80. {
  81. if (_node->right->left == nullptr)
  82. {
  83. _node->right->bf = ;
  84. _node->left->bf = ;
  85. }
  86. else
  87. {
  88. _node->left->bf = ;
  89. _node->right->bf = -;
  90. }
  91. }
  92. _node->bf = ;
  93. if (_node->parent == nullptr) root = _node;
  94. }
  95.  
  96. template<typename _Ty>
  97. void AVLTree<_Ty>::RL_RL(Node* _node)
  98. {
  99. BTO::rightRotate(_node->right);
  100. _node = BTO::leftRotate(_node);
  101. if (_node->left->left == nullptr)
  102. _node->left->bf = _node->right->bf = ;
  103. else
  104. {
  105. if (_node->left->right == nullptr)
  106. {
  107. _node->left->bf = -;
  108. _node->right->bf = ;
  109. }
  110. else
  111. {
  112. _node->left->bf = ;
  113. _node->right->bf = ;
  114. }
  115. }
  116. _node->bf = ;
  117. if (_node->parent == nullptr) root = _node;
  118. }
  119.  
  120. template<typename _Ty>
  121. void AVLTree<_Ty>::insert(const _Ty& _key)
  122. {
  123. auto ret = BTO::insert(root, _key);
  124. if (ret.second) ++size_n;
  125. else return;
  126. auto temp = ret.first;
  127. if (temp->parent == nullptr) return;
  128.  
  129. ;
  130. ;
  131. temp = temp->parent;
  132. if (temp->left != nullptr && temp->right != nullptr) return;
  133.  
  134. Node* pa = temp->parent;
  135. while (pa != nullptr)
  136. {
  137. if (temp == pa->left)
  138. pa->bf += -;
  139. else
  140. pa->bf += ;
  141. )
  142. {
  143. ) LL_R(pa);
  144. else LR_LR(pa);
  145. break;
  146. }
  147. )
  148. {
  149. ) RL_RL(pa);
  150. else RR_L(pa);
  151. break;
  152. }
  153. temp = pa;
  154. pa = pa->parent;
  155. }
  156. }
  157.  
  158. template<typename _Ty>
  159. bool AVLTree<_Ty>::erase(const _Ty& _key)
  160. {
  161. bool succeed = false;
  162. Node* del = BTO::find(root, _key);
  163. if (del != nullptr)
  164. {
  165. erase(del);
  166. succeed = true;
  167. }
  168. return succeed;
  169. }
  170.  
  171. template<typename _Ty>
  172. void AVLTree<_Ty>::erase(Node* _node)
  173. {
  174. if (_node == nullptr) return;
  175. --size_n;
  176. Node* pa = nullptr;
  177. Node* temp = nullptr;
  178. if (_node->left == nullptr && _node->right == nullptr)
  179. {
  180. if (_node == root)
  181. {
  182. root = nullptr;
  183. delete _node;
  184. return;
  185. }
  186. if (_node == _node->parent->left)
  187. {
  188. _node->parent->left = nullptr;
  189. _node->parent->bf += ;
  190. temp = _node->parent->right;
  191. }
  192. else
  193. {
  194. _node->parent->right = nullptr;
  195. _node->parent->bf += -;
  196. temp = _node->parent->left;
  197. }
  198. pa = _node->parent;
  199. delete _node;
  200. }
  201. else if (!(_node->left != nullptr && _node->right != nullptr))
  202. {
  203. if (_node->parent == nullptr)
  204. {
  205. if (_node->left != nullptr) root = _node->left;
  206. else root = _node->right;
  207. root->parent = nullptr;
  208. delete _node;
  209. return;
  210. }
  211. if (_node == _node->parent->left)
  212. {
  213. _node->parent->left = _node->left == nullptr ? _node->right : _node->left;
  214. if (_node->left != nullptr) _node->left->parent = _node->parent;
  215. else _node->right->parent = _node->parent;
  216. _node->parent->bf += ;
  217. temp = _node->parent->right;
  218. }
  219. else
  220. {
  221. _node->parent->right = _node->left == nullptr ? _node->right : _node->left;
  222. if (_node->left != nullptr) _node->left->parent = _node->parent;
  223. else _node->right->parent = _node->parent;
  224. _node->parent->bf += -;
  225. temp = _node->parent->left;
  226. }
  227. pa = _node->parent;
  228. delete _node;
  229. }
  230. else
  231. {
  232. Node* de = BTO::findMinimum(_node->right);
  233. _node->key = de->key;
  234. ++size_n;
  235. erase(de);
  236. }
  237. )
  238. {
  239. temp = pa;
  240. pa = pa->parent;
  241. if (pa != nullptr)
  242. {
  243. if (temp == pa->left)
  244. {
  245. temp = pa->right;
  246. pa->bf += ;
  247. }
  248. else
  249. {
  250. temp = pa->left;
  251. pa->bf += -;
  252. }
  253. }
  254. }
  255. while (pa != nullptr)
  256. {
  257. )
  258. {
  259. || temp->bf == ) LL_R(pa);
  260. else LR_LR(pa);
  261. }
  262. )
  263. {
  264. ) RL_RL(pa);
  265. else RR_L(pa);
  266. }
  267. else break;
  268. : ;
  269. pa = temp->parent;
  270. if (pa != nullptr)
  271. {
  272. if (isLeft)
  273. {
  274. temp = pa->left;
  275. pa->bf += -;
  276. }
  277. else
  278. {
  279. temp = pa->right;
  280. pa->bf += ;
  281. }
  282. }
  283. }
  284. }
  285.  
  286. #endif // !__AVLTREE_H__

二叉树(3)AVL 树的更多相关文章

  1. 二叉树与AVL树

    二叉树 什么是二叉树? 父节点至多只有两个子树的树形结构成为二叉树.如下图所示,图1不是二叉树,图2是一棵二叉树. 图1 普通的树                                    ...

  2. 二叉树,AVL树和红黑树

    为了接下来能更好的学习TreeMap和TreeSet,讲解一下二叉树,AVL树和红黑树. 1. 二叉查找树 2. AVL树 2.1. 树旋转 2.1.1. 左旋和右旋 2.1.2. 左左,右右,左右, ...

  3. 二叉树-二叉查找树-AVL树-遍历

    一.二叉树 定义:每个节点都不能有多于两个的儿子的树. 二叉树节点声明: struct treeNode { elementType element; treeNode * left; treeNod ...

  4. python常用算法(5)——树,二叉树与AVL树

    1,树 树是一种非常重要的非线性数据结构,直观的看,它是数据元素(在树中称为节点)按分支关系组织起来的结构,很像自然界中树那样.树结构在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构都可用树形 ...

  5. 5分钟了解二叉树之AVL树

    转载请注明出处:https://www.cnblogs.com/morningli/p/16033733.html AVL树是带有平衡条件的二叉查找树,其每个节点的左子树和右子树的高度最多相差1.为了 ...

  6. 二叉树之AVL树的平衡实现(递归与非递归)

    这篇文章用来复习AVL的平衡操作,分别会介绍其旋转操作的递归与非递归实现,但是最终带有插入示例的版本会以递归呈现. 下面这张图绘制了需要旋转操作的8种情况.(我要给做这张图的兄弟一个赞)后面会给出这八 ...

  7. 二叉树之AVL树

    高度为 h 的 AVL 树,节点数 N 最多2^h − 1: 最少N(h)=N(h− 1) +N(h− 2) + 1. 最少节点数n 如以斐波那契数列可以用数学归纳法证明: 即: N(0) = 0 ( ...

  8. 04-树4. Root of AVL Tree-平衡查找树AVL树的实现

    对于一棵普通的二叉查找树而言,在进行多次的插入或删除后,容易让树失去平衡,导致树的深度不是O(logN),而接近O(N),这样将大大减少对树的查找效率.一种解决办法就是要有一个称为平衡的附加的结构条件 ...

  9. AVL树和伸展树 -数据结构(C语言实现)

    读数据结构与算法分析 AVL树 带有平衡条件的二叉树,通常要求每颗树的左右子树深度差<=1 可以将破坏平衡的插入操作分为四种,最后通过旋转恢复平衡 破坏平衡的插入方式 描述 恢复平衡旋转方式 L ...

  10. 【数据结构】什么是AVL树

    目录 什么是AVL树 1. 什么是AVL树 2. 节点的实现 3. AVL树的调整 3.1 LL旋转 3.2 RR旋转 3.3 RL旋转 3.4 LR旋转 什么是AVL树 二叉查找树的一个局限性就是有 ...

随机推荐

  1. java: -source 1.5 中不支持 diamond 运算符 ,lambadas表达式 2018-03-13 22:43:47 eleven十一 阅读数 876更多

  2. sublime3使用技巧

    1.鼠标悬浮,显示文件引用 Preference ——>   Settings   ——>    "index_files": true   (保存,重新打开即可) 2 ...

  3. Nuxt的路由配置以及传参

    Nuxt 路由可以使用a标签进行链接跳转,例如我们创建了一个demo.vue的文件 <p> <a href="/demo">跳转去Demo页面</a& ...

  4. python 中对list去重

    本文去重的前提是要保证顺序不变,本文给出了多种实现方法,需要的朋友可以参考下 1.直观方法 最简单的思路就是: ids = [1,2,3,3,4,2,3,4,5,6,1] news_ids = [] ...

  5. linux下删除文件夹

    ---恢复内容开始--- 4月份左右接触linux,一直到现在,收获不多,原因是因为我没有足够的努力,其实这段时间以来我也很自责. 今天学习linux进程调度等知识,使用小红帽时,准备删除一个无用的文 ...

  6. 原生js按回车键实现登录

    这篇文章主要介绍了原生JS按回车键实现登录的方法,众所周知,这是在web程序设计中的一个非常实用的小技巧,主要用于表单提交,包括注册.登录等等功能,具有很好的用户体验,有着非常广泛的实用价值,需要的朋 ...

  7. 【转】docker配置参数详解---/etc/docker/daemon.json完整参数

    docker-daemon.json各配置详解 { “api-cors-header”:"", ——————在引擎API中设置CORS标头 “authorization-plugi ...

  8. MySQL双机热备环境搭建

    一.    前期准备 准备两台服务器(电脑),接入到同一局域网中,能够使双方可以ping通: 安装MySQL数据库,具体安装方法网上很全面,但是安装的版本需保持一致: 服务器IP地址设置. l  A服 ...

  9. RTT学习之软件包

    网络工具集 (NetUtils) Ping 工具: 是一种网络诊断工具,用来测试数据包能否通过 IP 协议到达特定主机,依赖于LWIP,支持域名和IP访问: NTP 工具:NTP 是网络时间协议 (N ...

  10. MySQL主从复制(一主两从)

       主库开启bin-log二进制日志功能,并建立slave账号,并授权从库连接主库,从库通过change master得到主库的相关同步信息, 然后连接主库进行验证,主库产生的新数据会导入到bin- ...