前面笔者介绍了二叉搜索树的一般实现和平衡二叉树的实现原理。
本篇文章将继续前文的平衡搜索树来介绍一种具体的平衡搜索树—AVL树。
AVL树的特性
AVL树中,对于任一节点V,其左右子树的高度差不能超过1。这个高度差定义为平衡因子。
为了判断AVL树的平衡性,在沿用前文的平衡二叉树的模版代码中,需要添加如下方法:
public Boolean isAvlBalanced(TreeNode node) {
//获取平衡因子
int balance = node.right.height - node.left.height;
return balance > 2 && balance < -2;
}
失衡和重平衡
AVL树与常规的二叉搜索树一样,也应支持插入、删除等动态修改操作。但经过这类操作之后,节点的高度可能发生变化,以致于不再满足AVL树的条件。
观察下图的插入操作,在对图(b)一个AVL树插入’M’,于是,节点’N’、’R’和’G’ 都将失衡。类似地,删除’Y’之后, 也会如图(a)所示导致节点’R’的失衡。
如此因节点x的插入或删除而暂时失衡的节点,构成失衡节点集,记作失衡节点集UT(x)。请注意,若x为被摘除的节点,则UT(x)仅含单个节点; 但若x为被引入的节点,则UT(x)可能包含多个节点。
从对UT(x)的分析入手,分别介绍使失衡搜索树重新恢复平衡的调整算法。
节点插入失衡的恢复算法
不难看出,新引入节点x后,UT(x)中的节点都是x的祖先,且高度不低于x的祖父。以下,将其中的最深者记作g(x)—最深者即为高度最小的节点。在x与g(x)之间的通路上,设p为g(x)的孩子,v为p的孩子。注意, 既然g(x)不低于x的祖父,则p必是x的真祖先。
首先,需要找到如上定义的g(x)。为此,可从x出发沿parent指针逐层上行并核对平衡因子, 首次遇到的失衡祖先即为g(x)。既然原树是平衡的,故这一过程只需O(logn)时间。
既然g(x)是因x的引入而失衡,则p和v的高度均不会低于其各自的兄弟。因此,可在g(x)的左右孩子中寻找高度最高的节点p,用同样的方法在节点p的左右孩子中寻找节点最高的孩子v。
确定g(x),p,v三个节点后,将根据三个节点的联接方向,采用不同的局部调整方案,分述如下:
单旋
如上图(a)所示,设v是p的右孩子,p是g的右孩子。这种情况必是在v的子树中插入的新的节点x,使得g失衡,图(a)的阴影部分为插入x的节点,其兄弟为空。采用前文平衡二叉树的实现原理中的逆时针zag已g为轴旋转之后,使得g重新平衡。旋转后的结果图(b)所示。根据搜索树的等价原理来看。a,b两树的等价的。
同理,如果v是p的左孩子,p是g的左孩子。则可通过顺时针zig来重平衡。
双旋
如上图(a)所示,设节点v是p的左孩子,而p是g的右孩子。
这种情况,也必是由于在子树v中插入了新节点x,而致使g不再平衡。同样地,在图中以虚线联接的每一对灰色方块中,其一对应于新节点x,另一为空。
此时,可先做p为轴的顺时针旋转,得到如图(b)所示的一棵等价二叉搜索树。
再做g为轴的逆时针旋转zag,得到如图(c)所示的另一棵等价二叉搜索树。
此类分别以父子节点为轴、方向互逆的连续两次旋转,合称“双旋”。可见,经如此调整之后,g亦必将重新平衡。不难验证,通过zag(p)和zig(g)可以处理对称的情况。
无论单旋或双旋,经局部调整之后,不仅g(x)能够重获平衡, 而且局部子树的高度也必将复原。这就意味着,g(x)以上所有祖先的平衡因子亦将统一地复原,也意味着在AVL树中插入新节点后,仅需不超过两次旋转,即可使整树恢复平衡。
AVL树中节点的插入:
这段代码中的3+4算法即在本文后面
/**
* 添加子节点
* @throws Exception
*/
public TreeNode addNode(Double data) {
TreeNode inTree = this.search(data);
if(inTree.data != null) {
return inTree;
}
inTree