zoukankan      html  css  js  c++  java
  • 平衡树以及AVL树

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

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

      几乎所有平衡树的操作都基于树操作,通过旋转操作可以使得树趋于平衡。 对一棵查找树(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)) + 1;
    7     y->right = max(GetHeight(y->left), z->height)) + 1;
    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)) + 1;
    7     y->right = max(GetHeight(y->right), z->height)) + 1;
    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 = 0)
    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 ? -1 : 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)) + 1;
     10     y->right = max(GetHeight(y->left), z->height) + 1;
     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)) + 1;
     21     y->right = max(GetHeight(y->right), z->height) + 1;
     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) == 2) { //虽然每次都检查,但是只调整最后一次
     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) == 2) {
     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)) + 1;
     65 }
     66 
     67 
     68 template<typename T>
     69 void AVLTree<T>::Remove(AVLTreeNode* &p, const T& k) {
     70     if (p == NULL) return;
     71     if (p->key > k) {
     72         Remove(p->left, k);
     73         if (GetHeight(p->right) - GetHeight(p->left) == 2) {
     74             if (p->right->right != NULL) {
     75                 RotateWithRightChild(p);
     76             } else {
     77                 DoubleWithRightChild(p);
     78             }
     79         } 
     80     }else if (p->key < k) {
     81         Remove(p->right, k);
     82         if (GetHeight(p->left) - GetHeight(p->right) == 2) {
     83             if (p->left->left != NULL) {
     84                 RotateWithLeftChild(p);
     85             } else {
     86                 DoubleWithLeftChild(p);
     87             }
     88         }
     89     } else if (p->left != NULL && p->right != NULL) {
     90         p->key = FindMin(p->right)->key; //用右子树最小节点键值代替要删除节点的键值,与二叉搜索树类似
     91         Remove(p->right, p->key);
     92         if (GetHeight(p->left) - GetHeight(p->right) == 2) {
     93             if (p->left->left != NULL) {
     94                 RotateWithLeftChild(p);
     95             } else {
     96                 DoubleWithLeftChild(p);
     97             }
     98         }
     99     } else {
    100         AVLTreeNode* temp = p;
    101         p = p->left ? p->left : p->right;
    102         delete temp; 
    103     }
    104 
    105     if (p != NULL) {
    106         p->height = max(GetHeight(p->left), GetHeight(p->right)) + 1;
    107     }
    108 }
    109 
    110 
    111 template<typename T>
    112 typename AVLTree<T>::AVLTreeNode* AVLTree<T>::FindMin(AVLTreeNode* p) const {
    113     AVLTreeNode* t = p;
    114     while (t != NULL && t->left != NULL) {
    115         t = t->left;
    116     }
    117 
    118     return t;
    119 }

      

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

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

  • 相关阅读:
    百度和谷歌,你选择谁?
    数据库的另一种设计方法
    超级IO操作类
    WEB工具类,很强很大
    JS在AJAX中获取鼠标坐标
    弃掉HTML标记的小巧代码
    XML工具操作类,很强大
    FTP 下载功能代码
    db4o开门之篇
    ASP.NET程序中常用代码汇总(转载)
  • 原文地址:https://www.cnblogs.com/vincently/p/4225976.html
Copyright © 2011-2022 走看看