平衡树是计算机科学中的一类数据结构。 平衡树是计算机科学中的一类改进的二叉查找树。一般的二叉查找树的查询复杂度是跟目标结点到树根的距离(即深度)有关,因此当结点的深度普遍较大时,查询的均摊复杂度会上升,为了更高效的查询,平衡树应运而生了。

  在这里,平衡指所有叶子的深度趋于平衡,更广义的是指在树上所有可能查找的均摊复杂度偏低。

  几乎所有平衡树的操作都基于树操作,通过旋转操作可以使得树趋于平衡。 对一棵查找树(search tree)进行查询/新增/删除 等动作, 所花的时间与树的高度h 成比例, 并不与树的容量 n 成比例。如果可以让树维持矮矮胖胖的好身材, 也就是让h维持在O(lg n)左右, 完成上述工作就很省时间。能够一直维持好身材, 不因新增删除而长歪的搜寻树, 叫做balanced search tree(平衡树)。 旋转Rotate —— 不破坏左小右大特性的小手术 平衡树有很多种, 其中有几类树维持平衡的方法, 都是靠整形小手术。

  各种平衡树:AVL树,经典平衡树,所有操作的最坏复杂度是O(lgN)的。

        Treap,利用随机堆的期望深度来优化树的深度,达到较优的期望复杂度。

        伸展树、红黑树,节点大小平衡树。2-3树、AA树。

AVL树:

  AVL树是一棵自平衡的二叉搜索树,在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下都是O(lgN)

为什么需要AVL树:

  大多数二叉查找操作(搜索、最大、最小、插入、删除...)会花费O(h),h是二叉搜索树的高度。对于不平衡的二叉查找树,这些操作的时间复杂度为O(n)。如果我们保证在每一次插入和删除之后树的高度为O(lgN),那么我们就能保证对于所有的操作都有O(lgN)的上界。AVL树的高度总是O(logN),n是树中节点的数量。

2. 旋转

如果在AVL树中进行插入或删除节点后,可能导致AVL树失去平衡。这种失去平衡的可以概括为4种姿态:LL(左左),LR(左右),RR(右右)和RL(右左)。下面给出它们的示意图:

上图中的4棵树都是"失去平衡的AVL树",从左往右的情况依次是:LL、LR、RL、RR。除了上面的情况之外,还有其它的失去平衡的AVL树,如下图:

上面的两张图都是为了便于理解,而列举的关于"失去平衡的AVL树"的例子。总的来说,AVL树失去平衡时的情况一定是LL、LR、RL、RR这4种之一,它们都由各自的定义:

(1) LL:LeftLeft,也称为"左左"。插入或删除一个节点后,根节点的左子树的左子树还有非空子节点,导致"根的左子树的高度"比"根的右子树的高度"大2,导致AVL树失去了平衡。
     例如,在上面LL情况中,由于"根节点(8)的左子树(4)的左子树(2)还有非空子节点",而"根节点(8)的右子树(12)没有子节点";导致"根节点(8)的左子树(4)高度"比"根节点(8)的右子树(12)"高2。

(2) LR:LeftRight,也称为"左右"。插入或删除一个节点后,根节点的左子树的右子树还有非空子节点,导致"根的左子树的高度"比"根的右子树的高度"大2,导致AVL树失去了平衡。
     例如,在上面LR情况中,由于"根节点(8)的左子树(4)的左子树(6)还有非空子节点",而"根节点(8)的右子树(12)没有子节点";导致"根节点(8)的左子树(4)高度"比"根节点(8)的右子树(12)"高2。

(3) RL:RightLeft,称为"右左"。插入或删除一个节点后,根节点的右子树的左子树还有非空子节点,导致"根的右子树的高度"比"根的左子树的高度"大2,导致AVL树失去了平衡。
     例如,在上面RL情况中,由于"根节点(8)的右子树(12)的左子树(10)还有非空子节点",而"根节点(8)的左子树(4)没有子节点";导致"根节点(8)的右子树(12)高度"比"根节点(8)的左子树(4)"高2。

(4) RR:RightRight,称为"右右"。插入或删除一个节点后,根节点的右子树的右子树还有非空子节点,导致"根的右子树的高度"比"根的左子树的高度"大2,导致AVL树失去了平衡。
     例如,在上面RR情况中,由于"根节点(8)的右子树(12)的右子树(14)还有非空子节点",而"根节点(8)的左子树(4)没有子节点";导致"根节点(8)的右子树(12)高度"比"根节点(8)的左子树(4)"高2。

前面说过,如果在AVL树中进行插入或删除节点后,可能导致AVL树失去平衡。AVL失去平衡之后,可以通过旋转使其恢复平衡,下面分别介绍"LL(左左),LR(左右),RR(右右)和RL(右左)"这4种情况对应的旋转方法。

2.1 LL的旋转

LL失去平衡的情况,可以通过一次旋转让AVL树恢复平衡。如下图:

图中左边是旋转之前的树,右边是旋转之后的树。从中可以发现,旋转之后的树又变成了AVL树,而且该旋转只需要一次即可完成。
对于LL旋转,你可以这样理解为:LL旋转是围绕"失去平衡的AVL根节点"进行的,也就是节点k2;而且由于是LL情况,即左左情况,就用手抓着"左孩子,即k1"使劲摇。将k1变成根节点,k2变成k1的右子树,"k1的右子树"变成"k2的左子树"。

LL的旋转代码

  1. template<typename T>
  2. void AVLTree<T>::RotateWithLeftChild(AVLTreeNode* &z) {
  3. AVLTreeNode* y = z->left;
  4. z->left = y->right;
  5. y->right = z;
  6. z->height = max(GetHeight(z->left), GetHeight(z->right)) + ;
  7. y->right = max(GetHeight(y->left), z->height)) + ;
  8. z = y;
  9. }

2.2 RR的旋转

理解了LL之后,RR就相当容易理解了。RR是与LL对称的情况!RR恢复平衡的旋转方法如下:

图中左边是旋转之前的树,右边是旋转之后的树。RR旋转也只需要一次即可完成。

RR的旋转代码

  1. template<typename T>
  2. void AVLTree<T>::RotateWithRightChild(AVLTreeNode* &z) {
  3. AVLTreeNode* y = z->right;
  4. z->right = y->left;
  5. y->left = z;
  6. z->height = max(GetHeight(z->left), GetHeight(z->right)) + ;
  7. y->right = max(GetHeight(y->right), z->height)) + ;
  8. z = y;
  9. }

2.3 LR的旋转

LR失去平衡的情况,需要经过两次旋转才能让AVL树恢复平衡。如下图:

第一次旋转是围绕"k1"进行的"RR旋转",第二次是围绕"k3"进行的"LL旋转"。

LR的旋转代码

  1. template<typename T>
  2. void AVLTree<T>::DoubleWithLeftChild(AVLTreeNode* &z) {
  3. RotateWithRightChild(z->left);
  4. RotateWithLeftChild(z);
  5. }

2.4 RL的旋转

RL是与LR的对称情况!RL恢复平衡的旋转方法如下:

第一次旋转是围绕"k3"进行的"LL旋转",第二次是围绕"k1"进行的"RR旋转"。

RL的旋转代码

  1. template<typename T>
  2. void AVLTree<T>::DoubleWithRightChild(AVLTreeNode* &z) {
  3. RotateWithRightChild(z->right);
  4. RotateWithLeftChild(z);
  5. }

插入:

  向AVL树插入,可以透过如同它是未平衡的二叉查找树一样,把给定的值插入树中,接着自底往上向根节点折回,于在插入期间成为不平衡的所有节点上进行旋转来完成。因为折回到根节点的路途上最多有1.44乘log n个节点,而每次AVL旋转都耗费固定的时间,所以插入处理在整体上的耗费为O(log n) 时间。

 删除:

  从AVL树中删除,可以通过把要删除的节点向下旋转成一个叶子节点,接着直接移除这个叶子节点来完成。因为在旋转成叶子节点期间最多有log n个节点被旋转,而每次AVL旋转耗费固定的时间,所以删除处理在整体上耗费O(log n) 时间。

 查找

  可以像普通二叉查找树一样的进行,所以耗费O(log n)时间,因为AVL树总是保持平衡的。不需要特殊的准备,树的结构不会由于查找而改变。

代码:

  头文件:

  1. #ifndef AVL_TREE_H_
  2. #define AVL_TREE_H_
  3.  
  4. template<typename T>
  5. class AVLTree {
  6. public:
  7. AVLTree():root_(NULL){}
  8. AVLTree(const AVLTree &rhs){}
  9. AVLTree& operator=(const AVLTree &rhs){}
  10. ~AVLTree(){}
  11.  
  12. void Insert(const T& k) {
  13. Insert(root_, k);
  14. }
  15.  
  16. void Remove(const T& k) {
  17. Remove(root_, k);
  18. }
  19.  
  20. private:
  21. struct AVLTreeNode {
  22. T key;
  23. int height;
  24. AVLTreeNode* left;
  25. AVLTreeNode* right;
  26.  
  27. AVLTreeNode(const T& k, AVLTreeNode* l = NULL, AVLTreeNode * r = NULL, int h = )
  28. : key(k), left(l), right(r), height(h) {}
  29. };
  30.  
  31. AVLTreeNode *root_; //根节点
  32.  
  33. int GetHeight(AVLTreeNode* p) const {
  34. return p == NULL ? - : p->height;
  35. }
  36.  
  37. void Insert(AVLTreeNode* &p, const T& k);
  38. void Remove(AVLTreeNode* &p, const T& k);
  39.  
  40. void RotateWithLeftChild(AVLTreeNode* &z);
  41. void RotateWithRightChild(AVLTreeNode* &z);
  42.  
  43. void DoubleWithLeftChild(AVLTreeNode* &z);
  44. void DoubleWithRightChild(AVLTreeNode* &z);
  45.  
  46. AVLTreeNode* FindMin(AVLTreeNode* p) const;
  47. };
  48. #endif

源文件:

  

  1. #include "avl_tree.h"
  2.  
  3. //LL
  4. template<typename T>
  5. void AVLTree<T>::RotateWithLeftChild(AVLTreeNode* &z) {
  6. AVLTreeNode* y = z->left;
  7. z->left = y->right;
  8. y->right = z;
  9. z->height = max(GetHeight(z->left), GetHeight(z->right)) + ;
  10. y->right = max(GetHeight(y->left), z->height) + ;
  11. z = y;
  12. }
  13.  
  14. //RR
  15. template<typename T>
  16. void AVLTree<T>::RotateWithRightChild(AVLTreeNode* &z) {
  17. AVLTreeNode* y = z->right;
  18. z->right = y->left;
  19. y->left = z;
  20. z->height = max(GetHeight(z->left), GetHeight(z->right)) + ;
  21. y->right = max(GetHeight(y->right), z->height) + ;
  22. z = y;
  23. }
  24.  
  25. //LR
  26. template<typename T>
  27. void AVLTree<T>::DoubleWithLeftChild(AVLTreeNode* &z) {
  28. RotateWithRightChild(z->left);
  29. RotateWithLeftChild(z);
  30. }
  31.  
  32. //RL
  33. template<typename T>
  34. void AVLTree<T>::DoubleWithRightChild(AVLTreeNode* &z) {
  35. RotateWithRightChild(z->right);
  36. RotateWithLeftChild(z);
  37. }
  38.  
  39. template<typename T>
  40. void AVLTree<T>::Insert(AVLTreeNode* &p, const T& k) {
  41. if (p == NULL) {
  42. t = new AVLTreeNode(k);
  43. } else if (k < p->key) { //左子树中插入
  44. Insert(p->left, k);
  45. if (GetHeight(p->left) - GetHeight(p->right) == ) { //虽然每次都检查,但是只调整最后一次
  46. if (k < p->left->key) { //LL
  47. RotateWithLeftChild(p);
  48. } else { //LR
  49. DoubleWithLeftChild(p);
  50. }
  51. }
  52. } else if (k > p->val) {//在右子树中插入
  53. Insert(p->right, k);
  54. if (GetHeight(p->right) - GetHeight(p->left) == ) {
  55. if (x > p->right->key) {//RR
  56. RotateWithRightChild(p);
  57. } else { //RL
  58. DoubleWithRightChild(p);
  59. }
  60. }
  61. }else
  62. ; //重复
  63.  
  64. p->height = max(GetHeight(p->left), GetHeight(p->right)) + ;
  65. }
  66.  
  67. template<typename T>
  68. void AVLTree<T>::Remove(AVLTreeNode* &p, const T& k) {
  69. if (p == NULL) return;
  70. if (p->key > k) {
  71. Remove(p->left, k);
  72. if (GetHeight(p->right) - GetHeight(p->left) == ) {
  73. if (p->right->right != NULL) {
  74. RotateWithRightChild(p);
  75. } else {
  76. DoubleWithRightChild(p);
  77. }
  78. }
  79. }else if (p->key < k) {
  80. Remove(p->right, k);
  81. if (GetHeight(p->left) - GetHeight(p->right) == ) {
  82. if (p->left->left != NULL) {
  83. RotateWithLeftChild(p);
  84. } else {
  85. DoubleWithLeftChild(p);
  86. }
  87. }
  88. } else if (p->left != NULL && p->right != NULL) {
  89. p->key = FindMin(p->right)->key; //用右子树最小节点键值代替要删除节点的键值,与二叉搜索树类似
  90. Remove(p->right, p->key);
  91. if (GetHeight(p->left) - GetHeight(p->right) == ) {
  92. if (p->left->left != NULL) {
  93. RotateWithLeftChild(p);
  94. } else {
  95. DoubleWithLeftChild(p);
  96. }
  97. }
  98. } else {
  99. AVLTreeNode* temp = p;
  100. p = p->left ? p->left : p->right;
  101. delete temp;
  102. }
  103.  
  104. if (p != NULL) {
  105. p->height = max(GetHeight(p->left), GetHeight(p->right)) + ;
  106. }
  107. }
  108.  
  109. template<typename T>
  110. typename AVLTree<T>::AVLTreeNode* AVLTree<T>::FindMin(AVLTreeNode* p) const {
  111. AVLTreeNode* t = p;
  112. while (t != NULL && t->left != NULL) {
  113. t = t->left;
  114. }
  115.  
  116. return t;
  117. }

  

  参考文献:1.《数据结构与算法分析C++描述》(第三版)——Mark Allen Weiss, 人民邮电出版社  

       2. http://blog.csdn.net/pyang1989/article/details/22697121

平衡树以及AVL树的更多相关文章

  1. 平衡树、AVL树

    平衡树 平衡树有AVL树.红黑树.2-3树.2-3-4树 AVL树 AVL树是最早的一种平衡树,它以发明者的名字命名:AVL是一种特殊的二叉搜索树,平移保证二叉搜索树的正确. 特征 在AVL树中节点的 ...

  2. java项目---用java实现二叉平衡树(AVL树)并打印结果(详)(3星)

    package Demo; public class AVLtree { private Node root; //首先定义根节点 private static class Node{ //定义Nod ...

  3. 从零开始学算法---二叉平衡树(AVL树)

    先来了解一些基本概念: 1)什么是二叉平衡树? 之前我们了解过二叉查找树,我们说通常来讲, 对于一棵有n个节点的二叉查找树,查询一个节点的时间复杂度为log以2为底的N的对数. 通常来讲是这样的, 但 ...

  4. 【数据结构06】二叉平衡树(AVL树)

    目录 一.平衡二叉树定义 二.这货还是不是平衡二叉树? 三.平衡因子 四.如何保持平衡二叉树平衡? 五.平衡二叉树插入节点的四种情况 六.平衡二叉树操作的代码实现 七.AVL树总结 @ 一.平衡二叉树 ...

  5. AVL树(平衡二叉查找树)

    首先要说AVL树,我们就必须先说二叉查找树,先介绍二叉查找树的一些特性,然后我们再来说平衡树的一些特性,结合这些特性,然后来介绍AVL树. 一.二叉查找树 1.二叉树查找树的相关特征定义 二叉树查找树 ...

  6. AVL树、splay树(伸展树)和红黑树比较

    AVL树.splay树(伸展树)和红黑树比较 一.AVL树: 优点:查找.插入和删除,最坏复杂度均为O(logN).实现操作简单 如过是随机插入或者删除,其理论上可以得到O(logN)的复杂度,但是实 ...

  7. Mysql为什么使用b+树,而不是b树、AVL树或红黑树?

    首先,我们应该考虑一个问题,数据库在磁盘中是怎样存储的?(答案写在下一篇文章中) b树.b+树.AVL树.红黑树的区别很大.虽然都可以提高搜索性能,但是作用方式不同. 通常文件和数据库都存储在磁盘,如 ...

  8. 二叉查找树,AVL树,伸展树【CH4601普通平衡树】

    最近数据结构刚好看到了伸展树,在想这个东西有什么应用,于是顺便学习一下. 二叉查找树(BST),对于树上的任意一个节点,节点的左子树上的关键字都小于这个节点的关键字,节点的右子树上的关键字都大于这个节 ...

  9. AVL树(二叉平衡树)详解与实现

    AVL树概念 前面学习二叉查找树和二叉树的各种遍历,但是其查找效率不稳定(斜树),而二叉平衡树的用途更多.查找相比稳定很多.(欢迎关注数据结构专栏) AVL树是带有平衡条件的二叉查找树.这个平衡条件必 ...

随机推荐

  1. 20155218 2016-2017-2 《Java程序设计》第9周学习总结

    20155218 2016-2017-2 <Java程序设计>第9周学习总结 教材学习内容总结 JDBC全名Java DataBase Connectivity,是java联机数据库的标准 ...

  2. 20155313 实验一《Java开发环境的熟悉》实验报告

    一.实验内容 1.使用JDK编译.运行简单的Java程序 2.使用IDEA 编辑.编译.运行.调试Java程序. 二.练习 题目:实现学生信息管理. 具体代码: import java.util.*; ...

  3. 20155336 《Java程序设计》实验二 (Java面向对象程序设计)实验报告

    20155336 <Java程序设计>实验二 (Java面向对象程序设计)实验报告 实验内容 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.继承.多态 初步掌握UML建模 熟悉 ...

  4. WordPress用户导入Drupal7并登录

    用户导入比较简单.使用Feeds模块中的Feeds Import工具就行. 不过有个不好地方的,导入前密码是明文,导入后该模块会自动转换为Drupal加密后的密码. 这需要导入后原wp的用户也能登录d ...

  5. selenium自动化之元素高亮显示

    目的: 在UI自动化的时候,有时候我们需要查看运行的过程.为了更好的显示这个过程,可以进行元素高亮,以显眼的颜色来提示测试人员目前的操作在哪一步. 解决办法: 使用js代码来将元素的背景颜色和边框颜色 ...

  6. python程序设计——面向对象程序设计:属性

    python 3.x 的属性 可以将属性设置为 可读,可修改,可删除 # 只读属性,不允许修改和删除 class Test: def __init__(self,value): self.__valu ...

  7. 网络流小结(HNOI2019之前)

    \(\text{一:Dinic最大流}\) 最坏复杂度 \({\mathcal O(n^2m)}\) 一般可以处理 \(10^4\) ~ \(10^5\) 的网络. struct Edge { int ...

  8. POJ 3579 Median 二分加判断

    Median Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 12453   Accepted: 4357 Descripti ...

  9. [C++基础] tips

    1. 在g++ 中使支持C++11 https://askubuntu.com/questions/773283/how-do-i-use-c11-with-g This you can do by ...

  10. PReLU——Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification

    1. 摘要 在 \(ReLU\) 的基础上作者提出了 \(PReLU\),在几乎没有增加额外参数的前提下既可以提升模型的拟合能力,又能减小过拟合风险. 针对 \(ReLU/PReLU\) 的矫正非线性 ...