• 【C++进阶】AVL树


    0.前言

    前面我们已经学习过二叉搜索树了,但如果我们是用二叉搜索树来封装map和set等关联式容器是有缺陷的,很可能会退化为单分支的情况,那样效率就极低了,那么有没有方法来弥补二叉搜索树的缺陷呢?

    那么AVL树就出现了,通过AVL树的右单旋、左单旋、左右单旋,右左单旋等操作来防止二叉搜索树退化为单分支的情况出现。

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

    1.AVL树的概念

    一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树是AVL树:

    • 它的左右子树都是AVL树
    • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
       

    我们这里使用的平衡因子是右子树高度 - 左子树高度。 

     

    2.AVL树节点的定义

    代码如下:

    1. template<class K, class V>
    2. struct AVLTreeNode
    3. {
    4. AVLTreeNode* _left; //该节点的左孩子
    5. AVLTreeNode* _right; //该节点的右孩子
    6. AVLTreeNode* _parent; //该节点的双亲
    7. pair _kv; //该节点的key和value
    8. int _bf; //balance factor 平衡因子
    9. AVLTreeNode(const pair& kv) //该节点初始化
    10. :_left(nullptr)
    11. , _right(nullptr)
    12. , _parent(nullptr)
    13. , _kv(kv)
    14. , _bf(0)
    15. {}
    16. };

    3.AVL树的插入

    AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么
    AVL树的插入过程可以分为两步:

    1. 按照二叉搜索树的方式插入新节点
    2. 调整节点的平衡因子
       

    插入思路

     1.按照搜索树规则插入

    2.更新插入节点的祖先节点的平衡因子

            a.插入父亲的右边,父亲的平衡因子--

            b.插入父亲的左边,父亲的平衡因子++

            c.父亲平衡因子 == 0, 父亲所在子树高度不变,不再继续往上更新。插入结束

            d.父亲平衡因子 == 1 or -1, 父亲所在子树高度变了,继续往上更新。

            e.父亲平衡因子 == 2 or -2, 父亲所在子树已经不平衡了,需要旋转处理。

    更新中不可能出现其他值,插入之前树是AVL树,平衡因子要么是1 -1 0, ++ --

    最多就是c/d/e三种情况。

     代码如下

    1. bool Insert(const pair& kv)
    2. {
    3. if (_root == nullptr)
    4. {
    5. _root = new Node(kv);
    6. return true;
    7. }
    8. Node* parent = nullptr;
    9. Node* cur = _root;
    10. while (cur)
    11. {
    12. if (cur->_kv.first < kv.first)
    13. {
    14. parent = cur;
    15. cur = cur->_right;
    16. }
    17. else if (cur->_kv.first > kv.first)
    18. {
    19. parent = cur;
    20. cur = cur->_left;
    21. }
    22. else
    23. {
    24. return false;
    25. }
    26. }
    27. cur = new Node(kv);
    28. if (parent->_kv.first < kv.first)
    29. {
    30. parent->_right = cur;
    31. }
    32. else
    33. {
    34. parent->_left = cur;
    35. }
    36. cur->_parent = parent;
    37. //更新平衡因子
    38. while (parent)
    39. {
    40. if (cur == parent->_left)
    41. {
    42. parent->_bf--;
    43. }
    44. else
    45. {
    46. parent->_bf++;
    47. }
    48. if (parent->_bf == 0)
    49. {
    50. //更新结束
    51. break;
    52. }
    53. else if (parent->_bf == -1 || parent->_bf == 1)
    54. {
    55. //继续往上更新
    56. cur = parent;
    57. parent = parent->_parent;
    58. }
    59. else if (parent->_bf == -2 || parent->_bf == 2)
    60. {
    61. //当前子树出现了问题,需要旋转平衡一下
    62. if (parent->_bf == -2 && cur->_bf == -1)
    63. {
    64. //右单旋
    65. RotateR(parent);
    66. }
    67. else if (parent->_bf == 2 && cur->_bf == 1)
    68. {
    69. //左单旋
    70. RotateL(parent);
    71. }
    72. else if (parent->_bf == -2 && cur->_bf == 1)
    73. {
    74. //左右双旋
    75. RotateLR(parent);
    76. }
    77. else if (parent->_bf == 2 && cur->_bf == -1)
    78. {
    79. //右左双旋
    80. //RotateRL(parent);
    81. }
    82. break;
    83. }
    84. else
    85. {
    86. //理论上不会出现这种情况,但仍要判断,大佬都不能保证自己的代码没有bug
    87. assert(false);
    88. }
    89. }
    90. return true;
    91. }

    4.AVL树的旋转

    4.1 右单旋

    插入新节点要插入较高左子树的左侧(左左)-》右单旋

    图形解析:

     实现代码如下:

    1. void RotateR(Node* parent)
    2. {
    3. Node* subL = parent->_left;
    4. Node* subLR = subL->_right;
    5. parent->_left = subLR;
    6. if (subLR) //subLR有可能为空,不为空时才能调整subLR的父节点
    7. subLR->_parent = parent;
    8. subL->_right = parent;
    9. //parent不一定就是根节点,也可能是子树,所以要设置一个ppNode来标记parent的父节点
    10. Node* ppNode = parent->_parent;
    11. parent->_parent = subL;
    12. if (parent == _root)
    13. {
    14. _root = subL;
    15. _root->_parent = nullptr;
    16. }
    17. else
    18. {
    19. if (ppNode->_left == parent)
    20. {
    21. ppNode->_left = subL;
    22. }
    23. else
    24. {
    25. ppNode->_right = subL;
    26. }
    27. subL->_parent = ppNode;
    28. }
    29. subL->_bf = parent->_bf = 0;
    30. }

    4.2 左单旋

    插入新节点要插入较高右子树的右侧(右右)-》左单旋

    图形解析:

     代码如下:

    1. void RotateL(Node* parent)
    2. {
    3. Node* subR = parent->_right;
    4. Node* subRL = subR->_left;
    5. parent->_right = subRL;
    6. if (subRL) //subRL有可能为空,不为空时才能调整subRL的父节点
    7. subRL->_parent = parent;
    8. subR->_left = parent;
    9. //parent不一定就是根节点,也可能是子树,所以要设置一个ppNode来标记parent的父节点
    10. Node* ppNode = parent->_parent;
    11. parent->_parent = subR;
    12. if (parent == _root)
    13. {
    14. _root = subR;
    15. _root->_parent = nullptr;
    16. }
    17. else
    18. {
    19. if (ppNode->_left == parent)
    20. {
    21. ppNode->_left = subR;
    22. }
    23. else
    24. {
    25. ppNode->_right = subR;
    26. }
    27. subR->_parent = ppNode;
    28. }
    29. subR->_bf = parent->_bf = 0;
    30. }

    4.3 左右双旋

    插入新节点要插入较高左子树的右侧(左右)-》左右双旋

    图形解析:

    对于插入的位置根据h的高度和插入的位置时的平衡因子出现的最终的结果可分为三种情况:

    1.h == 0

            60自己就是新增节点。bf == 0

    此时的平衡因子为,30 60 90节点都为0。

    2.h > 0

            a.新增节点插入的是60的左子树中 bf == -1

    此时的平衡因子为,30节点为0,90为0,60为1

            b.新增节点插入的是60的右子树中bf == 1

    此时的平衡因子为,30节点为-1,90为0,60为0

    代码如下:

     

    1. void RotateLR(Node* parent)
    2. {
    3. Node* subL = parent->_left;
    4. Node* subLR = subL->_right;
    5. int bf = subLR->_bf;
    6. RotateL(parent->_left);
    7. RotateR(parent);
    8. if (bf == 0)
    9. {
    10. subL->_bf = 0;
    11. subLR->_bf = 0;
    12. parent->_bf = 0;
    13. }
    14. else if (bf == -1)
    15. {
    16. subL->_bf = 0;
    17. subLR->_bf = 0;
    18. parent->_bf = 1;
    19. }
    20. else if (bf == 1)
    21. {
    22. subL->_bf = -1;
    23. subLR->_bf = 0;
    24. parent->_bf = 0;
    25. }
    26. else
    27. {
    28. //理论上不会出现的情况
    29. assert(false);
    30. }
    31. }

    4.4 右左双旋

    插入新节点要插入较高右子树的左侧(右左)-》右左双旋

    与左右双旋类似

    代码如下:

    1. void RotateRL(Node* parent)
    2. {
    3. Node* subR = parent->_right;
    4. Node* subRL = subR->_left;
    5. int bf = subRL->_bf;
    6. RotateR(parent->_right);
    7. RotateL(parent);
    8. if (bf == 0)
    9. {
    10. parent->_bf = 0;
    11. subRL->_bf = 0;
    12. subR->_bf = 0;
    13. }
    14. else if (bf == -1)
    15. {
    16. parent->_bf = 0;
    17. subRL->_bf = 0;
    18. subR->_bf = 1;
    19. }
    20. else if (bf == 1)
    21. {
    22. parent->_bf = -1;
    23. subRL->_bf = 0;
    24. subR->_bf = 0;
    25. }
    26. else
    27. {
    28. //理论上不会出现的情况
    29. assert(false);
    30. }
    31. }


    总结
    假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑
    1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR
    当pSubR的平衡因子为1时,执行左单旋
    当pSubR的平衡因子为-1时,执行右左双旋
    2. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL
    当pSubL的平衡因子为-1是,执行右单旋
    当pSubL的平衡因子为1时,执行左右双旋
    旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。

     

    5.AVL树的验证

    5.1 验证其是否为二叉搜索树

    给一个序列进行一次中序遍历,看是否有序,有序即为二叉搜索树。

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

    5.2 验证其是否为平衡树

    1. bool _IsBalance(Node* root)
    2. {
    3. if (root == nullptr)
    4. {
    5. return true;
    6. }
    7. int leftHeight = _Height(root->_left);
    8. int rightHeight = _Height(root->_right);
    9. //不平衡
    10. if (abs(leftHeight - rightHeight) >= 2)
    11. {
    12. cout << root->_kv.first << endl;
    13. return false;
    14. }
    15. //顺便检查一下平衡因子是否正确
    16. if (rightHeight - leftHeight != root->_bf)
    17. {
    18. cout << root->_kv.first << endl;
    19. return false;
    20. }
    21. return _IsBalance(root->_left)
    22. && _IsBalance(root->right);
    23. }

    6.AVL树的删除(了解)

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

    7.AVL树的性能

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

     8.AVL树的整体模拟代码实现

    1. #pragma once
    2. #include
    3. using namespace std;
    4. #include
    5. template<class K, class V>
    6. struct AVLTreeNode
    7. {
    8. AVLTreeNode* _left; //该节点的左孩子
    9. AVLTreeNode* _right; //该节点的右孩子
    10. AVLTreeNode* _parent; //该节点的双亲
    11. pair _kv; //该节点的key和value
    12. int _bf; //balance factor 平衡因子
    13. AVLTreeNode(const pair& kv) //该节点初始化
    14. :_left(nullptr)
    15. , _right(nullptr)
    16. , _parent(nullptr)
    17. , _kv(kv)
    18. , _bf(0)
    19. {}
    20. };
    21. template<class K, class V>
    22. class AVLTree
    23. {
    24. typedef AVLTreeNode Node;
    25. public:
    26. bool Insert(const pair& kv)
    27. {
    28. if (_root == nullptr)
    29. {
    30. _root = new Node(kv);
    31. return true;
    32. }
    33. Node* parent = nullptr;
    34. Node* cur = _root;
    35. while (cur)
    36. {
    37. if (cur->_kv.first < kv.first)
    38. {
    39. parent = cur;
    40. cur = cur->_right;
    41. }
    42. else if (cur->_kv.first > kv.first)
    43. {
    44. parent = cur;
    45. cur = cur->_left;
    46. }
    47. else
    48. {
    49. return false;
    50. }
    51. }
    52. cur = new Node(kv);
    53. if (parent->_kv.first < kv.first)
    54. {
    55. parent->_right = cur;
    56. }
    57. else
    58. {
    59. parent->_left = cur;
    60. }
    61. cur->_parent = parent;
    62. //更新平衡因子
    63. while (parent)
    64. {
    65. if (cur == parent->_left)
    66. {
    67. parent->_bf--;
    68. }
    69. else
    70. {
    71. parent->_bf++;
    72. }
    73. if (parent->_bf == 0)
    74. {
    75. //更新结束
    76. break;
    77. }
    78. else if (parent->_bf == -1 || parent->_bf == 1)
    79. {
    80. //继续往上更新
    81. cur = parent;
    82. parent = parent->_parent;
    83. }
    84. else if (parent->_bf == -2 || parent->_bf == 2)
    85. {
    86. //当前子树出现了问题,需要旋转平衡一下
    87. if (parent->_bf == -2 && cur->_bf == -1)
    88. {
    89. //右单旋
    90. RotateR(parent);
    91. }
    92. else if (parent->_bf == 2 && cur->_bf == 1)
    93. {
    94. //左单旋
    95. RotateL(parent);
    96. }
    97. else if (parent->_bf == -2 && cur->_bf == 1)
    98. {
    99. //左右双旋
    100. RotateLR(parent);
    101. }
    102. else if (parent->_bf == 2 && cur->_bf == -1)
    103. {
    104. //右左双旋
    105. RotateRL(parent);
    106. }
    107. break;
    108. }
    109. else
    110. {
    111. //理论上不会出现这种情况,但仍要判断,大佬都不能保证自己的代码没有bug
    112. assert(false);
    113. }
    114. }
    115. return true;
    116. }
    117. void RotateR(Node* parent)
    118. {
    119. Node* subL = parent->_left;
    120. Node* subLR = subL->_right;
    121. parent->_left = subLR;
    122. if (subLR) //subLR有可能为空,不为空时才能调整subLR的父节点
    123. subLR->_parent = parent;
    124. subL->_right = parent;
    125. //parent不一定就是根节点,也可能是子树,所以要设置一个ppNode来标记parent的父节点
    126. Node* ppNode = parent->_parent;
    127. parent->_parent = subL;
    128. if (parent == _root)
    129. {
    130. _root = subL;
    131. _root->_parent = nullptr;
    132. }
    133. else
    134. {
    135. if (ppNode->_left == parent)
    136. {
    137. ppNode->_left = subL;
    138. }
    139. else
    140. {
    141. ppNode->_right = subL;
    142. }
    143. subL->_parent = ppNode;
    144. }
    145. subL->_bf = parent->_bf = 0;
    146. }
    147. void RotateL(Node* parent)
    148. {
    149. Node* subR = parent->_right;
    150. Node* subRL = subR->_left;
    151. parent->_right = subRL;
    152. if (subRL) //subRL有可能为空,不为空时才能调整subRL的父节点
    153. subRL->_parent = parent;
    154. subR->_left = parent;
    155. //parent不一定就是根节点,也可能是子树,所以要设置一个ppNode来标记parent的父节点
    156. Node* ppNode = parent->_parent;
    157. parent->_parent = subR;
    158. if (parent == _root)
    159. {
    160. _root = subR;
    161. _root->_parent = nullptr;
    162. }
    163. else
    164. {
    165. if (ppNode->_left == parent)
    166. {
    167. ppNode->_left = subR;
    168. }
    169. else
    170. {
    171. ppNode->_right = subR;
    172. }
    173. subR->_parent = ppNode;
    174. }
    175. subR->_bf = parent->_bf = 0;
    176. }
    177. void RotateLR(Node* parent)
    178. {
    179. Node* subL = parent->_left;
    180. Node* subLR = subL->_right;
    181. int bf = subLR->_bf;
    182. RotateL(parent->_left);
    183. RotateR(parent);
    184. if (bf == 0)
    185. {
    186. subL->_bf = 0;
    187. subLR->_bf = 0;
    188. parent->_bf = 0;
    189. }
    190. else if (bf == -1)
    191. {
    192. subL->_bf = 0;
    193. subLR->_bf = 0;
    194. parent->_bf = 1;
    195. }
    196. else if (bf == 1)
    197. {
    198. subL->_bf = -1;
    199. subLR->_bf = 0;
    200. parent->_bf = 0;
    201. }
    202. else
    203. {
    204. //理论上不会出现的情况
    205. assert(false);
    206. }
    207. }
    208. void RotateRL(Node* parent)
    209. {
    210. Node* subR = parent->_right;
    211. Node* subRL = subR->_left;
    212. int bf = subRL->_bf;
    213. RotateR(parent->_right);
    214. RotateL(parent);
    215. if (bf == 0)
    216. {
    217. parent->_bf = 0;
    218. subRL->_bf = 0;
    219. subR->_bf = 0;
    220. }
    221. else if (bf == -1)
    222. {
    223. parent->_bf = 0;
    224. subRL->_bf = 0;
    225. subR->_bf = 1;
    226. }
    227. else if (bf == 1)
    228. {
    229. parent->_bf = -1;
    230. subRL->_bf = 0;
    231. subR->_bf = 0;
    232. }
    233. else
    234. {
    235. //理论上不会出现的情况
    236. assert(false);
    237. }
    238. }
    239. Node* Find(const K& key)
    240. {
    241. Node* cur = _root;
    242. while (cur)
    243. {
    244. if (cur->_kv.first < key)
    245. {
    246. cur = cur->_right;
    247. }
    248. else if (cur->_kv.first > key)
    249. {
    250. cur = cur->_left;
    251. }
    252. else
    253. {
    254. return cur;
    255. }
    256. }
    257. return nullptr;
    258. }
    259. void InOrder()
    260. {
    261. _InOrder(_root);
    262. cout << endl;
    263. }
    264. bool IsBalance()
    265. {
    266. return _IsBalance(_root);
    267. }
    268. int Height()
    269. {
    270. return _Height(_root);
    271. }
    272. int Size()
    273. {
    274. return _Size(_root);
    275. }
    276. private:
    277. int _Size(Node* root)
    278. {
    279. return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
    280. }
    281. int _Height(Node* root)
    282. {
    283. if (root == nullptr)
    284. {
    285. return 0;
    286. }
    287. return max(_Height(root->_left), _Height(root->_right)) + 1;
    288. }
    289. bool _IsBalance(Node* root)
    290. {
    291. if (root == nullptr)
    292. {
    293. return true;
    294. }
    295. int leftHeight = _Height(root->_left);
    296. int rightHeight = _Height(root->_right);
    297. //不平衡
    298. if (abs(leftHeight - rightHeight) >= 2)
    299. {
    300. cout << root->_kv.first << endl;
    301. return false;
    302. }
    303. //顺便检查一下平衡因子是否正确
    304. if (rightHeight - leftHeight != root->_bf)
    305. {
    306. cout << root->_kv.first << endl;
    307. return false;
    308. }
    309. return _IsBalance(root->_left)
    310. && _IsBalance(root->_right);
    311. }
    312. void _InOrder(Node* root)
    313. {
    314. if (root == nullptr)
    315. {
    316. return;
    317. }
    318. _InOrder(root->_left);
    319. cout << root->_kv.first << ":" << root->_kv.second << endl;
    320. _InOrder(root->_right);
    321. }
    322. Node* _root = nullptr;
    323. };
    324. void TestAVLTree1()
    325. {
    326. int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
    327. AVLTree<int, int> t1;
    328. for (auto e : a)
    329. {
    330. t1.Insert({ e,e });
    331. }
    332. t1.InOrder();
    333. }
    334. void TestAVLTree2()
    335. {
    336. int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
    337. AVLTree<int, int> t1;
    338. for (auto e : a)
    339. {
    340. t1.Insert({ e,e });
    341. }
    342. t1.InOrder();
    343. cout << t1.IsBalance() << endl;
    344. }

  • 相关阅读:
    CodeForces-1324F Maximum White Subtree(换根dp 联通子图信息查询)
    Hudi从内核到实战介绍
    【JavaWeb】手写一个Servlet+JSP+JavaBean分页
    docker --link容器互联
    6.5 - 万维网
    ts学习笔记
    python末尾逗号导致返回结果是一个元组
    一图看懂,阿里云飞天企业版如何支持政企数智创新
    使用mindspore提供的docker运行官方示例resnet_thor,训练速度非常慢
    2022年数维杯国际大学生数学建模挑战赛D题三重拉尼娜事件下极端气候灾害损失评估与应对策略研究解题过程
  • 原文地址:https://blog.csdn.net/2301_78611726/article/details/139009541