• C++【AVL树】


    目录

    一、什么是AVL树

    插入

    更新平衡因子的规则

    旋转的规则

    第二种情况:左旋

    第一种情况:右旋

    第三种情况:左右旋

    第四种情况:右左旋

    测试一下​​​​​​​

    检查是不是平衡二叉树

    求高度

     随机插入一组数据测试


    二叉搜索树如果不平衡的话,就没办法发挥出二叉搜索树的优势

    AVLTree,红黑树

    一、什么是AVL树

    二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1

    (需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
    一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
    它的左右子树都是AVL树
    左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1 )

    AVL树又可以称为高度平衡二叉搜索树 

    为什么这里控制高度之差不超过1,而不是相等?

    因为没有办法保证。

    比方说我们的树只有两个结点,那么必然是一个为根节点,一个为左子树结点或者右子树结点,所以左子树的高度和右子树的高度是不可能相等的。我们做不到 

    这样的树不是AVL树,因为我们没有满足每一个结点的左右子树的高度的差都是小于等于1

     通过控制树的高度去控制树的平衡

    这里的平衡因子就是高度差

    平衡因子=右子树的高度-左子树的高度

    (非必须)也可以不要,我们用的话是为了方便实现。

    也就是为了让我们有一个量化的值可以去查看

    插入

    如果我们是在8的左子树插入了一个7,那么8的左右子树的高度差变成0,依旧满足我们平衡二叉树的特性。

    但是如果在9的右子树加上了一个10呢?

    此时我们的9的左右子树的差是1,我们8的左右子树的差变成了2,我们需要对其进行调整和旋转。

    更新平衡因子的规则

    1、新增在右子树 

    如果新增的结点在父节点的右子树,我们需要将父节点的平衡因子+1,因为平衡因子的计算时右子树的高度减去左子树的高度,我们这里右子树的高度+1,但是左子树的高度并没有发生变化

    parent->bf++

    2、新增在左子树

    如果新增的结点在父节点的左子树,我们需要将父节点的平衡因子-1,因为平衡因子的计算时右子树的高度减去左子树的高度,我们这里左子树的高度+1,但是右子树的高度并没有发生变化

    parent->bf--

    3、更新后,parent->bf==1 或者 -1

    说明parent更新前的平衡因子是0,说明左右子树高度相等。

    插入后有一边高。

    parent高度变了,需要继续往上更新!(对上一层有影响)

    4、更新后,parent->bf==0

    说明parent更新前的平衡因子是1或者-1,说明左右子树高度相差1。

    插入后左右子树高度相等,插入填上了矮的那一边。

    parent所在的子树高度不变,不需要继续往上更新!(对上一层没有影响)

    5、更新后,parent->bf==2 或者 -2

    说明parent更新前的平衡因子是1或者-1,说明左右子树高度相差1。

    (原本就是平衡的临界值了)

    插入后原本就是高的一边更加高,打破了平衡。

    parent高度变了,需要旋转处理

    6、更新后parent->bf >2or<-2的值?

    不可能,如果存在,则说明插入前就不是AVL树,需要去检查之前的操作的问题。

     

    下面的情况就需要旋转 

     

     如何用代码实现我们上述的方法

    1. //最坏的情况是更新到我们的根节点
    2. //如果parent为空,也就是更新到了我们的根节点,没有上层节点了
    3. while(parent)
    4. {
    5. if(cur==parent->_right)
    6. {
    7. parent->_bf++;
    8. }
    9. else
    10. {
    11. parent->_bf--;
    12. }
    13. //父节点的bf为0的话,就停止迭代。
    14. if((parent->_bf)==0)
    15. {
    16. break;
    17. }
    18. //如果parent的_bf是1或者-1
    19. //往上迭代
    20. else if(abs(parent->_bf)==1)
    21. {
    22. parent=parent->_parent;
    23. cur=cur->_parent;
    24. }
    25. else if(abs(parent->_bf)==2)
    26. {
    27. //说明parent所在的子树已经不平衡了,需要旋转来调整
    28. }
    29. //说明parent的平衡因子>2,也就是在插入我们这个结点前面,我们的平衡二叉树已经不平衡了
    30. else
    31. {
    32. //直接抛出异常
    33. assert(false);
    34. }
    35. }

    我们观察到平衡因子是控制我们这一整棵树的关键。 

    旋转的规则

    出问题的时候一定是以parent为根节点所在的子树出问题了

    处理的原则

    1、旋转成平衡树 

    2、保持搜索树规则

    假设我们往树中依次插入

    1        3        5

     

    上面的树右边高,我们就需要往左边转,也就是左旋!

    更加复杂一点的情况

     

     

     

    我们往树中依次插入

    5        3        1         

     旋转的位置有可能是整棵树,也有可能是子树

    1.新节点插入较高左子树的左侧---左左:右单旋 

     

    2.新节点插入较高右子树的右侧---右右:左单旋

    3.新节点插入较高左子树的右侧---左右:先左单旋再右单旋

    4.新节点插入较高右子树的左侧---右左:先右单旋再左单旋

     

    按照上面的图例演示,举几个简单的例子

    h==0

     

    h==1 

     

    所以这里的具象图有着无限多种情况,但是我们用上面的抽象图其实都是可以解释的。 

    第二种情况:左旋

      

    只有parent和subR的平衡因子发生了变化,且都变成0

    1. void RotateL(Node* parent)
    2. {
    3. Node* subR=parent->_right;
    4. Node* subRL=subR->_left;
    5. parent->_right=subRL;
    6. //修改三叉链里面的父亲结点
    7. if(subRL)
    8. {
    9. subRL->_parent=parent;
    10. }
    11. Node* ppNode=parent->_parent;
    12. subR->_left=parent;
    13. parent->_parent=subR;
    14. //1、parent是整棵子树的根
    15. if(parent==_root)
    16. {
    17. _root=subR;
    18. subR->_parent= nullptr;
    19. }
    20. //2、parent是子树的根,也就是parent结点上面还有祖先结点
    21. else
    22. {
    23. if(ppNode->_left==parent)
    24. {
    25. ppNode->_left=subR;
    26. }
    27. else
    28. {
    29. ppNode->_right=subR;
    30. }
    31. subR->_parent=ppNode;
    32. }
    33. //修改平衡因子
    34. subR->_bf=parent->_bf=0;
    35. }

    第一种情况:右旋

    1. //右旋
    2. void RotateR(Node* parent)
    3. {
    4. Node*subL=parent->_left;
    5. Node*subLR=subL->_right;
    6. parent->_left=subLR;
    7. //修改三叉链表里面的父亲节点
    8. if(subLR)
    9. {
    10. subLR->_parent=parent;
    11. }
    12. Node* ppNode=parent->_parent;
    13. subL->_right=parent;
    14. parent->_parent=subL;
    15. //1.parent是整个子树的根
    16. if(parent==_root)
    17. {
    18. _root=subL;
    19. subL->_parent= nullptr;
    20. }
    21. //2.parent是子树的根,也就是parent结点上面还有祖先结点
    22. else
    23. {
    24. if(ppNode->_left==parent)
    25. {
    26. ppNode->_left=subL;
    27. }
    28. else
    29. {
    30. ppNode->_right=subL;
    31. }
    32. subL->_parent=ppNode;
    33. }
    34. //修改平衡因子
    35. subL->_bf=parent->_bf=0;
    36. }

    第三种情况:左右旋

    case1:在b插入

     无论是在b的位置插入还是在c的位置插入,b or c高度变为h,都会引发双旋。

    case2:在c插入 

     case3:h=0的情况(60自己就是新增)

     这里对应的90的结点就是我们的parent

    几个相关结点的标记如下

    case1:

     case2:

    case3:

    1. //左右旋
    2. void RotateLR(Node* parent)
    3. {
    4. Node* subL=parent->_left;
    5. Node* subLR=subL->_right;
    6. int bf=subLR->_bf;
    7. RotateL(parent->_left);
    8. RotateR(parent);
    9. //平衡因子的更新
    10. //对上面的三种情况分别更改平衡因子
    11. subLR->_bf=0;
    12. //case1
    13. if(bf==1)
    14. {
    15. parent->_bf=0;
    16. subL->_bf=-1;
    17. }
    18. //case2
    19. else if(bf==-1)
    20. {
    21. parent->_bf=1;
    22. subL->_bf=0;
    23. }
    24. //case3
    25. else if(bf==0)
    26. {
    27. parent->_bf=0;
    28. subL->_bf=0;
    29. }
    30. else
    31. {
    32. assert(false);
    33. }
    34. }

    第四种情况:右左旋

    case1:在c位置插入新数据

     case2:在b位置插入新数据

    case3:h==0,(60自己就是新增) 

     几个相关结点标记如下

    case1:

    case2:

    case3:

    1. //左右旋
    2. void RotateRL(Node* parent)
    3. {
    4. Node* subR=parent->_right;
    5. Node* subRL=subR->_left;
    6. int bf=subRL->_bf;
    7. RotateR(parent->_right);
    8. RotateL(parent);
    9. //平衡因子的更新
    10. //对上面的三种情况分别更改平衡因子
    11. subRL->_bf=0;
    12. //case1
    13. if(bf==1)
    14. {
    15. parent->_bf=-1;
    16. subR->_bf=0;
    17. }
    18. //case2
    19. else if(bf==-1)
    20. {
    21. parent->_bf=0;
    22. subR->_bf=1;
    23. }
    24. //case3
    25. else if(bf==0)
    26. {
    27. parent->_bf=0;
    28. subR->_bf=0;
    29. }
    30. else
    31. {
    32. assert(false);
    33. }
    34. }

    旋转的价值和意义

    1.让子树保持平衡

    2.降低树的高度(高度恢复到插入之前的样子)

    测试一下

    1. #ifndef AVL_AVLTREE_H
    2. #define AVL_AVLTREE_H
    3. #pragma once
    4. using namespace std;
    5. template<class K,class V>
    6. struct AVLTreeNode
    7. {
    8. AVLTreeNode* _left;
    9. AVLTreeNode* _right;
    10. //三叉链,更新结点可能会影响结点的祖先
    11. AVLTreeNode* _parent;
    12. pair _kv;
    13. int _bf;//平衡因子 balance factor
    14. AVLTreeNode(const pair&kv)
    15. :_left(nullptr)
    16. ,_right(nullptr)
    17. ,_parent(nullptr)
    18. ,_kv(kv)
    19. ,_bf(0)
    20. {
    21. }
    22. };
    23. template<class K,class V>
    24. struct AVLTree
    25. {
    26. typedef AVLTreeNodeNode;
    27. public:
    28. bool Insert(const pair&kv)
    29. {
    30. if(_root== nullptr)
    31. {
    32. _root=new Node(kv);
    33. return true;
    34. }
    35. Node * parent= nullptr;
    36. Node* cur=_root;
    37. while(cur)
    38. {
    39. if(cur->_kv.first
    40. {
    41. parent=cur;
    42. cur=cur->_right;
    43. }
    44. else if(cur->_kv.first>kv.first)
    45. {
    46. parent=cur;
    47. cur=cur->_left;
    48. }
    49. else
    50. {
    51. //相等的话,不插入,去重
    52. return false;
    53. }
    54. }
    55. //插入我们的新节点
    56. cur=new Node(kv);
    57. if(parent->_kv.first
    58. {
    59. parent->_right=cur;
    60. }
    61. else
    62. {
    63. parent->_left=cur;
    64. }
    65. //及时更新我们三叉链的parent的属性
    66. cur->_parent=parent;
    67. //控制平衡
    68. //1、沿着这个位置往上更新平衡因子
    69. //这里的新插入的结点会影响祖先结点的平衡
    70. //最坏的情况是更新到我们的根节点
    71. //如果parent为空,也就是更新到了我们的根节点,没有上层节点了
    72. while(parent)
    73. {
    74. if(cur==parent->_right)
    75. {
    76. parent->_bf++;
    77. }
    78. else
    79. {
    80. parent->_bf--;
    81. }
    82. //父节点的bf为0的话,就停止迭代。
    83. if((parent->_bf)==0)
    84. {
    85. break;
    86. }
    87. //如果parent的_bf是1或者-1
    88. //往上迭代
    89. else if(abs(parent->_bf)==1)
    90. {
    91. parent=parent->_parent;
    92. cur=cur->_parent;
    93. }
    94. else if(abs(parent->_bf)==2)
    95. {
    96. //说明parent所在的子树已经不平衡了,需要旋转来调整
    97. //右右--左旋
    98. if((parent->_bf==2)&&(cur->_bf==1))
    99. {
    100. RotateL(parent);
    101. }
    102. else if((parent->_bf==-2)&&(cur->_bf==-1))
    103. {
    104. RotateR(parent);
    105. }
    106. else if(parent->_bf==-2&&cur->_bf==1)
    107. {
    108. RotateLR(parent);
    109. }else if(parent->_bf==2&&cur->_bf==-1)
    110. {
    111. RotateRL(parent);
    112. }else{
    113. assert(false);
    114. }
    115. break;
    116. }
    117. //说明parent的平衡因子>2,也就是在插入我们这个结点前面,我们的平衡二叉树已经不平衡了
    118. else
    119. {
    120. //直接抛出异常
    121. assert(false);
    122. }
    123. }
    124. return true;
    125. }
    126. void InOrder()
    127. {
    128. _InOrder(_root);
    129. cout << endl;
    130. }
    131. private:
    132. //左旋
    133. void RotateL(Node* parent)
    134. {
    135. Node* subR=parent->_right;
    136. Node* subRL=subR->_left;
    137. parent->_right=subRL;
    138. //修改三叉链里面的父亲结点
    139. if(subRL)
    140. {
    141. subRL->_parent=parent;
    142. }
    143. Node* ppNode=parent->_parent;
    144. subR->_left=parent;
    145. parent->_parent=subR;
    146. //1、parent是整棵子树的根
    147. if(parent==_root)
    148. {
    149. _root=subR;
    150. subR->_parent= nullptr;
    151. }
    152. //2、parent是子树的根,也就是parent结点上面还有祖先结点
    153. else
    154. {
    155. if(ppNode->_left==parent)
    156. {
    157. ppNode->_left=subR;
    158. }
    159. else
    160. {
    161. ppNode->_right=subR;
    162. }
    163. subR->_parent=ppNode;
    164. }
    165. //修改平衡因子
    166. subR->_bf=parent->_bf=0;
    167. }
    168. //右旋
    169. void RotateR(Node* parent)
    170. {
    171. Node*subL=parent->_left;
    172. Node*subLR=subL->_right;
    173. parent->_left=subLR;
    174. //修改三叉链表里面的父亲节点
    175. if(subLR)
    176. {
    177. subLR->_parent=parent;
    178. }
    179. Node* ppNode=parent->_parent;
    180. subL->_right=parent;
    181. parent->_parent=subL;
    182. //1.parent是整个子树的根
    183. if(parent==_root)
    184. {
    185. _root=subL;
    186. subL->_parent= nullptr;
    187. }
    188. //2.parent是子树的根,也就是parent结点上面还有祖先结点
    189. else
    190. {
    191. if(ppNode->_left==parent)
    192. {
    193. ppNode->_left=subL;
    194. }
    195. else
    196. {
    197. ppNode->_right=subL;
    198. }
    199. subL->_parent=ppNode;
    200. }
    201. //修改平衡因子
    202. subL->_bf=parent->_bf=0;
    203. }
    204. //左右旋
    205. void RotateLR(Node* parent)
    206. {
    207. Node* subL=parent->_left;
    208. Node* subLR=subL->_right;
    209. int bf=subLR->_bf;
    210. RotateL(parent->_left);
    211. RotateR(parent);
    212. //平衡因子的更新
    213. //对上面的三种情况分别更改平衡因子
    214. subLR->_bf=0;
    215. //case1
    216. if(bf==1)
    217. {
    218. parent->_bf=0;
    219. subL->_bf=-1;
    220. }
    221. //case2
    222. else if(bf==-1)
    223. {
    224. parent->_bf=1;
    225. subL->_bf=0;
    226. }
    227. //case3
    228. else if(bf==0)
    229. {
    230. parent->_bf=0;
    231. subL->_bf=0;
    232. }
    233. else
    234. {
    235. assert(false);
    236. }
    237. }
    238. //左右旋
    239. void RotateRL(Node* parent)
    240. {
    241. Node* subR=parent->_right;
    242. Node* subRL=subR->_left;
    243. int bf=subRL->_bf;
    244. RotateR(parent->_right);
    245. RotateL(parent);
    246. //平衡因子的更新
    247. //对上面的三种情况分别更改平衡因子
    248. subRL->_bf=0;
    249. //case1
    250. if(bf==1)
    251. {
    252. parent->_bf=-1;
    253. subR->_bf=0;
    254. }
    255. //case2
    256. else if(bf==-1)
    257. {
    258. parent->_bf=0;
    259. subR->_bf=1;
    260. }
    261. //case3
    262. else if(bf==0)
    263. {
    264. parent->_bf=0;
    265. subR->_bf=0;
    266. }
    267. else
    268. {
    269. assert(false);
    270. }
    271. }
    272. void _InOrder(Node* root)
    273. {
    274. if (root == nullptr)
    275. {
    276. return;
    277. }
    278. _InOrder(root->_left);
    279. cout << root->_kv.first << ":" << root->_kv.second << endl;
    280. _InOrder(root->_right);
    281. }
    282. private:
    283. Node* _root=nullptr;
    284. };
    285. #endif //AVL_AVLTREE_H

     

    1. void TestAVLTree1()
    2. {
    3. int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
    4. AVLTree<int, int> t1;
    5. for (auto e : a)
    6. {
    7. t1.Insert(make_pair(e, e));
    8. }
    9. t1.InOrder();
    10. }

     

    检查是不是平衡二叉树

     但是中序遍历有序,只能说明我们的avl树是一棵搜索树,并不能直接说明是平衡二叉树,我们也不能靠检查平衡因子的方式去检查,因为我们代码运行的条件就是检查的平衡因子。所以不能这么查。我们这里不妨查平衡二叉树的高度来检查是不是平衡二叉树

    求高度

    1. int Height(Node* root)
    2. {
    3. if (root == nullptr)
    4. return 0;
    5. return max(Height(root->_left), Height(root->_right)) + 1;
    6. }
    1. bool _IsBalance(Node* root)
    2. {
    3. if (root == nullptr)
    4. {
    5. return true;
    6. }
    7. int leftHT = Height(root->_left);
    8. int rightHT = Height(root->_right);
    9. int diff = rightHT - leftHT;
    10. if (diff != root->_bf)
    11. {
    12. cout << root->_kv.first << "平衡因子异常" << endl;
    13. return false;
    14. }
    15. return abs(diff) < 2
    16. && _IsBalance(root->_left)
    17. && _IsBalance(root->_right);
    18. }

     测试

    1. void TestAVLTree1()
    2. {
    3. int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 }; // 测试双旋平衡因子调节
    4. // int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
    5. AVLTree<int, int> t1;
    6. for (auto e : a)
    7. {
    8. t1.Insert(make_pair(e, e));
    9. }
    10. t1.InOrder();
    11. cout <<"IsBalance:"<IsBalance() << endl;
    12. }

     

     

     随机插入一组数据测试

    1. void TestAVLTree2()
    2. {
    3. size_t N = 10000;
    4. srand(time(0));
    5. AVLTree<int, int> t1;
    6. for (size_t i = 0; i < N; ++i)
    7. {
    8. int x = rand();
    9. t1.Insert(make_pair(x, i));
    10. /* bool ret = t1.IsBalance();
    11. if (ret == false)
    12. {
    13. int u = 1;
    14. }
    15. else
    16. {
    17. cout << "Insert:" << x << " IsBalance:" <
    18. }*/
    19. }
    20. cout << "IsBalance:" << t1.IsBalance() << endl;
    21. }

     

  • 相关阅读:
    Bert相关面试题整理
    Dubbo—— 一个服务既是消费者又是提供者
    【Spring面试】八、事务相关
    积分商城游戏能够给商家带来什么?怎么搭建积分商城?
    CentOS 环境 SIPp 安装及使用
    b2b2c o2o 多商家入驻商城 直播带货商城 电子商务
    攻防世界_MISC之碎纸机11
    Photoshop图层混合模式公式(Unity,CG实现)
    WinHex(三)
    javaweb没有web.xml设置方法
  • 原文地址:https://blog.csdn.net/weixin_62684026/article/details/127585338