当前位置:   article > 正文

AVL(二叉平衡搜索树)详解_avl树添加节点何时查询平衡性

avl树添加节点何时查询平衡性

目录

1.什么是二叉平衡搜索树?

2.AVL树的结点结构

3.AVL模板类

3.1. 插入操作

(1)插入情况

(2)旋转操作

1.左旋操作

2.右旋操作

3.双旋操作

3.2 检查平衡性


1.什么是二叉平衡搜索树?

首先,我们要了解什么是二叉搜索树,这里大家可以看我写的上一篇博客    二叉搜索树

并且本文也在二叉搜索树的基础上进行讲解。

我们可以知道,AVL树是二叉搜索树的一种,它与二叉搜索树的不同之处就在于平衡二字,这也是为了弥补二叉搜索树的缺陷而做出的改变。

我们知道,二叉搜索树在极端条件下会出现下面的情况:

 在这种情况下,二叉搜索树退化为链表,无论是查找,插入,删除等一系列操作,二叉搜索树都与链表没有任何区别,那我们建立二叉搜索树的优势就不复存在了。

为了弥补这个缺点,有了二叉平衡搜索树,二叉平衡搜索树有如下性质:

1.满足二叉搜索树的性质

2.任何一个节点的左右树高度差不超过一

3.左右子树都满足AVL树的性质

这就保证了二叉搜索树的性质同时不会出现极端情况退化为链表,满足这种条件的树我们称之为二叉平衡搜索树。

2.AVL树的结点结构

首先,我们知道AVL树是二叉搜索树的一种,唯一多了平衡的特性,因此结点结构也是与二叉搜索树类似的,只是多了一个 int 类型的数据 ,平衡因子 _bf  ,平衡因子的值是此结点的右子树高度减去左子树高度后得到的值,具体结构如下:

  1. template<class T>
  2. struct AVLTreeNode
  3. {
  4. AVLTreeNode(const T& data = T())
  5. : _pLeft(nullptr)
  6. , _pRight(nullptr)
  7. , _pParent(nullptr)
  8. , _data(data)
  9. , _bf(0)
  10. {}
  11. AVLTreeNode<T>* _pLeft;
  12. AVLTreeNode<T>* _pRight;
  13. AVLTreeNode<T>* _pParent;
  14. T _data;
  15. int _bf; // 节点的平衡因子
  16. };

使用template类模板我们可以使用多种数据类型的AVL结点类型,如果想要存储key-value结构的数据,我们可以将data的数据类型更改为pair ,这里为了方便大家理解,我采用key的结构,同时我们写了一个构造函数来进行结点的构造。

3.AVL模板类

完成了AVL树的结点结构,我们要进行AVL树的模板类的编写,首先,我们可以搭建框架:

  1. template<class T>
  2. class AVLTree
  3. {
  4. typedef AVLTreeNode<T> Node;
  5. public:
  6. AVLTree()
  7. : _pRoot(nullptr)
  8. {}
  9. private:
  10. Node* _pRoot;
  11. };

这里我们知道AVL树的类模板中只有一个成员变量 _pRoot,用来存储AVL树的的根结点,并且我们简单写一个构造函数,有兴趣的朋友也可以去实现一下拷贝构造,析构......默认成员函数。

接下来我们编写AVL类中主要的成员方法:

3.1. 插入操作

与二叉搜索树不同的是,AVL的树操作更加复杂,首先,我们要找到结点的插入位置,这步操作我们可以与二叉搜索树的插入操作进行复用,其次,我们插入结点后,要让树满足 “平衡” ,这里我们就有可能要对树的结构进行一些改变,我们称之为 旋转操作。

(1)插入情况

首先,我们要知道,在插入之前树是一棵AVL树,总的来说,这棵树只有三种情况:

1.左右均衡

这种情况下,AVL树几完全平衡的,此时无论我们在哪边插入都不会影响AVL的平衡性质

2.左高右低

在AVL树中,左右高度差不超过1,因此即便左高右低,高度也只差了1,情况如下:

这种情况下,我们如果插入结点到右子树,会使AVL树更加平衡,但是如果插入节点到左子树上,就会影响AVL的平衡,并且插入到左子树也有两种可能:

1.插入到左结点的 左子树上

2.插入到左结点的右子树上

这两种操作需要的操作也是不相同的,我们后面再说。

3.左低右高

这种情况下,我们如果插入结点到左子树,会使AVL树更加平衡,但是如果插入节点到右子树上,就会影响AVL的平衡,并且插入到右子树也有两种可能:

1.插入到右结点的左子树上

2.插入到右结点的右子树上

并且我们要知道插入结点后的一些性质:

1.插入结点后,只对其祖先结点产生影响。

2.若某祖先结点的平衡因子变为0,影响不再向上传递。

3.若某祖先的平衡因子为+1/-1,影响继续向上传递。 

(2)旋转操作
1.左旋操作

 当右子树偏高且结点插入到右节点的右子树上时候,我们要采用左旋操作,详细过程如下:

首先,我们要注意,只有当AVL树本来就右边偏高并且插入结点在AVL树的右边子树上才能够使用左旋,把这个条件变化成数据体现,其实也就是当我们找到平衡因子异常的结点parent,如果它的平衡因子为2,且cur平衡因子为1。类似这样的结构,parent,cur和插入结点在一条直线上,如下:

满足了这个条件,我们就可以以cur为轴心进行左旋,并且我们可以注意到,左旋后的parent和cur平衡因子都变为0,则影响不再向上传递。

也就是说,满足左旋场景我们只需要左旋一次就能够完成调整,得到的树就满足AVL树的条件。

代码如下:

  1. // 左单旋
  2. void RotateL(Node* pParent)
  3. {
  4. Node* cur = pParent->_pRight;
  5. Node* curleft = cur->_pLeft;
  6. //左旋第一步,cur的左树变为parent的右子树
  7. pParent->_pRight = curleft;
  8. if (curleft)
  9. {
  10. curleft->_pParent = pParent;
  11. }
  12. //左旋第二步,parent变为cur的左子树
  13. cur->_pLeft = pParent;
  14. //记录根节点的上一个节点
  15. Node* ppnode = pParent->_pParent;
  16. pParent->_pParent = cur;
  17. if (pParent == _pRoot)//如果parent已经是根节点了
  18. {
  19. _pRoot = cur;
  20. cur->_pParent = nullptr;
  21. }
  22. else //如果parent不是根节点,需要把cur与前面节点进行连接
  23. {
  24. if (ppnode->_pLeft == pParent)//如果原来的parent是上一节点的左孩子
  25. {
  26. ppnode->_pLeft = cur;
  27. cur->_pParent = ppnode;
  28. }
  29. else //如果原来的parent是上一节点的右孩子
  30. {
  31. ppnode->_pRight = cur;
  32. cur->_pParent = ppnode;
  33. }
  34. }
  35. //完成平衡因子的修改
  36. cur->_bf = pParent->_bf = 0;
  37. }

2.右旋操作

当左子树偏高且结点插入到左节点的左子树上时候,我们要采用右边旋操作,详细过程如下:

要进行右旋 ,需要满足的条件为:

插入之前,左子树偏高,插入结点在左结点的左子树上,和左旋一样,parent,cur和插入结点在一条直线上,不过类似与下面的结构:

描述为数据表现就是,当我们找到平衡因子异常的结点parent:若parent的bf为2,cur的bf为1,我们就以cur为轴心进行右旋。

并且我们可以注意到,进行旋转后,parent和cur的平衡因子变为0,影响不再向上传递,

也就是说,满足右旋场景我们只需要右旋一次就能够完成调整,得到的树就满足AVL树的条件。

代码如下:

  1. // 右单旋
  2. void RotateR(Node* pParent)
  3. {
  4. Node* cur = pParent->_pLeft;
  5. Node* curright = cur->_pRight;
  6. //右旋第一步,cur的右子树给parent的左子树
  7. pParent->_pLeft = curright;
  8. if (curright)
  9. {
  10. curright->_pParent = pParent;
  11. }
  12. //右旋第二步,parent变为cur的右子树
  13. cur->_pRight = pParent;
  14. Node* ppnode = pParent->_pParent;
  15. pParent->_pParent = cur;
  16. if (pParent == _pRoot)
  17. {
  18. _pRoot = cur;
  19. cur->_pParent = nullptr;
  20. }
  21. else
  22. {
  23. if (ppnode->_pLeft == pParent)
  24. {
  25. ppnode->_pLeft = cur;
  26. cur->_pParent = ppnode;
  27. }
  28. else
  29. {
  30. ppnode->_pRight = cur;
  31. cur->_pParent = ppnode;
  32. }
  33. }
  34. cur->_bf = pParent->_bf = 0;
  35. }

3.双旋操作

1.右左双旋

在讲解插入情况时,我们知道右高左低有两种情况

其中,我们如果插入到右结点的右子树,就满足了左旋的条件,直接进行左旋就可以完成。

但是我们如果插入到右结点的左子树,这个时候 ,parent ,cur和插入结点并不在一条直线上,而是一条折线结构,数据表现为,当我们找到平衡因子异常的结点parent,其平衡因子为2,cur的平衡因子为-1,其右如下:

详细操作如下:

 首先,我们找到平衡因子异常的结点为parent,先对其right结点进行右旋,然后再对parent结点进行左旋,具体操作如下:

  1. // 右左双旋
  2. void RotateRL(Node* pParent)
  3. {
  4. RotateR(pParent->_pRight);
  5. RotateL(pParent);
  6. }

2.左右双旋

与右左双旋相对的是,左右双旋是左高右低中的一种情况

当我们把新结点插入到左结点的左子树时,满足右旋操作的条件,进行右旋操作即可。

但是如果我们插入到 左结点的右子树时,形成另外一种折线结构,数据表现为,当我们找到平衡因子异常的结点parent,其平衡因子为-2,cur的平衡因子为1,如下:

此时,我们需要进行左右双旋,具体操作如下:

代码如下:

  1. // 左右双旋
  2. void RotateLR(Node* pParent)
  3. {
  4. RotateL(pParent->_pLeft);
  5. RotateR(pParent);
  6. }

插入操作全过程:

  1. // 在AVL树中插入值为data的节点
  2. bool Insert(const T& data)
  3. {
  4. //树为空
  5. if (!_pRoot)
  6. {
  7. Node* newnode = new Node(data);
  8. _pRoot = newnode;
  9. return true;
  10. }
  11. Node* parent =nullptr;
  12. Node* cur = _pRoot;
  13. while (cur)
  14. {
  15. if (cur->_data > data)
  16. {
  17. parent = cur;
  18. cur = cur->_pLeft;
  19. }
  20. else if (cur->_data < data)
  21. {
  22. parent = cur;
  23. cur = cur->_pRight;
  24. }
  25. else
  26. {
  27. return false;
  28. }
  29. }
  30. Node* newnode = new Node(data);
  31. if (parent->_data < data)
  32. {
  33. parent->_pRight = newnode;
  34. newnode->_pParent = parent;
  35. }
  36. else
  37. {
  38. parent->_pLeft = newnode;
  39. newnode->_pParent = parent;
  40. }
  41. //向添加节点的祖先进行进行平衡因子的更新,查找是否有_bf>1的节点
  42. cur = newnode;
  43. parent = newnode->_pParent;
  44. while (parent)
  45. {
  46. if (cur == parent->_pLeft)
  47. {
  48. parent->_bf--;
  49. }
  50. else
  51. {
  52. parent->_bf++;
  53. }
  54. //如果出现平衡因子为0,更新结束
  55. if (parent->_bf == 0)
  56. {
  57. break;
  58. }
  59. else if (parent->_bf ==1|| parent->_bf == -1)//如果平衡因子正常,继续向上进行更新
  60. {
  61. cur = parent;
  62. parent = parent->_pParent;
  63. }
  64. else if(parent->_bf == 2 || parent->_bf == -2)//平衡因子不正常,需要进行旋转
  65. {
  66. if (parent->_bf == 2 && cur->_bf == 1)
  67. {
  68. RotateL(parent);
  69. }
  70. else if (parent->_bf == -2 && cur->_bf == -1)
  71. {
  72. RotateR(parent);
  73. }
  74. else if (parent->_bf == 2 && cur->_bf == -1)
  75. {
  76. RotateRL(parent);
  77. }
  78. else if (parent->_bf == -2 && cur->_bf == 1)
  79. {
  80. RotateLR(parent);
  81. }
  82. break;
  83. }
  84. else //平衡因子大于2,出现问题
  85. {
  86. assert(false);
  87. }
  88. }
  89. return true;
  90. }

3.2 检查平衡性

检查AVL树是否平衡,我们需要计算左右子树的高度,使用递归操作可以简单实现,操作如下:

  1. public:
  2. bool IsBalance()
  3. {
  4. return IsBalance(_pRoot);
  5. }
  6. int Height()
  7. {
  8. return Height(_pRoot);
  9. }
  10. //这样做的目的是为了让外部可以直接调用私有成员_pRoot
  11. private:
  12. int Height(Node* root)
  13. {
  14. if (root == nullptr)
  15. return 0;
  16. int leftHeight = Height(root->_pLeft);
  17. int rightHeight = Height(root->_pRight);
  18. return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
  19. }
  20. bool IsBalance(Node* root)
  21. {
  22. if (root == nullptr)
  23. return true;
  24. int leftHight = Height(root->_pLeft);
  25. int rightHight = Height(root->_pRight);
  26. if (rightHight - leftHight != root->_bf)
  27. {
  28. std::cout << "平衡因子异常:" << root->_data << "->" << root->_bf << std::endl;
  29. std::cout << leftHight << " "<<rightHight << " " << root->_bf;
  30. return false;
  31. }
  32. return abs(rightHight - leftHight) < 2
  33. && IsBalance(root->_pLeft)
  34. && IsBalance(root->_pRight);
  35. }

最后,其实AVL树的删除,查找,删除等等操作我没有进行实现,因为其实只要掌握了插入,其他操作简简单单,如果我完成了AVL树的其他功能,我也会添加进来。

下面贴上完整代码供大家参考:

  1. #pragma once
  2. #include<cassert>
  3. #include<iostream>
  4. template<class T>
  5. struct AVLTreeNode
  6. {
  7. AVLTreeNode(const T& data = T())
  8. : _pLeft(nullptr)
  9. , _pRight(nullptr)
  10. , _pParent(nullptr)
  11. , _data(data)
  12. , _bf(0)
  13. {}
  14. AVLTreeNode<T>* _pLeft;
  15. AVLTreeNode<T>* _pRight;
  16. AVLTreeNode<T>* _pParent;
  17. T _data;
  18. int _bf; // 节点的平衡因子
  19. };
  20. // AVL: 二叉搜索树 + 平衡因子的限制
  21. template<class T>
  22. class AVLTree
  23. {
  24. typedef AVLTreeNode<T> Node;
  25. public:
  26. AVLTree()
  27. : _pRoot(nullptr)
  28. {}
  29. // 在AVL树中插入值为data的节点
  30. bool Insert(const T& data)
  31. {
  32. //树为空
  33. if (!_pRoot)
  34. {
  35. Node* newnode = new Node(data);
  36. _pRoot = newnode;
  37. return true;
  38. }
  39. Node* parent =nullptr;
  40. Node* cur = _pRoot;
  41. while (cur)
  42. {
  43. if (cur->_data > data)
  44. {
  45. parent = cur;
  46. cur = cur->_pLeft;
  47. }
  48. else if (cur->_data < data)
  49. {
  50. parent = cur;
  51. cur = cur->_pRight;
  52. }
  53. else
  54. {
  55. return false;
  56. }
  57. }
  58. Node* newnode = new Node(data);
  59. if (parent->_data < data)
  60. {
  61. parent->_pRight = newnode;
  62. newnode->_pParent = parent;
  63. }
  64. else
  65. {
  66. parent->_pLeft = newnode;
  67. newnode->_pParent = parent;
  68. }
  69. //向添加节点的祖先进行进行平衡因子的更新,查找是否有_bf>1的节点
  70. cur = newnode;
  71. parent = newnode->_pParent;
  72. while (parent)
  73. {
  74. if (cur == parent->_pLeft)
  75. {
  76. parent->_bf--;
  77. }
  78. else
  79. {
  80. parent->_bf++;
  81. }
  82. //如果出现平衡因子为0,更新结束
  83. if (parent->_bf == 0)
  84. {
  85. break;
  86. }
  87. else if (parent->_bf ==1|| parent->_bf == -1)//如果平衡因子正常,继续向上进行更新
  88. {
  89. cur = parent;
  90. parent = parent->_pParent;
  91. }
  92. else if(parent->_bf == 2 || parent->_bf == -2)//平衡因子不正常,需要进行旋转
  93. {
  94. if (parent->_bf == 2 && cur->_bf == 1)
  95. {
  96. RotateL(parent);
  97. }
  98. else if (parent->_bf == -2 && cur->_bf == -1)
  99. {
  100. RotateR(parent);
  101. }
  102. else if (parent->_bf == 2 && cur->_bf == -1)
  103. {
  104. RotateRL(parent);
  105. }
  106. else if (parent->_bf == -2 && cur->_bf == 1)
  107. {
  108. RotateLR(parent);
  109. }
  110. break;
  111. }
  112. else //平衡因子大于2,出现问题
  113. {
  114. assert(false);
  115. }
  116. }
  117. return true;
  118. }
  119. // AVL树的验证
  120. bool IsBalance()
  121. {
  122. return IsBalance(_pRoot);
  123. }
  124. int Height()
  125. {
  126. return Height(_pRoot);
  127. }
  128. private:
  129. // 根据AVL树的概念验证pRoot是否为有效的AVL树
  130. int Height(Node* root)
  131. {
  132. if (root == nullptr)
  133. return 0;
  134. int leftHeight = Height(root->_pLeft);
  135. int rightHeight = Height(root->_pRight);
  136. return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
  137. }
  138. bool IsBalance(Node* root)
  139. {
  140. if (root == nullptr)
  141. return true;
  142. int leftHight = Height(root->_pLeft);
  143. int rightHight = Height(root->_pRight);
  144. if (rightHight - leftHight != root->_bf)
  145. {
  146. std::cout << "平衡因子异常:" << root->_data << "->" << root->_bf << std::endl;
  147. std::cout << leftHight << " "<<rightHight << " " << root->_bf;
  148. return false;
  149. }
  150. return abs(rightHight - leftHight) < 2
  151. && IsBalance(root->_pLeft)
  152. && IsBalance(root->_pRight);
  153. }
  154. // 右单旋
  155. void RotateR(Node* pParent)
  156. {
  157. Node* cur = pParent->_pLeft;
  158. Node* curright = cur->_pRight;
  159. //右旋第一步,cur的右子树给parent的左子树
  160. pParent->_pLeft = curright;
  161. if (curright)
  162. {
  163. curright->_pParent = pParent;
  164. }
  165. //右旋第二步,parent变为cur的右子树
  166. cur->_pRight = pParent;
  167. Node* ppnode = pParent->_pParent;
  168. pParent->_pParent = cur;
  169. if (pParent == _pRoot)
  170. {
  171. _pRoot = cur;
  172. cur->_pParent = nullptr;
  173. }
  174. else
  175. {
  176. if (ppnode->_pLeft == pParent)
  177. {
  178. ppnode->_pLeft = cur;
  179. cur->_pParent = ppnode;
  180. }
  181. else
  182. {
  183. ppnode->_pRight = cur;
  184. cur->_pParent = ppnode;
  185. }
  186. }
  187. cur->_bf = pParent->_bf = 0;
  188. }
  189. // 左单旋
  190. void RotateL(Node* pParent)
  191. {
  192. Node* cur = pParent->_pRight;
  193. Node* curleft = cur->_pLeft;
  194. //左旋第一步,cur的左树变为parent的右子树
  195. pParent->_pRight = curleft;
  196. if (curleft)
  197. {
  198. curleft->_pParent = pParent;
  199. }
  200. //左旋第二步,parent变为cur的左子树
  201. cur->_pLeft = pParent;
  202. //记录根节点的上一个节点
  203. Node* ppnode = pParent->_pParent;
  204. pParent->_pParent = cur;
  205. if (pParent == _pRoot)//如果parent已经是根节点了
  206. {
  207. _pRoot = cur;
  208. cur->_pParent = nullptr;
  209. }
  210. else //如果parent不是根节点,需要把cur与前面节点进行连接
  211. {
  212. if (ppnode->_pLeft == pParent)//如果原来的parent是上一节点的左孩子
  213. {
  214. ppnode->_pLeft = cur;
  215. cur->_pParent = ppnode;
  216. }
  217. else //如果原来的parent是上一节点的右孩子
  218. {
  219. ppnode->_pRight = cur;
  220. cur->_pParent = ppnode;
  221. }
  222. }
  223. //完成平衡因子的修改
  224. cur->_bf = pParent->_bf = 0;
  225. }
  226. // 右左双旋
  227. void RotateRL(Node* pParent)
  228. {
  229. RotateR(pParent->_pRight);
  230. RotateL(pParent);
  231. }
  232. // 左右双旋
  233. void RotateLR(Node* pParent)
  234. {
  235. RotateL(pParent->_pLeft);
  236. RotateR(pParent);
  237. }
  238. private:
  239. Node* _pRoot;
  240. };

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

闽ICP备14008679号