在上一个专题中,我们在谈论二叉查找树的效率的时候。不同结构的二叉查找树,查找效率有很大的不同(单支树结构的查找效率退化成了顺序查找)。如何解决这个问题呢?关键在于如何最大限度的减小树的深度。正是基于这个想法,平衡二叉树出现了。
平衡二叉树的定义 (AVL—— 发明者为Adel'son-Vel'skii 和 Landis)
平衡二叉查找树,又称 AVL树。 它除了具备二叉查找树的基本特征之外,还具有一个非常重要的特点:它 的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值(平衡因子 ) 不超过1。 也就是说AVL树每个节点的平衡因子只可能是-1、0和1(左子树高度减去右子树高度)。
那么如何是二叉查找树在添加数据的同时保持平衡呢?基本思想就是:当在二叉排序树中插入一个节点时,首先检查是否因插入而破坏了平衡,若 破坏,则找出其中的最小不平衡二叉树,在保持二叉排序树特性的情况下,调整最小不平衡子树中节点之间的关系,以达 到新的平衡。所谓最小不平衡子树 指离插入节点最近且以平衡因子的绝对值大于1的节点作为根的子树。
平衡二叉树的操作
1. 查找操作
平衡二叉树的查找基本与二叉查找树相同。
2. 插入操作
在平衡二叉树中插入结点与二叉查找树最大的不同在于要随时保证插入后整棵二叉树是平衡的。那么调整不平衡树的基本方法就是: 旋转 。 下面我们归纳一下平衡旋转的4中情况
1) 绕某元素左旋转
80 90
/ \ 左旋 / \
60 90 ---- -> 80 120
/ \ / \ /
85 120 60 85 100
/
100
a) BST树 b ) AVL树
分析一下:在插入数据100之前,a图的B ST树只有80节点的平衡因子是-1(左高-右高),但整棵树还是平衡的。加入100之后,80节点的平衡因子就成为了-2,此时平衡被破坏。需要左旋转成b 图。
当树中节点X的右孩子的右孩子上插入新元素,且平衡因子从-1变成-2后,就需要绕节点X进行左旋转。
2) 绕某元素右旋转
100 85
/ \ 右旋 / \
85 120 ------ -> 60 100
/ \ \ / \
60 90 80 90 120
\
80
a) B ST树 b) AVL树
当树中节点X的左孩子的左孩子上插入新元素,且平衡因子从1变成2后,就需要绕节点X进行右旋转。
3) 绕某元素的左子节点左旋转,接着再绕该元素自己右旋转。 此情况下就是左旋与右旋 的结合,具体操作时可以分 解成这两种操作,只是围绕点不一样而已。
100 100 90
/ \ 左旋 / \ 右旋 / \
80 120 ------> 90 120 ------> 80 100
/ \ / / \ \
60 90 80 60 85 120
/ / \
85 60 85
当树中节点X的左孩子的右孩子上插入新元素,且 平衡因子从1变成2后,就需要 先绕X的左子节点Y左旋转,接着再绕X右旋转
4) 绕某元素的右子节点右旋转,接着再绕该元素自己左旋转。 此情况下就是 右旋与左旋 的结合,具体操作时可以分解 成这两种操作,只是围绕点不一样而已 。
80 80 85
/ \ 右 旋 / \ 左 旋 / \
60 100 ------> 60 85 -------> 80 100
/ \ \ / / \
85 120 100 60 90 120
\ / \
90 90 120
当树中节点X的右孩子的左孩子上插入新元素,且 平衡因子从-1变成-2后,就需要 先绕X的右子节点Y右旋转,接着再绕X左旋转
平衡二叉树性能分析
平衡二叉树的性能优势:
很显然,平衡二叉树的优势在于不会出现普通二叉查找树的最差情况。其查找的时间复杂度为O(logN)。
平衡二叉树的缺陷:
(1) 很遗憾的是,为了保证高度平衡,动态插入和删除的代价也随之增加。因此,我们在下一专题中讲讲《红黑树》 这种更加高效的查找结构。
(2) 所有二叉查找树结构的查找代价都与树高是紧密相关的,能否通过减少树高来进一步降低查找代价呢。我们可以通过多路查找树的结构来做到这一点,在后面专题中我们将通过《多路查找树/B-树/B+树 》来介绍。
(3) 在大数据量查找环境下(比如说系统磁盘里的文件目录,数据库中的记录查询 等),所有的二叉查找树结构(BST、AVL、RBT)都不合适。如此大规模的数据量(几G数据),全部组织成平衡二叉树放在内存中是不可能做到的。那么 把这棵树放在磁盘中吧。问题就来了:假如构造的平衡二叉树深度有1W层。那么从根节点出发到叶子节点很可能就需要1W次的硬盘IO读写。大家都知道,硬盘的机械部件读写数据的速度远远赶不上纯电子媒体的内存。 查找效率在IO读写过程中将会付出巨大的代价。在大规模数据查询这样一个实际应用背景下,平衡二叉树的效率就很成问题了。对这一问题的解决:我们也会在《多路查找树/B-树/B+树 》 将详细分析。
上面提到的红黑树和多路查找树都是属于深度有界查找树(depth-bounded tree —DBT)
来自于:http://hxraid.iteye.com/blog/609949
----------------------------------------------------------------------------------------------------------------------------------------------------------
avltree.c函数实现
testavl.c测试AVL树的实现
测试:首先插入1到7,然后插入16到10,最后插入8和9。AVL树的应该为下图所示
测试结果如下图所示
---------------------------------------------------------------------------------------------------------------------------------------------------------
来于:http://blog.csdn.net/zitong_ccnu/article/details/11097663
--------------------------------------------------------------------
//关于平衡二叉树的实现 BBST(Blance binary search tree)
#include <cstdio>
#include <cstdlib>
//#define _OJ_
typedef struct tree
{
int data;
int height;
struct tree *lchild;
struct tree *rchild;
} tree, *Bitree;
int
Height(Bitree T)
{
if(T == NULL) return -1;
else return T->height;
}
int
Max(int h1, int h2)
{
return h1 > h2 ? h1 : h2;
}
Bitree
Left(Bitree T)
{
Bitree T1;
T1 = T->rchild;
T->rchild = T1->lchild;
T1->lchild = T;
T->height = Max(Height(T->lchild), Height(T->rchild)) + 1;
printf("T == %d\n", T->height);
T1->height = Max(Height(T1->lchild), Height(T1->rchild)) + 1;
printf("T1 == %d\n", T1->height);
return T1;
}
Bitree
Right(Bitree T)
{
Bitree T1;
T1 = T->lchild;
T->lchild = T1->rchild;
T1->rchild = T;
T->height = Max(Height(T->lchild), Height(T->rchild)) + 1;
printf("T == %d\n", T->height);
T1->height = Max(Height(T1->lchild), Height(T1->rchild)) + 1;
printf("T1 == %d\n", T1->height);
return T1;
}
Bitree
Left_Right(Bitree T)
//左孩子的右孩子插入元素
{
T = Left(T);
return Right(T);
}
Bitree
Right_Left(Bitree T)
//右孩子的左孩子插入元素
{
T = Right(T);
return Left(T);
}
Bitree
Insert(Bitree T, int x)
{
if(T == NULL) {
T = (Bitree) malloc (sizeof(tree));
T->data = x; T->height = 0;
T->lchild = T->rchild = NULL;
}
else if(x < T->data) {
T->lchild = Insert(T->lchild, x);
if(Height(T->lchild) - Height(T->rchild) == 2) {
if(x < T->lchild->data)
T = Right(T); //在左边左儿子插入元素ll此时右旋
if(x > T->lchild->data)
T = Left_Right(T); //在左边右儿子的插入元素lr此时左右旋
}
}
else if(x > T->data) {
T->rchild = Insert(T->rchild, x);
if(Height(T->rchild) - Height(T->lchild) == 2) {
printf("\n%d %d\n", Height(T->rchild), Height(T->lchild));
printf("ok\n");
if(x > T->rchild->data)
T = Left(T); //在右边右儿子插入元素rr此时左旋
if(x < T->lchild->data)
T = Right_Left(T); //在右边左儿子插入元素rl此时右左旋
}
}
T->height = Max(Height(T->lchild), Height(T->rchild)) + 1;
return T;
}
void
preoder(Bitree T)
{
if(T) {
printf("data == %d\n", T->data);
preoder(T->lchild);
printf("\n");
preoder(T->rchild);
}
}
int main(int argc, char const *argv[]) {
#ifndef _OJ_ //ONLINE JUDGE
freopen("input.txt", "r", stdin);
//freopen("output.txt", "w", stdout);
#endif
int a[100];
int n;
Bitree T = NULL;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]); printf("%d ", a[i]);
T = Insert(T, a[i]);
}
preoder(T);
// printf("%d\n", Height(T));
// printf("%d\n", T->data);
return 0;
}