当前位置:   article > 正文

【数据结构】AVL树——平衡二叉搜索树_c++ 平衡树详解

c++ 平衡树详解

个人主页:东洛的克莱斯韦克-CSDN博客

祝福语:愿你拥抱自由的风

目录

二叉搜索树

AVL树概述

平衡因子

旋转情况分类

左单旋

右单旋

左右双旋

右左双旋

AVL树节点设计

AVL树设计

详解单旋

左单旋

右单旋

详解双旋

左右双旋

平衡因子情况如下

右左双旋

平衡因子情况如下


二叉搜索树

【C++】详解二叉搜索树-CSDN博客

AVL树概述

平衡树:左子树的高度等于右子树的高度

不平衡树:左子树的高度不等于等于右子树的高度

二叉搜索树很难是一颗平衡树。

对二叉树进行插入和删除的操作,或插入大量的数据不够随机,都会是使二叉搜索树不够平衡。

极端情况下,二叉树会退化成类似链表的结构,那么二叉搜索树查询数据的效率荡然无存。

在二叉树的基础上加入平衡的概念就是平衡二叉搜索树,也叫AVL树

AVL树也不是一颗绝对的平衡树,AVL树的平衡是相对的,它允许左子树和右子树的高度为 1 ,但不能超过 1

平衡是相对的很好理解,因为一个父亲节点最多只能有两个孩子节点,而数据又是一个一个插入的,所以一定会出现左子树和右子树高度差为 1 的情况。

B树可达到绝对平衡,因为B树是多叉结构——一个父亲节点有多个孩子节点

如果左子树和右子树的高度差为 2 就视为打破平衡

如果打破平衡,就需要通过旋转这一操作让左右子树的高度差小于等于 1 。

AVL树是保持一种相对平衡的状态,而不是绝对平衡。那么AVL树搜索数据的效率只能是接近O(logN)

AVL树只是保证了搜索效率的下限,而不是提高了上限

平衡因子

平衡因子这一概念并不是AVL树所必备的——从代码实现的角度来说,如果不加入平衡因子的概念理解起来会比较抽象。

平衡因子:让每个节点存一个整型,该整形值的大小等于右子树的高度减左子树的高度

平衡因子等于 0 左右子树平衡

平衡因子等于 1左右子树相对平衡,右树偏高

平衡因子等于 -1 :左右子树相对平衡,左树树偏高

平衡因子等于 2 -2左右子树不平衡

平衡因子的更新:

插入父亲节点的右边平衡因子加加,插入父亲节点的右边平衡因子减减

父亲节点更新后的平衡因子等于 1 或 -1 ,需要不断往上(溯源)更新,直到父亲节点的平衡因子为 0 或 更新至整棵树的根节点就停止更新

如果父亲节点的平衡因子为 2 或 -2 时,需要对这棵子树旋转,旋转后更新平衡因子

示例

旋转情况分类

旋转分为:

左单旋 右单旋  左右双旋  右左双旋

左单旋

:新节点插入较高右子树的右侧

具象图:

抽象图:

那么左单旋是怎么旋的呢?核心步骤为:

设父亲节点为:fathernode 孩子节点为:cur

cur的左孩子成为fathernode的右孩子,

再让fathernode成为cur的左孩子。

如下示意图

右单旋

:新节点插入较高左子树的左侧

具象图:

抽象图:

那么右单旋是怎么旋的呢?核心步骤为:

设父亲节点为:fathernode 孩子节点为:cur

cur的右孩子成为fathernode的左孩子,

再让fathernode成为cur的右孩子

如下示意图:

左右双旋

:新节点插入在较高左子树的右侧——先左单旋再右单旋

左右双旋的核心步骤为:

设父亲节点为:fathernode 

父亲的左孩子节点为:fathernodeL

父亲的左孩子节点的右孩子节点的为fathernodeLR

先让fathernodeL左单旋,再让fathernodeLR进行右单旋

这里小编直接上抽象图:

右左双旋

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

设父亲节点为:fathernode 

父亲的 右孩子节点为:fathernodeR

父亲的右孩子节点的左孩子节点的为fathernodeRL

先对fathernodeR进行右单旋,再对fathernode进行左单旋。

示意图:

AVL树节点设计

【C++】详解C++的模板-CSDN博客

AVL树的节点需要三个指针,分别指向左孩子节点,右孩子节点,父亲节点。指向父亲节点的指针是为了能溯源更新平衡因子。

需要一个整型存储平衡因子,平衡因子在构造函数的初始化列表中初始化为 0,因为新节点左右孩子都为空。

  1. template <class K>
  2. class AVLTreeNode
  3. {
  4. public:
  5. AVLTreeNode(const K& key) //构造函数
  6. :_key(key)
  7. , _left(nullptr)
  8. , _right(nullptr)
  9. , _FatherNode(nullptr)
  10. , _bf(0)
  11. {
  12. }
  13. K _key; //键值
  14. AVLTreeNode<K>* _left;//左孩子
  15. AVLTreeNode<K>* _right;//右孩子
  16. AVLTreeNode<K>* _FatherNode;//父亲
  17. int _bf;//平衡因子
  18. };

AVL树设计

  1. template <class K>
  2. class AVLTree
  3. {
  4. typedef AVLTreeNode<K> node;
  5. node* _root;
  6. public:
  7. AVLTree() //构造函数,初始化为空树
  8. :_root(nullptr)
  9. {
  10. }
  11. bool Insert(const K& key)//插入元素
  12. {
  13. //
  14. if (nullptr == _root) //是否是空树
  15. {
  16. _root = new node(key);
  17. return true;
  18. }
  19. //
  20. node* cur = _root;
  21. node* fathernode = nullptr;
  22. while (cur) //查找插入的位置,如果树中已经有要插入的值,则插入失败,
  23. {
  24. if (cur->_key < key)
  25. {
  26. fathernode = cur;
  27. cur = cur->_right;
  28. }
  29. else if (cur->_key > key)
  30. {
  31. fathernode = cur;
  32. cur = cur->_left;
  33. }
  34. else
  35. {
  36. return false;
  37. }
  38. }
  39. cur = new node(key); //新插入节点
  40. if (fathernode->_key < cur->_key) //判断新节点应该是左孩子还是右孩子
  41. {
  42. fathernode->_right = cur;
  43. cur->_FatherNode = fathernode;
  44. }
  45. else
  46. {
  47. fathernode->_left = cur;
  48. cur->_FatherNode = fathernode;
  49. }
  50. //
  51. while (fathernode)//更新平衡因子
  52. {
  53. if (cur == fathernode->_left)
  54. {
  55. fathernode->_bf--;
  56. }
  57. else if (cur == fathernode->_right)
  58. {
  59. fathernode->_bf++;
  60. }
  61. //
  62. if (fathernode->_bf == 0)
  63. {
  64. // 更新结束
  65. break;
  66. }
  67. else if (fathernode->_bf == 1 || fathernode->_bf == -1)
  68. {
  69. // 继续往上更新
  70. cur = fathernode;
  71. fathernode = fathernode->_FatherNode;
  72. }
  73. else if (fathernode->_bf == 2 || fathernode->_bf == -2)
  74. {
  75. // 子树不平衡了,需要旋转
  76. if (fathernode->_bf == 2 && cur->_bf == 1)
  77. {
  78. RevolveLeft(fathernode);//左单旋
  79. }
  80. else if (fathernode->_bf == -2 && cur->_bf == -1)
  81. {
  82. RevolveRight(fathernode);//右单旋
  83. }
  84. else if (fathernode->_bf == 2 && cur->_bf == -1)
  85. {
  86. RevolveRightLeft(fathernode); //右左双旋
  87. }
  88. else if (fathernode->_bf == -2 && cur->_bf == 1)
  89. {
  90. RevolveLeftRight(fathernode);//左右双旋
  91. }
  92. else
  93. {
  94. assert(false); //平衡因子出问题了
  95. }
  96. break;
  97. }
  98. }
  99. return true;
  100. }
  101. }

下面通过代码的细节来深入理解旋转

详解单旋

左单旋

完整代码如下

  1. void RevolveLeft(node *& fathernode)//左单旋
  2. {
  3. node* cur = fathernode->_right; //父亲节点的右孩子
  4. fathernode->_right = cur->_left; //更改指向关系
  5. if (cur->_left != nullptr) //特殊情况
  6. cur->_left->_FatherNode = fathernode;//更改指向关系
  7. cur->_FatherNode = fathernode->_FatherNode;//更改指向关系
  8. if (fathernode->_FatherNode != nullptr) //为空是特殊情况,
  9. {
  10. if (fathernode->_FatherNode->_right == fathernode)
  11. {
  12. fathernode->_FatherNode->_right = cur;//更改指向关系
  13. }
  14. else
  15. {
  16. fathernode->_FatherNode->_left = cur;//更改指向关系
  17. }
  18. }
  19. cur->_left = fathernode;//更改指向关系
  20. fathernode->_FatherNode = cur;//更改指向关系
  21. fathernode->_bf = 0; //更新平衡因子
  22. cur->_bf = 0;
  23. }

处理指向关系时,一定不要忘了更改父亲的指向关系

经过左单旋之后,父亲节点和右孩子节点的平衡因子都为 0 ,可参考上文图示。

特殊情况如下,如果不处理特殊情况,程序很容易崩溃

右单旋

  1. void RevolveRight(node *& fathernode) //右单旋
  2. {
  3. node* cur = fathernode->_left; //父亲节点的左节点
  4. fathernode->_left = cur->_right;//更新指向关系
  5. if (cur->_right != nullptr) //特殊情况
  6. cur->_right->_FatherNode = fathernode;//更新指向关系
  7. cur->_FatherNode = fathernode->_FatherNode;//更新指向关系
  8. if (fathernode->_FatherNode != nullptr)//特殊情况
  9. {
  10. if (fathernode->_FatherNode->_right == fathernode)
  11. {
  12. fathernode->_FatherNode->_right = cur;//更新指向关系
  13. }
  14. else
  15. {
  16. fathernode->_FatherNode->_left = cur;//更新指向关系
  17. }
  18. }
  19. cur->_right = fathernode;//更新指向关系
  20. fathernode->_FatherNode = cur;//更新指向关系
  21. fathernode->_bf = 0;//更新平衡因子
  22. cur->_bf = 0;
  23. }

详解双旋

左右双旋

左右双旋只需复用左单旋和右单旋即可,但平衡因子的更新却比较麻烦

完整代码如下

  1. void RevolveLeftRight(node *& fathernode)//左右双旋
  2. {
  3. node* fathernodeL = fathernode->_left; //父亲节点的左孩子节点
  4. node* fathernodeLR = fathernodeL->_right;//父亲节点的左孩子节点的右孩子节点
  5. int bf = fathernodeLR->_bf; //保存平衡因子,实际是为了判断是插入了fathernodeLR左边还是右边还是fathernodeLR本身插入
  6. RevolveLeft(fathernodeL);
  7. RevolveRight(fathernode);
  8. //更新平衡因子
  9. if (bf == 0)
  10. {
  11. fathernode->_bf = 0;
  12. fathernodeL->_bf = 0;
  13. fathernodeLR->_bf = 0;
  14. }
  15. else if (bf == -1)
  16. {
  17. fathernode->_bf = 1;
  18. fathernodeL->_bf = 0;
  19. fathernodeLR->_bf = 0;
  20. }
  21. else if (bf == 1)
  22. {
  23. fathernodeL->_bf = -1;
  24. fathernode = 0;
  25. fathernodeLR = 0;
  26. }
  27. else
  28. {
  29. assert(false);
  30. }
  31. }

平衡因子情况如下

右左双旋

完整代码如下

  1. void RevolveRightLeft(node *& fathernode) //右左双旋
  2. {
  3. node* fathernodeR = fathernode->_right;
  4. node* fathernodeRL = fathernodeR->_left;
  5. int bf = fathernodeRL->_bf;
  6. RevolveRight(fathernodeR);
  7. RevolveLeft(fathernode);
  8. if (bf == 0)
  9. {
  10. fathernode->_bf = 0;
  11. fathernodeR->_bf = 0;
  12. fathernodeRL->_bf = 0;
  13. }
  14. else if (bf == 1)
  15. {
  16. fathernode->_bf = -1;
  17. fathernodeR->_bf = 0;
  18. fathernodeRL->_bf = 0;
  19. }
  20. else if (bf == -1)
  21. {
  22. fathernodeR->_bf = 1;
  23. fathernode->_bf = 0;
  24. fathernodeRL->_bf = 0;
  25. }
  26. else
  27. {
  28. assert(false);
  29. }
  30. }

平衡因子情况如下

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小舞很执着/article/detail/914498
推荐阅读
相关标签
  

闽ICP备14008679号