• AVL树C++实现——高度平衡二叉搜索树


    目录

    一、AVL树的概念

    二、AVL 树节点的定义

    三、AVL 树的插入

    3.1 搜索树的插入

    3.2 更新平衡因子的规则

    3.4 左单旋调整

    3.3 右单旋调整

    3.5 先左单旋再右单旋

    1. 右左插入

    2. 右右插入

    3. 右侧新增

    3.5 先右单旋再左单旋

    四、AVL 树测试

    五、代码与测试用例


    一、AVL树的概念

    二叉搜索树可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于再顺序表中搜索元素,效率低下。

    因此,两位俄罗斯数学家便发明了一种解决上述问题的方法:

    当向二叉搜索树中插入新节点后,如果能保证每个结点左右子树高度之差的绝对值不超过1(需要对树的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

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

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

    如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有 n 个结点,其高度可保持在 logN,搜索的时间复杂度为 logN。

    二、AVL 树节点的定义

    二叉搜索树结点的定义与 AVL 树结点的定义在于:

    1. 三叉链模式,增加了父指针。
    2. 增加了平衡因子,方便我们调整平衡(可有可无,只是为了方便我们实现)

    其数据的存储方式我们采用 K-value 模型。

    三、AVL 树的插入

    3.1 搜索树的插入

    AVL 本质就是二叉搜索树,只不过 AVL 树是在二叉搜索树的基础上引入了平衡因子。即,在 AVL 树中的插入过程可以分为两步:

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

    我们先来实现第一步,按二叉搜索树的方式插入新节点。

     这里就不过多介绍了,上一篇讲解过了二叉搜索树的插入。二叉搜索树的插入

    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. //找插入的位置
    11. while (cur)
    12. {
    13. if (cur->_kv.first < kv.first)
    14. {
    15. parent = cur;
    16. cur = cur->_right;
    17. }
    18. else if (cur->_kv.first > kv.first)
    19. {
    20. parent = cur;
    21. cur = cur->_left;
    22. }
    23. else
    24. {
    25. return false;
    26. }
    27. }
    28. //开始插入
    29. cur = new Node(kv);
    30. if (parent->_kv.first < kv.first)
    31. {
    32. parent->_right = cur;
    33. }
    34. else
    35. {
    36. parent->_left = cur;
    37. }
    38. //因为增加了父域,所以还要处理cur的父域
    39. cur->_parent = parent;
    40. }

    3.2 更新平衡因子的规则

    以上我们完成了节点的插入,但是插入了节点后,树可能会出现以下情况:

     所以,为了防止以上情况出现,我们在插入后要调整平衡。

    为了维持平衡,我们引入了平衡因子来控制树的平衡,所以我们每次插入后要更新平衡因子,如果出现失去平衡的情况,我们则要进行旋转来维持平衡。

    更新平衡因子的规则:

    1. 新增在右边,则会让父平衡因子++,新增在左边,父平衡因子 -- ;
    2. 更新后,如果 parent->bf == 0,说明 parent 插入前的平衡因子是 1 or -1,插入填上了矮的一边,parent 的子树高度不变,不需要继续往上更新
    3. 更新后,如果 parent->bf 为 1 或 -1, 说明parent插入前的平衡因子是0,说明左右子树高度相等,插入后有一边高,parnet 高度变了,需要继续往上更新
    4. 更新后,如果 parent->bf == 2 或 -2 ,说明 parent 插入前的平衡因子是 1 or -1,已经到达平衡临界值,parent 子树进行旋转处理将树保持平衡
    5. 更新后,如果 parent->bf >2 或 <-2 ,则说明插入前树已经失去的平衡,要进行代码的检查。

    现在,我们要对祖先路径更新平衡因子,直到更新到根节点结束。

    注释我写的非常清楚,结合上图和代码一起理解。

     当规则4出现时,我们则要进行旋转来调整平衡,旋转共分为 4 种情况,左左右单旋、右右左单旋、左右左右双旋、右左右左双旋我们一一进行解决。

    3.4 左单旋调整

    新节点插入较高右子树的右侧 --- 右右:左单旋
    观察下图,如果我们在 c 节点下插入新节点,导致 60的左右子树失去平衡,调整前后的情况如下:

     现在我们要发现左单旋的执行规律:

    1.树的右子树失去平衡(parent->bf == 2),并且新插入的节点位于右子树的右侧(cur->bf == 1)。

    2. 将 cur->left 交由 parent->right 抚养,再调整父子关系,使 cur->left 指向 parent ,即完成旋转。

    3.平衡因子归零。观察上图,旋转后,60 的左右子树高度相同(a、b高度为h,c的高度为 h+1,注意:60左子树的高度也为h+1,因为存在 30 节点)

    接下来我们编写代码

    3.3 右单旋调整

    新节点插入较高左子树的左侧-左左:右单旋。
    观察下图,如果我们在 a 节点处插入新节点,导致 60的左右子树失去平衡,调整前后的情况如下:

     现在我们要发现右单旋的执行规律:

    1.树的左子树失去平衡(parent->bf == -2),并且新插入的节点位于左子树的左侧(cur->bf == -1)。

    2. 将 cur->right 交由 parent->left 抚养,再调整父子关系,使 cur->right 指向 parent ,即完成旋转。

    右单旋就是方向相反的左单旋,理解了左单旋,右单旋实现起来非常简单, 代码如下。
    1. //情况2:左左右单旋
    2. void RotateR(Node * parent)
    3. {
    4. Node* subL = parent->_left;
    5. Node* subLR = subL->_right;
    6. parent->_left = subLR;
    7. if (subLR)
    8. subLR->_parent = parent;
    9. Node* ppNode = parent->_parent;
    10. subL->_right = parent;
    11. parent->_parent = subL;
    12. if (ppNode)
    13. {
    14. if (ppNode->_left == parent)
    15. {
    16. ppNode->_left = subL;
    17. }
    18. else
    19. {
    20. ppNode->_right = subL;
    21. }
    22. subL->_parent = ppNode;
    23. }
    24. else
    25. {
    26. _root = subL;
    27. subL->_parent = nullptr;
    28. }
    29. subL->_bf = parent->_bf = 0;
    30. }

    3.5 先左单旋再右单旋

    上面我们解决了4种情况中的两种情况,对于平衡的调整还有左右左右双旋和右左右左双旋这两种情况。

    其调整的条件是根的左子树的右侧插入了新的节点。

    这种插入情况我们还要再细分为三种插入情况,其平衡因子会出现不同的情况:

    1. 在30的右子树 60 左侧进行插入。
    2. 在30的右子树 60 右侧进行插入。
    3. 30的右子树为空,插入的节点为60本身。

    1. 右左插入

    先分析第一种情况,如图:

    此时 60 节点的左侧插入节点导致 90 节点下的左右子树高度失衡,所以我们接下来要对 90 节点下进行旋转处理。

    旋转的步骤分为两步:

     先对30进行左单旋,然后再对90进行右单旋

    1.先对 30 进行左单旋,这样,30节点的左右子树便恢复了平衡。

    2.因为 60 左子树高度高于右子树,以 90节点视角来看,符合右单旋的条件(左高右低),所以90节点进行右单旋

    然后对 90 进行右单旋

    2. 右右插入

     以上情况是在 60 的左边进行插入,同样也有在 60 的右边进行插入,两种插入会导致 90、30的 平衡因子出现不同的情况。注意观察下图平衡因子的变化,与上图是不同的。

     然后对90进行右单旋

    3. 右侧新增

     此时我们解决的都是 30 有左右子树,60有左右子树,还有一种可能,h为0,即60本身为新增。

    然后进行旋转

     好的,三种情况我们都分析完了,发现其实无论哪种情况,其步骤都经历的左单旋和右单旋,只是平衡因子因情况的不同而不同。

    而这三种情况的不同都是由 60 影响的,所以我们根据 60 平衡因子的不同分别进行调整即可。

     

    3.5 先右单旋再左单旋

     同样的,右左右左双旋和左右左右双旋几乎一致,就不做过多讲解了,结合画图,仿照左右双旋,很容易写出代码。

     代码如下:

    1. else if (parent->_bf == 2 && cur->_bf == -1)
    2. {
    3. //右左双旋
    4. Node* subR = parent->_right;
    5. Node* subRL = subR->_left;
    6. int bf = subRL->_bf;
    7. //旋转
    8. RotateR(parent->_right);
    9. RotateL(parent);
    10. subRL->_bf = 0;
    11. if (bf == 1)
    12. {
    13. subR->_bf = 0;
    14. parent->_bf = -1;
    15. }
    16. else if (bf == -1)
    17. {
    18. subR->_bf = 1;
    19. parent->_bf = 0;
    20. }
    21. else if (bf == 0)
    22. {
    23. subR->_bf = 0;
    24. parent->_bf = 0;
    25. }
    26. else
    27. {
    28. assert(false);
    29. }
    30. }

    四、AVL 树测试

    实现了插入我们来测试一下代码。

     好的,程序能正常运行,但程序能正常运行就说明我们的 AVL 就是成功的吗?我们的 AVL 树就是平衡的吗?

    所以现在我们要使用检查树的高度的方式来检查树是否是平衡的。即,如果左右子树的高度差不超过1,则说明该树是平衡的,接下来我们来编写成员函数。

    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. return true;
    5. int leftHeight = _Height(root->_left);
    6. int rightHeight = _Height(root->_right);
    7. int diffHeight = abs(leftHeight - rightHeight);
    8. // |diffHeight|小于2 则当前树平衡。
    9. // 还要去判断左右子树是否平衡
    10. return abs(diffHeight) < 2
    11. && _IsBalance(root->_left)
    12. && _IsBalance(root->_right);
    13. }

    好的,接下来往树中插入一万个随机数据,然后来判断其是否平衡。

    14层的平衡二叉树大约有16万个节点,所以高度计算正确,通过 IsBalance 函数也判定当前树为平衡。

    最后我们来玩一下AVL树,往树中插入一亿个数据,然后找到77777处的数据。结果如下:

     一亿个数据,插入的效率是O(n*logN),想找到其中任意一个值,最多查找高度处。

    五、代码与测试用例

    1. #include
    2. #include
    3. #include
    4. #include
    5. #pragma once
    6. using namespace std;
    7. //AVL树结点定义
    8. template<class K, class V>
    9. struct AVLTreeNode
    10. {
    11. AVLTreeNode * _left;
    12. AVLTreeNode * _right;
    13. AVLTreeNode * _parent;
    14. pair _kv; //key-value模型
    15. int _bf; //banlance factor 平衡因子
    16. AVLTreeNode(const pair& kv)
    17. :_left(nullptr), _right(nullptr), _parent(nullptr)
    18. , _kv(kv)
    19. , _bf(0)
    20. {}
    21. };
    22. template<class K, class V>
    23. struct AVLTree
    24. {
    25. typedef AVLTreeNode Node;
    26. public:
    27. bool Insert(const pair& kv)
    28. {
    29. if (_root == nullptr)
    30. {
    31. _root = new Node(kv);
    32. return true;
    33. }
    34. Node* parent = nullptr;
    35. Node* cur = _root;
    36. while (cur)
    37. {
    38. if (cur->_kv.first < kv.first)
    39. {
    40. parent = cur;
    41. cur = cur->_right;
    42. }
    43. else if (cur->_kv.first > kv.first)
    44. {
    45. parent = cur;
    46. cur = cur->_left;
    47. }
    48. else
    49. {
    50. return false;
    51. }
    52. }
    53. cur = new Node(kv);
    54. if (parent->_kv.first < kv.first)
    55. {
    56. parent->_right = cur;
    57. }
    58. else
    59. {
    60. parent->_left = cur;
    61. }
    62. cur->_parent = parent;
    63. // 控制平衡
    64. // 1、更新平衡因子
    65. while (parent)
    66. {
    67. if (cur == parent->_right)
    68. {
    69. parent->_bf++;
    70. }
    71. else
    72. {
    73. parent->_bf--;
    74. }
    75. if (parent->_bf == 0)
    76. {
    77. break;
    78. }
    79. else if (abs(parent->_bf) == 1)
    80. {
    81. parent = parent->_parent;
    82. cur = cur->_parent;
    83. }
    84. else if (abs(parent->_bf) == 2)
    85. {
    86. // 说明parent所在子树已经不平衡了,需要旋转处理
    87. if (parent->_bf == 2 && cur->_bf == 1)
    88. {
    89. RotateL(parent);
    90. }
    91. else if ((parent->_bf == -2 && cur->_bf == -1))
    92. {
    93. RotateR(parent);
    94. }
    95. else if (parent->_bf == -2 && cur->_bf == 1)
    96. {
    97. RotateLR(parent);
    98. }
    99. else if (parent->_bf == 2 && cur->_bf == -1)
    100. {
    101. RotateRL(parent);
    102. }
    103. else
    104. {
    105. assert(false);
    106. }
    107. break;
    108. }
    109. else
    110. {
    111. assert(false);
    112. }
    113. }
    114. return true;
    115. }
    116. void Inorder()
    117. {
    118. _Inorder(_root);
    119. }
    120. int Height()
    121. {
    122. return _Height(_root);
    123. }
    124. bool IsBalance()
    125. {
    126. return _IsBalance(_root);
    127. }
    128. pair Find(const K& key)
    129. {
    130. Node* cur = _root;
    131. while (cur)
    132. {
    133. if (cur->_kv.first > key)
    134. {
    135. cur = cur->_left;
    136. }
    137. else if (cur->_kv.first < key)
    138. {
    139. cur = cur->_right;
    140. }
    141. else
    142. {
    143. return cur->_kv;
    144. }
    145. }
    146. return pair<int, int>(0, 0);
    147. }
    148. private:
    149. bool _IsBalance(Node* root)
    150. {
    151. if (root == nullptr)
    152. return true;
    153. int leftHeight = _Height(root->_left);
    154. int rightHeight = _Height(root->_right);
    155. int diffHeight = abs(leftHeight - rightHeight);
    156. // |diffHeight|小于2 则当前树平衡。
    157. // 还要去判断左右子树是否平衡
    158. return abs(diffHeight) < 2
    159. && _IsBalance(root->_left)
    160. && _IsBalance(root->_right);
    161. }
    162. int _Height(Node* root)
    163. {
    164. if (root == nullptr)
    165. return 0;
    166. return max(_Height(root->_left), _Height(root->_right)) + 1;
    167. }
    168. void _Inorder(Node* root)
    169. {
    170. if (root)
    171. {
    172. _Inorder(root->_left);
    173. cout << root->_kv.first << ":" << root->_kv.second << endl;
    174. _Inorder(root->_right);
    175. }
    176. }
    177. void RotateL(Node* parent)
    178. {
    179. Node* subR = parent->_right;
    180. Node* subRL = subR->_left;
    181. parent->_right = subRL;
    182. if (subRL)
    183. subRL->_parent = parent;
    184. Node* ppNode = parent->_parent;
    185. subR->_left = parent;
    186. parent->_parent = subR;
    187. if (_root == parent)
    188. {
    189. _root = subR;
    190. subR->_parent = nullptr;
    191. }
    192. else
    193. {
    194. if (ppNode->_left == parent)
    195. {
    196. ppNode->_left = subR;
    197. }
    198. else
    199. {
    200. ppNode->_right = subR;
    201. }
    202. subR->_parent = ppNode;
    203. }
    204. subR->_bf = parent->_bf = 0;
    205. }
    206. void RotateR(Node* parent)
    207. {
    208. Node* subL = parent->_left;
    209. Node* subLR = subL->_right;
    210. parent->_left = subLR;
    211. if (subLR)
    212. {
    213. subLR->_parent = parent;
    214. }
    215. Node* ppNode = parent->_parent;
    216. subL->_right = parent;
    217. parent->_parent = subL;
    218. if (_root == parent)
    219. {
    220. _root = subL;
    221. subL->_parent = nullptr;
    222. }
    223. else
    224. {
    225. if (ppNode->_left == parent)
    226. {
    227. ppNode->_left = subL;
    228. }
    229. else
    230. {
    231. ppNode->_right = subL;
    232. }
    233. subL->_parent = ppNode;
    234. }
    235. subL->_bf = parent->_bf = 0;
    236. }
    237. void RotateLR(Node* parent)
    238. {
    239. Node* subL = parent->_left;
    240. Node* subLR = subL->_right;
    241. int bf = subLR->_bf;
    242. RotateL(parent->_left);
    243. RotateR(parent);
    244. subLR->_bf = 0;
    245. if (bf == 1)
    246. {
    247. parent->_bf = 0;
    248. subL->_bf = -1;
    249. }
    250. else if (bf == -1)
    251. {
    252. // 错的
    253. /*parent->_bf = 0;
    254. subL->_bf = 1;*/
    255. parent->_bf = 1;
    256. subL->_bf = 0;
    257. }
    258. else if (bf == 0)
    259. {
    260. parent->_bf = 0;
    261. subL->_bf = 0;
    262. }
    263. else
    264. {
    265. assert(false);
    266. }
    267. }
    268. void RotateRL(Node* parent)
    269. {
    270. Node* subR = parent->_right;
    271. Node* subRL = subR->_left;
    272. int bf = subRL->_bf;
    273. RotateR(parent->_right);
    274. RotateL(parent);
    275. subRL->_bf = 0;
    276. if (bf == 1)
    277. {
    278. subR->_bf = 0;
    279. parent->_bf = -1;
    280. }
    281. else if (bf == -1)
    282. {
    283. subR->_bf = 1;
    284. parent->_bf = 0;
    285. }
    286. else if (bf == 0)
    287. {
    288. parent->_bf = 0;
    289. subR->_bf = 0;
    290. }
    291. else
    292. {
    293. assert(false);
    294. }
    295. }
    296. private:
    297. Node* _root = nullptr;
    298. };
    299. void TestAVLTree1()
    300. {
    301. int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
    302. AVLTree<int, int> t1;
    303. for (auto e : a)
    304. {
    305. t1.Insert(make_pair(e, e));
    306. }
    307. t1.Inorder();
    308. cout << t1.Height() << endl;
    309. if (t1.IsBalance())
    310. cout << "平衡树" << endl;
    311. else
    312. cout << "非平衡树" << endl;
    313. }
    314. void TestAVLTree2()
    315. {
    316. //随机插入一万个数据
    317. size_t N = 10000;
    318. srand((unsigned int)time(NULL));
    319. AVLTree<int, int> t1;
    320. for (size_t i = 0; i < N; ++i)
    321. {
    322. int x = rand() % 100 + 1;
    323. t1.Insert(make_pair(i, x));
    324. bool IsBalance = t1.IsBalance();
    325. printf("%-4d:%-2d ", i, x);
    326. printf("Balance:%d\n", IsBalance);
    327. if (!IsBalance)
    328. {
    329. cout << i << endl;
    330. assert(false);
    331. }
    332. }
    333. //树的高度
    334. cout << "Tree Height:" << t1.Height() << endl;
    335. //判断平衡
    336. if (t1.IsBalance())
    337. cout << "IS AVL" << endl;
    338. else
    339. cout << "NOT AVL" << endl;
    340. }
    341. void TestAVLTree3()
    342. {
    343. size_t N = 100000000;
    344. srand((unsigned int)time(NULL));
    345. AVLTree<int, int> t1;
    346. for (size_t i = 0; i < N; ++i)
    347. {
    348. int x = rand() % 100 + 1;
    349. t1.Insert(make_pair(i, x));
    350. }
    351. cout << t1.Height() << endl;
    352. pair<int, int> result = t1.Find(777777);
    353. cout << result.first << ":" << result.second << endl;
    354. }
    355. int main()
    356. {
    357. //TestAVLTree1();
    358. //TestAVLTree2();
    359. TestAVLTree3();
    360. return 0;
    361. }

  • 相关阅读:
    python 使用xlsx和pandas处理Excel表格
    京东JAVA面试心得与面试题详解
    华为云CDN,如何推动互联网行业健康发展?
    JAVA反编译工具:JADX-GUI、
    力扣刷题day45|300最长递增子序列、674最长连续递增序列、718最长重复子数组
    Python基础语法(五)—— 文件基本操作(打开、写入、关闭、查找)
    Spring Boot 整合 Shiro,十分钟,让你知道有多简单
    微信小程序父子组件之间传值
    JAVA网络编程
    Android与H5交互 -- 点击H5跳转到 Android原生 页面
  • 原文地址:https://blog.csdn.net/Brant_zero/article/details/127813316