平衡二叉树(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.)也不知道我下面所写的是不是体现了发明者的本意,但至少,我认为现在的教科书歪曲了发明者的本意。

基本概念

?      
平衡

下面的引文出自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:

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

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.

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

?      
旋转

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


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=左子树深度-右子树深度.

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

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

二、平衡二叉树算法思想

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

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

(1)LL型平衡旋转法

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

(2)RR型平衡旋转法

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

(3)LR型平衡旋转法

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

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

(4)RL型平衡旋转法

由于在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的节点。



时间: 2024-08-02 04:15:00

平衡二叉树(AVL)的相关文章

图解平衡二叉树,AVL树(一)

图解平衡二叉树,AVL树(一) 学习过了二叉查找树,想必大家有遇到一个问题.例如,将一个数组{1,2,3,4}依次插入树的时候,形成了图1的情况.有建立树与没建立树对于数据的增删查改已经没有了任何帮助,反而增添了维护的成本.而只有建立的树如图2,才能够最大地体现二叉树的优点. 在上述的例子中,图2就是一棵平衡二叉树.科学家们提出平衡二叉树,就是为了让树的查找性能得到最大的体现(至少我是这样理解的,欢迎批评改正).下面进入今天的正题,平衡二叉树. AVL的定义 平衡二叉查找树:简称平衡二叉树.由前

平衡二叉树 AVL 的插入节点后旋转方法分析

平衡二叉树 AVL( 发明者为Adel'son-Vel'skii 和 Landis)是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1. 首先我们知道,当插入一个节点,从此插入点到树根节点路径上的所有节点的平衡都可能被打破,如何解决这个问题呢? 这里不讲大多数书上提的什么平衡因子,什么最小不平衡子树,实际上让人(me)更加费解.实际上你首要做的就是先找到第一个出现不平衡的节点,也就是高度最高的那个节点A,对以它为根的子树做一次旋转或者两次旋转,此时这个节点的平衡问题解决了,整个往

数据结构快速回顾——平衡二叉树 AVL (转)

平衡二叉树(Balanced Binary Tree)是二叉查找树的一个进化体,也是第一个引入平衡概念的二叉树.1962年,G.M. Adelson-Velsky 和 E.M. Landis发明了这棵树,所以它又叫AVL树.平衡二叉树要求对于每一个节点来说,它的左右子树的高度之差不能超过1,如果插入或者删除一个节点使得高度之差大于1,就要进行节点之间的旋转,将二叉树重新维持在一个平衡状态.这个方案很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(

平衡二叉树-AVL树的构建和操作

首先上一下单纯的二叉树插入节点的代码,用的是不返回新根节点的方式: 1 void insertNode(BTree *&root,int data) 2 { 3 if (root == nullptr)//当根节点为NULL时,在根节点上插入 4 { 5 root = initRoot(data); 6 return; 7 } 8 if (root->data == data)//当插入数据等于根节点时,不插入 9 { 10 return; 11 } 12 if (root->data

数据结构复习之平衡二叉树AVL删除

平衡二叉树的插入过程:http://www.cnblogs.com/hujunzheng/p/4665451.html 对于二叉平衡树的删除采用的是二叉排序树删除的思路: 假设被删结点是*p,其双亲是*f,不失一般性,设*p是*f的左孩子,下面分三种情况讨论: ⑴ 若结点*p是叶子结点,则只需修改其双亲结点*f的指针即可. ⑵ 若结点*p只有左子树PL或者只有右子树PR,则只要使PL或PR 成为其双亲结点的左子树即可. ⑶ 若结点*p的左.右子树均非空,先找到*p的中序前趋结点*s(注意*s是*

平衡二叉树,AVL树之代码篇

看完了第一篇博客,相信大家对于平衡二叉树的插入调整以及删除调整已经有了一定的了解,下面,我们开始介绍代码部分. 首先,再次提一下使用的结构定义 1 typedef char KeyType; //关键字 2 typedef struct MyRcdType //记录 3 { 4 KeyType key; 5 }RcdType,*RcdArr; 6 typedef enum MyBFStatus //为了方便平衡因子的赋值,这里进行枚举 7 { //RH,EH,LH分别表示右子树较高,左右子树等高

平衡二叉树,AVL树之图解篇

学习过了二叉查找树,想必大家有遇到一个问题.例如,将一个数组{1,2,3,4}依次插入树的时候,形成了图1的情况.有建立树与没建立树对于数据的增删查改已经没有了任何帮助,反而增添了维护的成本.而只有建立的树如图2,才能够最大地体现二叉树的优点.            在上述的例子中,图2就是一棵平衡二叉树.科学家们提出平衡二叉树,就是为了让树的查找性能得到最大的体现(至少我是这样理解的,欢迎批评改正).下面进入今天的正题,平衡二叉树. AVL的定义 平衡二叉查找树:简称平衡二叉树.由前苏联的数学

算法学习笔记 平衡二叉树 AVL树

AVL树是最先发明的自平衡二叉查找树, 其增删查时间复杂度都是 O(logn), 是一种相当高效的数据结构.当面对需要频繁查找又经常增删这种情景时,AVL树就非常的适用.[ 博客地址:http://blog.csdn.net/thisinnocence ] AVL树定义 AVL树诞生于 1962 年,由 G.M. Adelson-Velsky 和 E.M. Landis 发明.AVL树首先是一种二叉查找树.二叉查找树是这么定义的,为空或具有以下性质: 若它的左子树不空,则左子树上所有的点的值均小

java数据结构与算法之平衡二叉树(AVL树)的设计与实现

[版权申明]未经博主同意,不允许转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/53892797 出自[zejian的博客] 关联文章: java数据结构与算法之顺序表与链表设计与实现分析 java数据结构与算法之双链表设计与实现 java数据结构与算法之改良顺序表与双链表类似ArrayList和LinkedList(带Iterator迭代器与fast-fail机制) java数据结构与算法之栈(Stack)设

平衡二叉树AVL

1.定义 平衡二叉树(Balanced Binary Tree)是二叉查找树的一个改进,也是第一个引入平衡概念的二叉树.1962年,G.M. Adelson-Velsky 和 E.M. Landis发明了这棵树,所以它又叫AVL树.平衡二叉树要求对于每一个节点来说,它的左右子树的高度(深度)之差绝度值不能超过1.如果插入或者删除一个节点使得高度之差大于1,就要进行节点之间的旋转,将二叉树重新维持在一个平衡状态.这个方案很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况