• C++数据结构(上):模拟实现AVL


     "我们无法一直活在过去,亦如我们从未能挽救死亡"


    (1)AVL树:

    概念:平衡搜索二叉树。

    性质:

    ①根节点的左右子树 是AVL树

    ②左右子树的高度的差 不超过1

    ③比根节点大的 插在根节点的右边。 小的 在左边。


    (2)AVL树模拟实现: 

     关于AVL树概念上的东西,多讲无用。我们现在直接来实现。

    ① 结构框架:

    注:这里实现的是key值版。

    1. //AVL树的 节点
    2. template<class T>
    3. struct AVLTreeNode
    4. {
    5. //节点指针
    6. AVLTreeNode* _left;
    7. AVLTreeNode* _right;
    8. AVLTreeNode* _parent;
    9. //节点数值
    10. T _key;
    11. //为了调节平衡 这里引入平衡因子;
    12. int _bf;
    13. //构造
    14. AVLTreeNode(const T& key)
    15. :_left(nullptr),
    16. : _right(nullptr),
    17. _parent(nullptr),
    18. _key(key),
    19. _bf(0) //任何新插入的 节点 平衡因子都默认设置为0
    20. {}
    21. };
    22. template<class K>
    23. class AVLTree
    24. {
    25. typedef AVLTreeNode Node;
    26. AVLTree()
    27. :_root(nullptr)
    28. {}
    29. private:
    30. Node* _root;
    31. };

    对于AVL树而言,显然难点在插入的部分。所以这留到最后讲。那么这部分进行相关功能的实现。

     ②查找+中序遍历:

    1. Node* find(const K& key)
    2. {
    3. Node* cur = _root;
    4. while (cur)
    5. {
    6. if (key > cur->_key)
    7. {
    8. cur = cur->_right;
    9. }
    10. else if(key < cur->_key)
    11. {
    12. cur = cur->_left;
    13. }
    14. else
    15. {
    16. //找到了
    17. return cur;
    18. }
    19. }
    20. return nullptr;
    21. }
    22. void _InOrder(Node* root)
    23. {
    24. if (root == nullptr)
    25. return;
    26. _InOrder(root->_left);
    27. cout << root->_key << endl;
    28. _InOrder(root->_right);
    29. }
    30. void InOrder()
    31. {
    32. _InOrder(_root);
    33. }

    ③检查平衡+析构:

    1. void _Destory(Node* root)
    2. {
    3. if (root == nullptr)
    4. return;
    5. _Destory(root->_left);
    6. _Destory(root->_right);
    7. delete root;
    8. }
    9. ~AVLTree()
    10. {
    11. _Destory(_root);
    12. _root =nullptr;
    13. }
    14. int TreeHight(Node* root)
    15. {
    16. if (root == nullptr)
    17. return 0;
    18. int leftdepth = TreeHight(root->_left);
    19. int rightdepth = TreeHight(root->_right);
    20. //返回 左右子树 大的那个
    21. return leftdepth > rightdepth ? leftdepth + 1 : rightdepth + 1;
    22. }
    23. bool _isBalance(Node* root)
    24. {
    25. if (root == nullptr)
    26. return true; //空树也是平衡树
    27. int Maxleft = TreeHight(_root->_left);
    28. int Maxright = TreeHight(_root->_right);
    29. return abs(Maxleft - Maxright) < 2
    30. && _isBalance(root->_left)
    31. && _isBalance(root->_right);
    32. }
    33. bool _isBalance()
    34. {
    35. return _isBalance(_root);
    36. }

     关于如何理解检查这棵树是否平衡的函数,也就不多赘述。因为在前面二叉树篇讲过。

    下面就进入重头戏~


    ④AVL的插入;

            

    1. //如果是第一次插入 直接去做根 root
    2. if (_root == nullptr)
    3. {
    4. _root = new Node(k);
    5. return true;
    6. }
    7. //不是第一个值
    8. Node* cur = _root;
    9. Node* parent = nullptr;
    10. while (cur) //这里和find的思想一样
    11. {
    12. if (key > cur->_key)
    13. {
    14. parent = cur;
    15. cur = cur->_right;
    16. }
    17. else if (key < cur->_key)
    18. {
    19. parent = cur;
    20. cur = cur->_left;
    21. }
    22. else
    23. {
    24. //如果走到这里 说明 已经插入了和key 一样的数
    25. return true;
    26. }
    27. }
    28. //cur 走到空的位置了
    29. cur = new Node(key);
    30. Node* newnode = cur;
    31. //更新与parent的关系
    32. if (cur->_key > parent->_key)
    33. {
    34. parent->_right = cur;
    35. }
    36. else
    37. {
    38. parent->_left = cur;
    39. }
    40. //注意我们设计的是三叉连 结构
    41. //cur 还有反过来 指向parent
    42. cur->_parent = parent;

     

    任何一次对树数据的操作,都可能造成对树结构的破坏。

    所以,为了 保护树的结构。我们需要根据不同情况,对树进行位置的更换

    (1)平衡因子:

     我们让在根节点 右插入对 _bf ++ 

                                    左插入 _bf--;

    由此,每棵树节点的 平衡因子会分为三种情况。

     [0]  [-1,1]  [2,-2]

     

    1. //调节平衡因子;
    2. //while(parent)
    3. while (cur != root)
    4. {
    5. //更新节点bf
    6. if (cur == parent->_left)
    7. {
    8. parent->_bf--;
    9. }
    10. else
    11. {
    12. parent->_bf++;
    13. }
    14. //根据平衡因子 进行调整
    15. if (parent->_bf == 0)
    16. {
    17. break; //不做任何事情
    18. }
    19. else if(parent->_bf ==-1 || parent->_bf == 1)
    20. {
    21. //向上调整
    22. cur = parent;
    23. parent->_parent;
    24. }
    25. else if(parent->_bf == -2 || parent->_bf == 2)
    26. {
    27. //调整
    28. }
    29. else
    30. {
    31. //理论上不会走到这里来
    32. //如果走到这里来 也是调整前 已经不是AVL数
    33. assert(false);
    34. }
    35. }

     

    (2)左右单旋:

     我们在看单旋图中,就是bf=-1,1 的节点(也就是parent) 是旋转的轴。

    1. else if(parent->_bf == -2 || parent->_bf == 2)
    2. {
    3. //调整
    4. //左边新增节点
    5. if (parent->_bf == -2)
    6. {
    7. if (cur->_bf == -1) //新节点在左边插入
    8. {
    9. //左高 右边低 需要右翻转
    10. RotateR(parent);
    11. }
    12. else //新增节点插入在右边
    13. {
    14. }
    15. }
    16. else //右边新增节点
    17. {
    18. if (cur->_bf == 1) //新节点从右边插入
    19. {
    20. //此时 右高 左低 需要 右翻转
    21. RotateL(parent);
    22. }
    23. else //新增节点在左边
    24. {
    25. }
    26. }
    27. //翻转完 就跳出
    28. break;
    29. }

    这也就是单旋插入的逻辑。

    右单旋实现:

    也及时sub subLR parent 三方的关系;

     

    1. void RotateR(Node* parent)
    2. {
    3. Node* sub = parent->_left;
    4. Node* subLR = sub->_right;
    5. //让subLR去做parent的左
    6. parent->_left = subLR;
    7. //subLR可能不存在
    8. if(subLR)
    9. subLR->_parent = parent;
    10. sub->_right = parent;
    11. //记录 父节点 因为 sub 可能不是唯一独立的树
    12. Node* grandparent = parent->_parent;
    13. parent->_parent = sub;
    14. if (parent == _root)
    15. {
    16. //独立树
    17. _root = sub;
    18. sub->_parent = nullptr;
    19. }
    20. else
    21. {
    22. //子树
    23. //parent的 父亲仍然保留着 节点地址
    24. if (grandparent->_left == parent)
    25. {
    26. grandparent->_left = sub;
    27. }
    28. else
    29. {
    30. grandparent->_right = sub;
    31. }
    32. //更新回来
    33. sub->_parent = grandparent;
    34. }
    35. //调节平衡因子
    36. parent->_bf = sub->_bf = 0;
    37. }

     

     

    1. RotateL(Node* parent)
    2. {
    3. Node* sub = parent->_right;
    4. Node* subRL = sub->_left;
    5. //让subRL 去做parent的右边
    6. parent->_right = subRL;
    7. //仍然注意避坑
    8. if(subRL)
    9. subRL->_parent = parent;
    10. Node* grandparent = parent->_parent;
    11. sub->_left = parent;
    12. parent->_parent = sub;
    13. if (parent == _root)
    14. {
    15. _root = sub;
    16. sub->_parent = nullptr;
    17. }
    18. else
    19. {
    20. if (grandparent->_left == parent)
    21. {
    22. grandparent->_left = sub;
    23. }
    24. else
    25. {
    26. grandparent->_right = sub;
    27. }
    28. sub->_parent = grandparent;
    29. }
    30. parent->_bf = sub->_bf = 0;
    31. }

    (3)双旋转:

    左右双旋转:
     

    1. void RotateLR(Node* parent)
    2. {
    3. Node* sub = parent->_left;
    4. Node* subLR = sub->_right;
    5. //保存插入处 的平衡因子~
    6. int bf = subLR->_bf;
    7. RotateL(sub);
    8. RotateR(parent);
    9. //调整平衡因子
    10. if (bf == -1) //左边B插入
    11. {
    12. sub->_bf = 0;
    13. subRL->_bf = 0;
    14. parent->_bf = 1;
    15. }
    16. else if(bf ==1)
    17. {
    18. sub->_bf = -1;
    19. subRL->_bf = 0;
    20. parent->_bf = 0;
    21. }
    22. else if(bf ==0)
    23. {
    24. sub->_bf = 0;
    25. subRL->_bf = 0;
    26. parent->_bf = 0;
    27. }
    28. else
    29. {
    30. assert(false);
    31. }
    32. }

    右左双旋:

    这个同上也就 不再多分析。

    1. void RotateRL(Node* parent)
    2. {
    3. Node* sub = parent->_right;
    4. Node* subRL = sub->_left;
    5. int bf = subRL->_bf;
    6. RotateR(sub);
    7. RotateL(parent);
    8. if (bf == 1) //从C插入
    9. {
    10. subRL->_bf = 0;
    11. sub->_bf = 0;
    12. parent->_bf = -1;
    13. }
    14. else if (bf == -1)
    15. {
    16. parent->_bf = 0;
    17. subRL->_bf = 0;
    18. sub->_bf = 1;
    19. }
    20. else if (bf == 0)
    21. {
    22. parent->_bf = 0;
    23. subRL->_bf = 0;
    24. sub->_bf = 0;
    25. }
    26. else
    27. {
    28. assert(false);
    29. }
    30. }

    ④测试;

    插入也就到此为止,我们先来试着跑几组数据看是否正确。


     

     (3)K、V版AVL 树

    1. namespace dy
    2. {
    3. template<class K, class V>
    4. struct AVLTreeNode
    5. {
    6. //节点指针
    7. AVLTreeNode* _left;
    8. AVLTreeNode* _right;
    9. AVLTreeNode* _parent;
    10. //节点数值
    11. pair _kv;
    12. //为了调节平衡 这里引入平衡因子;
    13. int _bf;
    14. //构造
    15. AVLTreeNode(const pair& kv)
    16. :_left(nullptr),
    17. _right(nullptr),
    18. _parent(nullptr),
    19. _kv(kv),
    20. _bf(0) //任何新插入的 节点 平衡因子都默认设置为0
    21. {}
    22. };
    23. template<class K,class V>
    24. struct AVLTreeKV
    25. {
    26. typedef AVLTreeNode Node;
    27. Node* _root;
    28. AVLTreeKV()
    29. :_root(nullptr)
    30. {}
    31. void _InOrder(Node* root)
    32. {
    33. if (root == nullptr)
    34. return;
    35. _InOrder(root->_left);
    36. cout << root->_kv.first << ":"<_kv.second<
    37. _InOrder(root->_right);
    38. }
    39. //没封装迭代器 暂且用Node* 替
    40. pairbool> insert(const pair kv)
    41. {
    42. //如果是第一次插入 直接去做根 root
    43. if (_root == nullptr)
    44. {
    45. _root = new Node(kv);
    46. return make_pair(_root,true);
    47. }
    48. //不是第一个值
    49. Node* cur = _root;
    50. Node* parent = nullptr;
    51. while (cur) //这里和find的思想一样
    52. {
    53. if (kv.first > cur->_kv.first)
    54. {
    55. parent = cur;
    56. cur = cur->_right;
    57. }
    58. else if (kv.first < cur->_kv.first)
    59. {
    60. parent = cur;
    61. cur = cur->_left;
    62. }
    63. else
    64. {
    65. //如果走到这里 说明 已经插入了和key 一样的数
    66. return make_pair(cur,true);
    67. }
    68. }
    69. //cur 走到空的位置了
    70. cur = new Node(kv);
    71. Node* newnode = cur;
    72. //更新与parent的关系
    73. if (cur->_kv.first > parent->_kv.first)
    74. {
    75. parent->_right = cur;
    76. }
    77. else
    78. {
    79. parent->_left = cur;
    80. }
    81. //注意我们设计的是三叉连 结构
    82. //cur 还有反过来 指向parent
    83. cur->_parent = parent;
    84. //调节平衡因子;
    85. //while(parent)
    86. while (cur != _root)
    87. {
    88. //更新节点bf
    89. if (cur == parent->_left)
    90. {
    91. parent->_bf--;
    92. }
    93. else
    94. {
    95. parent->_bf++;
    96. }
    97. //根据平衡因子 进行调整
    98. if (parent->_bf == 0)
    99. {
    100. break; //不做任何事情
    101. }
    102. else if (parent->_bf == -1 || parent->_bf == 1)
    103. {
    104. //向上调整
    105. cur = parent;
    106. parent = parent->_parent;
    107. }
    108. else if (parent->_bf == -2 || parent->_bf == 2)
    109. {
    110. //调整
    111. //左边新增节点
    112. if (parent->_bf == -2)
    113. {
    114. if (cur->_bf == -1) //新节点在左边插入
    115. {
    116. //左高 右边低 需要右翻转
    117. RotateR(parent);
    118. }
    119. else //新增节点插入在右边
    120. {
    121. //此时 是折线
    122. //使用双旋
    123. RotateLR(parent);
    124. }
    125. }
    126. else //右边新增节点
    127. {
    128. if (cur->_bf == 1) //新节点从右边插入
    129. {
    130. //此时 右高 左低 需要 右翻转
    131. RotateL(parent);
    132. }
    133. else //新增节点在左边
    134. {
    135. RotateRL(parent);
    136. }
    137. }
    138. //翻转完 就跳出
    139. break;
    140. }
    141. else
    142. {
    143. //理论上不会走到这里来
    144. //如果走到这里来 也是调整前 已经不是AVL数
    145. assert(false);
    146. }
    147. }
    148. return make_pair(newnode,true);
    149. }
    150. }

     


    总结: 

    ①AVL树 插入节点时很看重平衡因子。

    ②平衡因子条件 [0] [-1,1] [-2,2]

    ③单旋是直线 双旋 为折线

    内容到这里也就结束啦,感谢你的阅读。

    祝你好运~

     

  • 相关阅读:
    PMP每日一练 | 考试不迷路-11.29(包含敏捷+多选)
    springboot 整合使用redis发布订阅功能
    Docker搭建ctfd平台
    jmeter-常用的一些java代码
    java中的IO缓冲流(高效流)---原始流的升级版
    tomcat优化
    Vue脚手架的初次了解/使用
    Mysql 语句优化方案—官方原版
    Vue--》详解Vue组件生命周期的三个阶段
    Web安全—Web漏扫工具Nikto安装与使用
  • 原文地址:https://blog.csdn.net/RNGWGzZs/article/details/126016218