平衡二叉树(AVL)

Part One : 
这个恐怕是整个《数据结构》教科书里面最难的和最“没用”的数据结构了(如今的教科书还有部分算法内容)。说它没用,偏偏是由于它太有用——有着和普通的二叉搜索树彻底同样的接口界面,绝大多数状况下比普通的二叉搜索树效率高(不少)。所以,一般状况下,人们都是一劳永逸的——写完后就重用,而不会再写了。因此说,你虽然学完了平衡二叉树,但极可能你永远也不会亲自写一个。你如今随便在身边拉我的,让他来写一个,能顺利的写出来的恐怕很少,玩笑之词,且勿当真。

在开始写以前,我很担忧,能不能把这部分写清楚,毕竟书上满天的switch…case,而且还只是一半——有左旋没有右旋,有插入没有删除。后来,我变得有信心了——由于书上都没有说清楚,都在那里说梦话。我没有找到AVL树的发明者的原著(G. M. Adelson-Velskii and Y. M. Landis. An algorithm for the organization of information. Soviet Math. Dokl., 3:1259--1262, 1962.)也不知道我下面所写的是否是体现了发明者的本意,但至少,我认为如今的教科书歪曲了发明者的本意。node

基本概念

Ø       平衡算法

    下面的引文出自Algorithms and Data Structures Niklaus Wirth, Prentice-Hall, Englewood Cliffs, NJ, 1986 ISBN: 0-13-022005-1 pp. 215 – 226数据结构

One such definition of balance has been postulated by Adelson-Velskii and Landis [4-1]. The balance criterion is the following:app

 

A tree is balanced if and only if for every node the heights of its two subtrees differ by at most 1.ide

 

Trees satisfying this condition are often called AVL-trees (after their inventors). We shall simply call them balanced trees because this balance criterion appears a most suitable one. (Note that all perfectly balanced trees are also AVL-balanced.)函数

 

The definition is not only simple, but it also leads to a manageable rebalancing procedure and an average search path length practically identical to that of tbe perfectly balanced tree.post

科技文都比较好懂,本人翻译水平比较差,就不献丑了,我只想让你们注意最后一段的画线部分,平衡化应该是易于操做的,而毫不是如今你在书上看到的铺天盖地的switch…caseui

Ø       旋转this

    平衡化靠的是旋转。参与旋转的是3个节点(其中一个多是外部节点NULL),旋转就是把这3个节点转个位置。注意的是,左旋的时候p->right必定不为空,右旋的时候p->left必定不为空,这是显而易见的。spa

p

 

 

 

p

p

 

左旋

 

t

 

t

 

(p)

 

NULL

 

 

 

NULL

    能够看到,左旋确实是在向“左”旋转,仍是很形象的。右旋是左旋的镜像,就再也不另行说明了。下表是左旋和右旋各个节点的指针变换状况。(括号表示NULL的状况不执行)

左旋

右旋

t->parent = p->parent

p->parent = t

t->parent = p->parent

p->parent = t

(t->left->parent = p)

p->right = t->left

(t->right->parent = p)

p->left = t->right

t->left = p

p = t

t->right = p

p = t

Ø       平衡因子(bf——balance factor

    AVL树的平衡化靠旋转,而是否须要平衡化,取决于树中是否出现了不平衡。为了不每次判断平衡时,都求一下左右子树的高度,引入了平衡因子。极可能是1962年的时候AV&L没有亲自给出定义,时下里平衡因子的定义乱七八糟——我看了4本书,两本是bf 左高-右高,两本是bf 右高-左高。最有意思的是两本中国人(严蔚敏和殷人昆)写的一本左减右,一本右减左;两本外国人写的也是这样。虽然没什么原则上的差异,可苦了中国的莘莘学子们——考试的时候可无论你是哪一个门派的。我照顾本身的习惯,下面的bf = 左高-右高,习惯不一样的请本身注意。

这样一来,是否须要平衡化的条件就很明了了——| bf | > 1。若是从空树开始创建,并时刻保持平衡,那么不平衡只会发生在插入删除操做上,而不平衡的标志就是出现bf == 2或者 bf == -2的节点。

插入和删除

    在AVL树插入和删除,实际上就是先按照普通二叉搜索树插入和删除,而后再平衡化。能够确定的说,插入和删除须要的最多平衡化次数不一样(下面会给出根本缘由),但这不代表插入和删除时的平衡化的思路有很大差异。现有的教科书,仅仅从表面上看到了到了平衡化操做次数不一样的假象,而没有从根本上认识到插入和删除对称的本质,搞得乱七八糟不说(铺天盖地的switch…case),还严重的误导了读者——觉得删除操做复杂的不可捉摸。

AVL树体现了一种平衡的美感,两种旋转是互为镜像的,插入删除是互为镜像的操做,没理由会有那么大的差异。实际上,平衡化能够统一的这样来操做:

1.    while (current != NULL)修改current的平衡因子。

Ø         插入节点时current->bf += (current->data > *p)?1:-1;

Ø         删除节点时current->bf -= (current->data > *p)?1:-1;

Ø         current指向插入节点或者实际删除节点的父节点,这是普通二叉搜索树的插入和删除操做带来的结果。*p初始值是插入节点或者实际删除节点的data。由于删除操做可能实际删除的不是data

2.    判断是否须要平衡化

if (current->bf == -2) L_Balance(c_root); else if (current->bf == 2) R_Balance(c_root);

3.    是否要继续向上修改父节点的平衡因子

Ø         插入节点时if (!current->bf) break;这时,以current为根的子树的高度和插入前的高度相同。

Ø         删除节点时if (current->bf) break;这时,以current为根的子树的高度和删除前的高度相同

Ø         之因此删除操做须要的平衡化次数多,就是由于平衡化不会增长子树的高度,可是可能会减小子树的高度,在有有可能使树增高的插入操做中,一次平衡化能抵消掉增高;在有可能使树减低的删除操做中,平衡化可能会带来祖先节点的不平衡

4.    当前节点移动到父节点,转1

p = &(current->data); current = current->parent;

完整的插入删除函数以下:

bool insert(const T &data)

{

       if (!BSTree<T>::insert(data)) return false; const T* p = &data;

       while (current)

       {

              current->bf += (current->data > *p)?1:-1;

              if (current->bf == -2) L_Balance(c_root);

              else if (current->bf == 2) R_Balance(c_root);

              if (!current->bf) break;

              p = &(current->data); current = current->parent;

       }

       return true;

}

bool remove(const T &data)

{

       if (!BSTree<T>::remove(data)) return false; const T* p = &r_r_data;

//class BSTree里添加proteceted: T r_r_data,在BSTree<T>::remove(const T &data)里修改成实际删除的节点的data

       while (current)

       {

              current->bf -= (current->data > *p)?1:-1;

              if (current->bf == -2) L_Balance(c_root);

              else if (current->bf == 2) R_Balance(c_root);

              if (current->bf) break;

              p = &(current->data); current = current->parent;

       }

       return true;

}

你能够看到,他们是多么的对称。

Part Two:
平衡二叉树 (Balanced binary tree) 是由阿德尔森 - 维尔斯和兰迪斯 (Adelson-Velskii and Landis) 1962 年首先提出的,因此又称为 AVL 树。

定义:平衡二叉树或为空树,或为以下性质的二叉排序树:

  1)左右子树深度之差的绝对值不超过1;

  2)左右子树仍然为平衡二叉树.

      平衡因子BF=左子树深度-右子树深度.

平衡二叉树每一个结点的平衡因子只能是10-1。若其绝对值超过1,则该二叉排序树就是不平衡的。

如图所示为平衡树和非平衡树示意图:

2、平衡二叉树算法思想

若向平衡二叉树中插入一个新结点后破坏了平衡二叉树的平衡性。首先要找出插入新结点后失去平衡的最小子树根结点的指针。而后再调整这个子树中有关结点之间的连接关系,使之成为新的平衡子树。当失去平衡的最小子树被调整为平衡子树后,原有其余全部不平衡子树无需调整,整个二叉排序树就又成为一棵平衡二叉树。

        失去平衡的最小子树是指以离插入结点最近,且平衡因子绝对值大于1的结点做为根的子树。假设用A表示失去平衡的最小子树的根结点,则调整该子树的操做可概括为下列四种状况。

 1LL型平衡旋转法

因为在A的左孩子B的左子树上插入结点F,使A的平衡因子由1增至2而失去平衡。故需进行一次顺时针旋转操做。即将A的左孩子B右上旋转代替A做为根结点,A右下旋转成为B的右子树的根结点。而原来B的右子树则变成A的左子树。

2RR型平衡旋转法

因为在A的右孩子C的右子树上插入结点F,使A的平衡因子由-1减至-2而失去平衡。故需进行一次逆时针旋转操做。即将A的右孩子C左上旋转代替A做为根结点,A左下旋转成为C的左子树的根结点。而原来C的左子树则变成A的右子树。

3LR型平衡旋转法

因为在A的左孩子B的右子数上插入结点F,使A的平衡因子由1增至2而失去平衡。故需进行两次旋转操做(先逆时针,后顺时针)。即先将A结点的左孩子B的右子树的根结点D左上旋转提高到B结点的位置,而后再把该D结点向右上旋转提高到A结点的位置。即先使之成为LL型,再按LL型处理

      如图中所示,即先将圆圈部分先调整为平衡树,而后将其以根结点接到A的左子树上,此时成为LL型,再按LL型处理成平衡型。

4RL型平衡旋转法 

因为在A的右孩子C的左子树上插入结点F,使A的平衡因子由-1减至-2而失去平衡。故需进行两次旋转操做(先顺时针,后逆时针),即先将A结点的右孩子C的左子树的根结点D右上旋转提高到C结点的位置,而后再把该D结点向左上旋转提高到A结点的位置。即先使之成为RR型,再按RR型处理。

 如图中所示,即先将圆圈部分先调整为平衡树,而后将其以根结点接到A的左子树上,此时成为RR型,再按RR型处理成平衡型。

平衡化靠的是旋转。参与旋转的是3个节点(其中一个多是外部节点NULL),旋转就是把这3个节点转个位置。注意的是,左旋的时候p->right必定不为空,右旋的时候p->left必定不为空,这是显而易见的。

若是从空树开始创建,并时刻保持平衡,那么不平衡只会发生在插入删除操做上,而不平衡的标志就是出现bf == 2或者 bf == -2的节点。


相关文章
相关标签/搜索