• 手撕AVL树


    目录

    一、概念

    二、 结点的定义

    2.1 键值对pair

    2.2 定义细节

    三、 AVL树的插入操作

    3.1 平衡因子调整规则

    3.2 旋转规则

    3.2.1 新结点插入较高左子树的左侧 — 左左:右单旋 

    3.2.2 新结点插入较高右子树的右侧 — 右右:左单旋

    3.2.3 新结点插入较高左子树的右侧 — 左右:左右双旋

    3.2.4 新结点插入较高右子树的左侧 — 右左:右左双旋

    四、 AVL树的删除操作

    五、 AVL树性能分析

    六、 完整代码


    一、概念

    二叉搜索树虽可以缩短查找的效率,但若数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在链表中搜索元素,效率低下。

    因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,若能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
     

    一棵AVL树是空树 或 具有以下性质的二叉搜索树:
    1. 它的左右子树都是AVL树
    2. 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)    右子树高度减去左子树高度
    3. 若一棵二叉搜索树是高度平衡的,其就是AVL树。
        若它有n个结点,其高度可保持在log_2 n,搜索时间复杂度O(log_2 n)
    4. 不允许键值冗余

    二、 结点的定义

    2.1 键值对pair

    pair是用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息

    SGI-STL中对于pair的定义:

    1. template <class T1, class T2>
    2. struct pair
    3. {
    4. typedef T1 first_type;
    5. typedef T2 second_type;
    6. T1 first;
    7. T2 second;
    8. pair(): first(T1()), second(T2()) {}
    9. pair(const T1& a, const T2& b): first(a), second(b) {}
    10. };

    使用时需要显示指定元素类型会导致代码过长,并且频繁使用时较为麻烦,于是出现make_pair进行自动类型推导。其定义为:

    1. template <class T1,class T2>
    2. pair make_pair (T1 x, T2 y)
    3. {
    4. return ( pair(x,y) );
    5. }

    2.2 定义细节

    结点中存储了左子树指针、右子树指针和父指针以及一个键值对(即数据域)、平衡因子。不过平衡因子并不是必要的,没有平衡因子同样可以实现AVL树。

    1. struct AVLTreeNode {
    2. AVLTreeNode(const pair& kv) :
    3. _parent(nullptr), _left(nullptr),
    4. _right(nullptr),_data(kv),_balance_factor(0) {}
    5. AVLTreeNode* _parent;
    6. AVLTreeNode* _left;
    7. AVLTreeNode* _right;
    8. pair _data;
    9. int _balance_factor;//平衡因子
    10. };

    普通二叉树和搜索二叉树都是二叉链(没有父指针),为什么AVL树会需要使用三叉链呢?

    这个问题可以从后面的讲解得到答案。
    插入和删除结点、旋转 、平衡因子调整,这些操作都会需要频繁的使用父指针,使用三叉链可以减少查找父结点的时间复杂度并且使得AVL树的实现更加方便。

    三、 AVL树的插入操作

    AVL本质上就是具有特殊性质的二叉搜索树,其插入操作与二叉搜索树较为相似,不过更为复杂。

    AVL树的插入过程可以分为两步
    1. 按照二叉搜索树的方式插入新节点    2. 调整节点的平衡因子

    1. bool insert(const pair& kv) {
    2. //第一步: 按照二叉搜索树的方式插入新节点 
    3. if (_root == nullptr) {
    4. _root = new TreeNode(kv);
    5. return true;
    6. }
    7. TreeNode* parent = nullptr;
    8. TreeNode* cur = _root;
    9. while (cur != nullptr) {
    10. if (kv.first > cur->_data.first) {
    11. parent = cur;
    12. cur = cur->_right;
    13. }
    14. else if (kv.first < cur->_data.first) {
    15. parent = cur;
    16. cur = cur->_left;
    17. }
    18. else return false;
    19. }
    20. cur = new TreeNode(kv);
    21. if (kv.first > parent->_data.first) {
    22. parent->_right = cur;
    23. }
    24. else { //kv.first < parent->_data.first)
    25. parent->_left = cur;
    26. }
    27. cur->_parent = parent;
    28. //调整平衡因子
    29. //………………
    30. }

    但是平衡因子是如何调整的呢?

    3.1 平衡因子调整规则

    1. 若新增结点在其父结点的左边,则父结点的平衡因子 -1;若新增结点在其父结点的右边,则父结点的平衡因子 +1。

    2. 更新后,若父结点的平衡因子为1 或 -1,说明插入前父结点的平衡因子为0,插入后父结点所在子树的高度发生变化,需要继续向上更新

    3.更新后,若父结点的平衡因子为0,说明插入前父结点的平衡因子为1 或 -1,插入到了父结点矮的一边,父结点所在子树的高度并未发生变化,所以不需继续向上更新

    4.更新后,若父结点的平衡因子为2 或 -2,说明插入前父结点的平衡因子为1 或 -1(达到平衡临界值),插入后已经破坏平衡,此时父结点所子树需要进行旋转处理

    5.更新后,若父结点的平衡因子的绝对值大于2(理论上而言不可能),说明插入前该树就不是AVL树,需检查之前的操作。

    1. while (parent != nullptr){
    2. //规则1
    3. if (cur == parent->_right) ++parent->_balance_factor;
    4. else --parent->_balance_factor;
    5. //规则2
    6. if(parent->_balance_factor == 0) break;
    7. //规则3
    8. else if (abs(parent->_balance_factor) == 1) {
    9. cur = parent;
    10. parent = parent->_parent;
    11. }
    12. //规则4
    13. else if (abs(parent->_balance_factor) == 2) {
    14. //需要旋转
    15. //………………
    16. }
    17. //规则5
    18. else {
    19. assert(false);
    20. }
    21. return true;
    22. }

    3.2 旋转规则

    AVL树的旋转可以分为四种:

    3.2.1 新结点插入较高左子树的左侧 — 左左:右单旋 

    1. //发生右旋的判断条件
    2. if (parent->_balance_factor == -2 && cur->_balance_factor == -1) {
    3. rotate_right(parent);
    4. }
    1. void rotate_right(TreeNode* parent) {
    2. TreeNode* subL = parent->_left;
    3. TreeNode* subLR = subL->_right;
    4. TreeNode* pparent = parent->_parent;//发生旋转的子树的父结点
    5. parent->_left = subLR;
    6. if (subLR != nullptr) subLR->_parent = parent;//h可能为0,即空树
    7. subL->_right = parent;
    8. parent->_parent = subL;
    9. //父结点所在的子树发生右旋转后,该子树的根结点发生改变
    10. if (_root == parent) {
    11. _root = subL;
    12. subL->_parent = nullptr;
    13. }
    14. else {
    15. if (pparent->_left == parent) pparent->_left = subL;
    16. else pparent->_right = subL;
    17. subL->_parent = pparent;
    18. }
    19. //更新平衡因子
    20. subL->_balance_factor = parent->_balance_factor = 0;
    21. }

    3.2.2 新结点插入较高右子树的右侧 — 右右:左单旋

    1. //发生左旋的判断条件
    2. if (parent->_balance_factor == 2 && cur->_balance_factor == 1) {
    3. rotate_left(parent);
    4. }
    1. void rotate_left(TreeNode* parent) {
    2. TreeNode* subR = parent->_right;
    3. TreeNode* subRL = subR->_left;
    4. TreeNode* pparent = parent->_parent;
    5. parent->_right = subRL;
    6. if (subRL != nullptr) subRL->_parent = parent;
    7. subR->_left = parent;
    8. parent->_parent = subR;
    9. //解决根结点变换带来的问题
    10. if (_root == parent) {
    11. _root = subR;
    12. subR->_parent = nullptr;
    13. }
    14. else {
    15. if (pparent->_left == parent) pparent->_left = subR;
    16. else pparent->_right = subR;
    17. subR->_parent = pparent;
    18. }
    19. subR->_balance_factor = parent->_balance_factor = 0;
    20. }

    3.2.3 新结点插入较高左子树的右侧 — 左右:左右双旋

    1. void rotate_left_right(TreeNode* parent) {
    2. TreeNode* subL = parent->_left;
    3. TreeNode* subLR = subL->_right;
    4. int bf = subLR->_balance_factor;
    5. rotate_left(parent->_left);
    6. rotate_right(parent);
    7. //更新平衡因子
    8. subLR->_balance_factor = 0;
    9. //情况一
    10. if (bf == 1) {
    11. parent->_balance_factor = 0;
    12. subL->_balance_factor = -1;
    13. }
    14. //情况二
    15. else if (bf == -1) {
    16. parent->_balance_factor = 1;
    17. subL->_balance_factor = 0;
    18. }
    19. //情况三
    20. else if (bf == 0) {
    21. parent->_balance_factor = 0;
    22. subL->_balance_factor = 0;
    23. }
    24. else assert(false);
    25. }

    3.2.4 新结点插入较高右子树的左侧 — 右左:右左双旋

    1. void rotate_right_left(TreeNode* parent) {
    2. TreeNode* subR = parent->_right;
    3. TreeNode* subRL = subR->_left;
    4. int bf = subRL->_balance_factor;
    5. rotate_right(parent->_right);
    6. rotate_left(parent);
    7. //调整平衡因子
    8. subRL->_balance_factor = 0;
    9. //情况一
    10. if (bf == 1) {
    11. parent->_balance_factor = -1;
    12. subR->_balance_factor = 0;
    13. }
    14. //情况二
    15. else if (bf == -1) {
    16. parent->_balance_factor = 0;
    17. subR->_balance_factor = 1;
    18. }
    19. //情况三
    20. else if (bf == 0) {
    21. parent->_balance_factor = 0;
    22. subR->_balance_factor = 0;
    23. }
    24. else assert(false);
    25. }

    四、 AVL树的删除操作

    因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不
    过与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。
     

    五、 AVL树性能分析

    AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1
    这样可以保证查询时高效的时间复杂度,即O(log_2 N)
    但是若要对AVL树做一些结构修改的操作,性能非常低下。比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置
    因此若需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合

    六、 完整代码

    1. #include
    2. #include
    3. #include
    4. #include
    5. using std::cout;
    6. using std::endl;
    7. using std::max;
    8. using std::swap;
    9. using std::pair;
    10. using std::make_pair;
    11. template<class K,class V>
    12. struct AVLTreeNode {
    13. AVLTreeNode(const pair& kv) :_parent(nullptr), _left(nullptr), _right(nullptr),_data(kv),_balance_factor(0) {}
    14. AVLTreeNode* _parent;
    15. AVLTreeNode* _left;
    16. AVLTreeNode* _right;
    17. pair _data;
    18. int _balance_factor;//平衡因子
    19. };
    20. template<class K, class V>
    21. class AVLTree
    22. {
    23. typedef AVLTreeNode TreeNode;
    24. public:
    25. bool insert(const pair& kv) {
    26. if (_root == nullptr) {
    27. _root = new TreeNode(kv);
    28. return true;
    29. }
    30. TreeNode* parent = nullptr;
    31. TreeNode* cur = _root;
    32. while (cur != nullptr) {
    33. if (kv.first > cur->_data.first) {
    34. parent = cur;
    35. cur = cur->_right;
    36. }
    37. else if (kv.first < cur->_data.first) {
    38. parent = cur;
    39. cur = cur->_left;
    40. }
    41. else return false;
    42. }
    43. cur = new TreeNode(kv);
    44. if (kv.first > parent->_data.first) {
    45. parent->_right = cur;
    46. }
    47. else { //kv.first < parent->_data.first)
    48. parent->_left = cur;
    49. }
    50. cur->_parent = parent;
    51. //控制平衡
    52. //更新平衡因子
    53. while (parent != nullptr){
    54. if (cur == parent->_right) ++parent->_balance_factor;
    55. else --parent->_balance_factor;
    56. if(parent->_balance_factor == 0) break;
    57. else if (abs(parent->_balance_factor) == 1) {
    58. cur = parent;
    59. parent = parent->_parent;
    60. }
    61. else if (abs(parent->_balance_factor) == 2) {
    62. //需要旋转
    63. if (parent->_balance_factor == 2 && cur->_balance_factor == 1) {
    64. rotate_left(parent);
    65. }
    66. else if (parent->_balance_factor == -2 && cur->_balance_factor == -1) {
    67. rotate_right(parent);
    68. }
    69. else if (parent->_balance_factor == -2 && cur->_balance_factor == 1) {
    70. rotate_left_right(parent);
    71. }
    72. else if (parent->_balance_factor == 2 && cur->_balance_factor == -1) {
    73. rotate_right_left(parent);
    74. }
    75. else assert(false);
    76. break;
    77. }
    78. else {
    79. assert(false);
    80. }
    81. }
    82. return true;
    83. }
    84. void inorder() {
    85. _inorder(_root);
    86. }
    87. bool IsBlance() {
    88. return _IsBlance(_root);
    89. }
    90. private:
    91. void _inorder(TreeNode* root) {
    92. if (root == nullptr) {
    93. return;
    94. }
    95. _inorder(root->_left);
    96. cout << root->_data.first << " ";
    97. _inorder(root->_right);
    98. }
    99. bool _IsBlance(TreeNode* root) {
    100. if (root == nullptr) return true;
    101. int diff = Height(root->_right) - Height(root->_left);
    102. if (diff != root->_balance_factor) {
    103. cout << root->_data.first << "结点的平衡因子异常" << endl;
    104. return false;
    105. }
    106. return abs(diff) < 2 && _IsBlance(root->_left) && _IsBlance(root->_right);
    107. }
    108. int Height(TreeNode* root) {
    109. if (root == nullptr) return 0;
    110. return max(Height(root->_left),Height(root->_right)) + 1;
    111. }
    112. void rotate_left(TreeNode* parent) {
    113. TreeNode* subR = parent->_right;
    114. TreeNode* subRL = subR->_left;
    115. TreeNode* pparent = parent->_parent;
    116. parent->_right = subRL;
    117. if (subRL != nullptr) subRL->_parent = parent;
    118. subR->_left = parent;
    119. parent->_parent = subR;
    120. //解决根结点变换带来的问题
    121. if (_root == parent) {
    122. _root = subR;
    123. subR->_parent = nullptr;
    124. }
    125. else {
    126. if (pparent->_left == parent) pparent->_left = subR;
    127. else pparent->_right = subR;
    128. subR->_parent = pparent;
    129. }
    130. subR->_balance_factor = parent->_balance_factor = 0;
    131. }
    132. void rotate_right(TreeNode* parent) {
    133. TreeNode* subL = parent->_left;
    134. TreeNode* subLR = subL->_right;
    135. TreeNode* pparent = parent->_parent;
    136. parent->_left = subLR;
    137. if (subLR != nullptr) subLR->_parent = parent;
    138. subL->_right = parent;
    139. parent->_parent = subL;
    140. if (_root == parent) {
    141. _root = subL;
    142. subL->_parent = nullptr;
    143. }
    144. else {
    145. if (pparent->_left == parent) pparent->_left = subL;
    146. else pparent->_right = subL;
    147. subL->_parent = pparent;
    148. }
    149. subL->_balance_factor = parent->_balance_factor = 0;
    150. }
    151. void rotate_left_right(TreeNode* parent) {
    152. TreeNode* subL = parent->_left;
    153. TreeNode* subLR = subL->_right;
    154. int bf = subLR->_balance_factor;
    155. rotate_left(parent->_left);
    156. rotate_right(parent);
    157. //更新平衡因子
    158. subLR->_balance_factor = 0;
    159. if (bf == 1) {
    160. parent->_balance_factor = 0;
    161. subL->_balance_factor = -1;
    162. }
    163. else if (bf == -1) {
    164. parent->_balance_factor = 1;
    165. subL->_balance_factor = 0;
    166. }
    167. else if (bf == 0) {
    168. parent->_balance_factor = 0;
    169. subL->_balance_factor = 0;
    170. }
    171. else assert(false);
    172. }
    173. void rotate_right_left(TreeNode* parent) {
    174. TreeNode* subR = parent->_right;
    175. TreeNode* subRL = subR->_left;
    176. int bf = subRL->_balance_factor;
    177. rotate_right(parent->_right);
    178. rotate_left(parent);
    179. subRL->_balance_factor = 0;
    180. if (bf == 1) {
    181. parent->_balance_factor = -1;
    182. subR->_balance_factor = 0;
    183. }
    184. else if (bf == -1) {
    185. parent->_balance_factor = 0;
    186. subR->_balance_factor = 1;
    187. }
    188. else if (bf == 0) {
    189. parent->_balance_factor = 0;
    190. subR->_balance_factor = 0;
    191. }
    192. else assert(false);
    193. }
    194. private:
    195. TreeNode* _root = nullptr;
    196. };
  • 相关阅读:
    使用Spark清洗统计业务数据并保存到数据库中
    02- 数据结构与算法 - 最长回文子串(动态规划/中心扩展算法/Manacher 算法)
    机器学习/人工智能的笔试面试题目——CNN相关问题总结
    tailwindcss -原子化 CSS 框架
    大模型应用开发:运行你的第一个聊天程序
    git图形化管理工具
    DS1302时钟芯片(SPI协议)
    国债1万亿,你该学点什么
    C/C++通过位操作实现2个uint32_t合并为uint64_t
    请求转发(J2EE)
  • 原文地址:https://blog.csdn.net/GG_Bruse/article/details/127883235