不同结构的二叉查找树,查找效率有很大的不同(单支树结构的查找效率退化成了顺序查找)。如何解决这个问题呢?关键在于如何最大限度的减小树的深度。正是基于这个想法,平衡二叉树出现了。

平衡二叉树的定义 (AVL—— 发明者为Adel’son-Vel’skii 和 Landis)

平衡二叉查找树,又称 AVL树。 它除了具备二叉查找树的基本特征之外,还具有一个非常重要的特点:它 的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值(平衡因子 ) 不超过1。 也就是说AVL树每个节点的平衡因子只可能是-1、0和1(左子树高度减去右子树高度)。

那么如何是二叉查找树在添加数据的同时保持平衡呢?基本思想就是:当在二叉排序树中插入一个节点时,首先检查是否因插入而破坏了平衡,若 破坏,则找出其中的最小不平衡二叉树,在保持二叉排序树特性的情况下,调整最小不平衡子树中节点之间的关系,以达 到新的平衡。所谓最小不平衡子树 指离插入节点最近且以平衡因子的绝对值大于1的节点作为根的子树。

平衡二叉树的操作

  • 查找操作

    平衡二叉树的查找基本与二叉查找树相同。

  • 插入操作

    在平衡二叉树中插入结点与二叉查找树最大的不同在于要随时保证插入后整棵二叉树是平衡的。那么调整不平衡树的基本方法就是: 旋转 。 下面我们归纳一下平衡旋转的4种情况:

    这里我们把必须重新平衡的节点叫做A。

    平衡因子:左子树的深度减去右子树的深度。

    相关旋转图示见 严蔚敏《数据结构》(C语言版)P235

    1. 单旋转–向右旋转平衡处理–在A的左儿子的左子树进行一次插入。此时平衡因子变为2。
    2. 单旋转–向左旋转平衡处理–在A的右儿子的右子树进行一次插入操作。此时平衡因子变为-2.
    3. 双旋转–先右后左旋转平衡处理–在A的左儿子的右子树进行一次插入操作。此时平衡因子变为2.
    4. 双旋转–先左后右平衡处理–在A的右儿子的左子树进行一次插入操作。此时平衡因子变为-2.

代码实现:

/**
*AVL 树 插入新节点 的实现
*/ struct AVLNode{
int val;
AVLNode* left;
AVLNode* right;
int height;
AVLNode(const int& value, AVLNode* lt, AVLNode* rt, int h = 0):val(value), left(lt),right(rt),height(h){}
}; /**
*Return thr height of node t ot -1 if NULL
* /
int height(AVLNode* t) const
{
return t == NULL ? -1 : t->height;
} void insert(const int& x, AVLNode* & t)
{
if(t == NULL) //empty Tree
t = new AVLNode(x, NULL, NULL);
else if(x < t->val) //插入到左儿子
{
//insert into left subtree
insert(x, t->left);
if(height(t->left) - height(t->right) == 2)
{
if(x < t->left->val) //插入到了左儿子的左子树中 ,向右单旋转
rotateWithLeftChild(t);
else //插入到左儿子的右子树, 先左后右双旋转
doubleRotateWithLeftChild(t);
}
}
else if(x > t->val) //插入到右儿子
{
insert(x, t->right);
if(height(t->right) - height(t->left) == 2)
{
if(x > t->right->val)
rotateWithRightChild(t); //插入到右儿子的右子树, 向左单旋转
else if(x < t->right->val)
doubleRotateWithRightChild(t); //插入到右儿子的左子树, 先右后左双旋转
}
}
else {
//待插入节点已经存在, do nothing
} t->height = max(height(t->left), height(t->right)) + 1;
} //向右单旋转
void rotateWithLeftChild( AVLNode* & root)
{
AVLNode *newRoot = root->left;
root->left = newRoot->right;
newRoot->right = root;
root->height = max(height(root->left), height(root->right)) + 1;
newRoot->height = max(height(newRoot->left), root->height) + 1;
root = newRoot; //root 指向新的root节点
} //向左单旋转
void rotateWithRightChild(AVLNode* & root)
{
AVLNode* newRoot = root->right;
root->right = newRoot->left;
newRoot->left = root;
root->height = max(height(root->left), height(root->right)) + 1;
newRoot->height = max(height(newRoot->right), root->height) + 1;
root = newRoot;
} //先左后右双旋转
void doubleRotateWithLeftChild(AVLNode* & root)
{
rotateWithRightChild(root->left); //先对root的左子树进行向左单旋转
rotateWithLeftChild(root); //然后对root进行向右单旋转
} //先右后左双旋转
void doubleRotateWithRightChild(AVLNode* & root)
{
rotateWithLeftChild(root->right); //先对root的右子树进行向右单旋转
rotateWithRightChild(root); //然后对root进行向左单旋转
}

平衡二叉树性能分析

平衡二叉树的性能优势:

很显然,平衡二叉树的优势在于不会出现普通二叉查找树的最差情况。其查找的时间复杂度为O(logN)。

在平衡树上进行查找的过程和二叉排序树相同,因此,在查找的过程中和给定值进行比较的关键字个数不超过树的深度。

那么,

含有n个关键字的平衡树的最大深度是多少呢?

为了解答这个问题,

可以借助 深度为h的平衡树所具有的最少节点数的计算公式:

最少节点数

S(h) = S(h - 1) + S(h - 2) + 1
其中,h = 0, S(h) = 1; h = 1, S(h) = 2.

得到h的最大值。

平衡二叉树的缺陷:

(1) 很遗憾的是,为了保证高度平衡,动态插入和删除的代价也随之增加。因此,我们在下一专题中讲讲《红黑树》 这种更加高效的查找结构。

(2) 所有二叉查找树结构的查找代价都与树高是紧密相关的,能否通过减少树高来进一步降低查找代价呢。我们可以通过多路查找树的结构来做到这一点,在后面专题中我们将通过《多路查找树/B-树/B+树 》来介绍。

(3) 在大数据量查找环境下(比如说系统磁盘里的文件目录,数据库中的记录查询 等),所有的二叉查找树结构(BST、AVL、RBT)都不合适。如此大规模的数据量(几G数据),全部组织成平衡二叉树放在内存中是不可能做到的。那么把这棵树放在磁盘中吧。问题就来了:假如构造的平衡二叉树深度有1W层。那么从根节点出发到叶子节点很可能就需要1W次的硬盘IO读写。大家都知道,硬盘的机械部件读写数据的速度远远赶不上纯电子媒体的内存。 查找效率在IO读写过程中将会付出巨大的代价。在大规模数据查询这样一个实际应用背景下,平衡二叉树的效率就很成问题了。对这一问题的解决:我们也会在《多路查找树/B-树/B+树 》 将详细分析。

上面提到的红黑树和多路查找树都是属于深度有界查找树(depth-bounded tree —DBT)

平衡二叉查找树 AVL 的实现的更多相关文章

  1. 【查找结构3】平衡二叉查找树 [AVL]

    在上一个专题中,我们在谈论二叉查找树的效率的时候.不同结构的二叉查找树,查找效率有很大的不同(单支树结构的查找效率退化成了顺序查找).如何解决这个问题呢?关键在于如何最大限度的减小树的深度.正是基于这 ...

  2. 006-数据结构-树形结构-二叉树、二叉查找树、平衡二叉查找树-AVL树

    一.概述 树其实就是不包含回路的连通无向图.树其实是范畴更广的图的特例. 树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合. 1.1.树的特性: 每个结点有零个或多个子 ...

  3. 面试题:什么叫平衡二叉查找树--AVL树

    查找.插入和删除在平均和最坏情况下都是O(log n) 增加和删除可能需要通过一次或多次树旋转来重新平衡这个树 节点的平衡因子是它的左子树的高度减去它的右子树的高度.带有平衡因子 1.0 或 -1 的 ...

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

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

  5. 平衡树初阶——AVL平衡二叉查找树+三大平衡树(Treap + Splay + SBT)模板【超详解】

    平衡树初阶——AVL平衡二叉查找树 一.什么是二叉树 1. 什么是树. 计算机科学里面的树本质是一个树状图.树首先是一个有向无环图,由根节点指向子结点.但是不严格的说,我们也研究无向树.所谓无向树就是 ...

  6. 数据结构-自平衡二叉查找树(AVL)详解

    介绍: 在计算机科学中,AVL树是最先发明的自平衡二叉查找树. 在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树. 查找.插入和删除在平均和最坏情况下都是O(log n).增 ...

  7. 算法学习 - 平衡二叉查找树实现(AVL树)

    平衡二叉查找树 平衡二叉查找树是非常早出现的平衡树,由于全部子树的高度差不超过1,所以操作平均为O(logN). 平衡二叉查找树和BS树非常像,插入和删除操作也基本一样.可是每一个节点多了一个高度的信 ...

  8. 树的平衡之AVL树——错过文末你会后悔,信我

    学习数据结构应该是一个循序渐进的过程: 当我们学习数组时,我们要体会数组的优点:仅仅通过下标就可以访问我们要找的元素(便于查找). 此时,我们思考:假如我要在第一个元素前插入一个新元素?采用数组需要挪 ...

  9. 二叉查找树(二叉排序树)的详细实现,以及随机平衡二叉查找树Treap的分析与应用

    这是一篇两年前写的东西,自我感觉还是相当不错的Treap教程.正好期末信息科学技术概论课要求交一个论文,就把这个东西修改了一下交了,顺便也发到这里吧. 随机平衡二叉查找树Treap的分析与应用 1.序 ...

随机推荐

  1. Vue ajax跨域请求

    Vue webpack-dev-server实现跨域请求 思路 配置webpack-dev-server,代理某一个路径到目标路径,同是更改源和重写 Vue里定义一个全部变量:site Vue.pro ...

  2. avalon教程-简介

    avalon是什么? avalon是一个MVVM框架, Modle-模型层,即为js中从后台接口中取出的数据,例如一个对象或者对象数组,并对这些数据进行一定的格式化.常见的返回数据是这样的{id : ...

  3. 第三方缓存软件memcached和redis异同

    memcached和redis相同点:都是以键值对的形式来存储数据,通俗讲就是一个大的hashtable缓存数据都是存在内容中 key-value 不同点:memcached:1.一个key所对应的值 ...

  4. java并发编程,通过Future取消任务

    功能:通过Executor框架提供的线程池,提交任务,使用Future取消任务 任务:增长序列号,从0开始隔1s增长1 通过Future指定时间取消任务 IncrementSequence.java ...

  5. VueJs组件prop验证简单理解

    今天看了vuejs的组件,看到了prop组件,主要作用是在传入数据的时候对传入的值做判断,写了个小例子. <div id="app"> <my-child :nu ...

  6. Dreams save us. Dreams lift us up and transform us into something better.

    Dreams save us. Dreams lift us up and transform us into something better.梦想能够拯救我们.梦想能够激励我们并让我们成为更好的人 ...

  7. Programming for thread in Java

    Programming for thread in Java Override Annotation package java.lang; import java.lang.annotation.El ...

  8. Android 关于apk 打包后的地图定位和导航失败的问题

    项目中,使用了高德地图定位,调试的debug包定位完全没有问题,但是签名打包后,却始终无法定位,发现是测试环境下的SHA1码和签名发布版的SHA1码是不同的. 所以我们需要获取发布版的SHA1码: 方 ...

  9. 【Android 界面效果46】自定义view常处理的回调方法

    onFinishInflate() 当View中所有的子控件均被映射成xml后触发 onMeasure(int, int) 确定所有子元素的大小 onLayout(boolean, int, int, ...

  10. Quartz Cron表达式的二三事

    最近在解决产品上的一个需求,就是定期生成报告(Report),我们叫做Scheduled Report. 原理:UI获取用户输入的时间信息,后台使用Spring框架设置定时任务,这里定时任务用的就是  ...