前言
本文是基于你已经有一定的二叉排序树知识。如果你还是小白,可以参考我之前的博客:《数据结构:二叉搜索树(BST)的基本操作》。所以,在本文中不会再出现关于BST树的基本知识。
版本说明
著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
作者:Coding-Naga
发表日期: 2015年12月28日
链接:http://blog.csdn.net/lemon_tree12138/article/details/50393548
来源:CSDN
更多内容:分类 >> 算法与数学
概述
AVL树又叫做平衡二叉树。前言部分我也有说到,AVL树的前提是二叉排序树(BST或叫做二叉搜索树)。由于在生成BST树的过程中可能会出现线型树结构,比如插入的顺序是:1, 2, 3, 4, 5, 6, 7..., n。在BST树中,比较理想的状况是每个子树的左子树和右子树的高度相等,此时搜索的时间复杂度是log(N)。可是,一旦这棵树演化成了线型树的时候,这个理想的情况就不存在了,此时搜索的时间复杂度是O(N),在数据量很大的情况下,我们并不愿意看到这样的结果。
现在我们要做的事就是让BST在创建的过程中不要向线型树发展。方法就是让其在添加新节点的时候,不断调整树的平衡状态。
定义:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
AVL树实现
1.类图
首先我们来看一下本文编写代码的类图(见图-1)。
因为考虑到图片内容的篇幅问题,类图中有一些地方的表述以eg.或getters(setters)省略掉了,详细内容请以代码为准。
知道策略模式或是看过我之前的博客《Java设计模式——策略模式》的都知道,这里我使用了策略模式来编写代码,4种不同的旋转方式明显的一个策略模式。这样一来,代码更加简捷,逻辑也更加清晰了。
图-1 AVL树程序类图
2.节点失衡
我们对于节点平衡有这样的定义:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。而这里提到的高度差,就是我们下面会引入的平衡因子:BF。
因为AVL树说到底还是一个二叉树,只有两个子节点。而且节点失衡的发生,是因为有一个新节点的插入,这个新插入的节点导致了某些节点左右子节点高度的不一致。所以我们可以枚举出以下4种情况的失衡状态。
(1)在一个节点的左子树的左子树上插入一个新节点。即LL。在这种情况下,我们可以通过将节点右旋使其平衡。如图-2所示;
图-2 LL单右旋操作
(2)在一个节点的右子树的右子树上插入一个新节点。即RR。在这种情况下,我们可以通过将节点左旋使其平衡。如图-3所示;
图-3 RR单左旋操作
(3)在一个节点的左子树的右子树上插入一个新节点。即LR。在这种情况下,我们不能直接通过将节点左旋或右来使其平衡了。这里需要两步来完成,先让树中高度较低的进行一次左旋,这个时候就变成了LL了。再进行一次单右旋操作即可。如图-4所示;
图-4 LR先左旋再右旋操作
(4)在一个节点的右子树的左子树上插入一个新节点。即RL。在这种情况下,我们不能直接通过将节点左旋或右来使其平衡了。这里需要两步来完成,先让树中高度较低的进行一次右旋,这个时候就变成了RR了。再进行一次单左旋操作即可。如图-5所示;
图-5 RL先右旋再左旋操作
3.旋转调节平衡
从上面对节点失衡的说明,以及图解。我想你已经对旋转的操作有了一个大概地认识了吧。从图中我们也可以看出,LL型和RR型、LR型和RL型是两个行为很相似地操作。其实他们互为对称。所以在下面的讲解中,我只针对LL型和LR型两种操作进行详细讲解,另外两种是类似的。
这里还有一点,可能读者会有一些不是太明白。就是我们在旋转的过程中,比如左旋,到底是怎么旋转法,以哪个节点为中心点旋转?
上面我们说到,失衡节点的平衡因子的绝对值是大于1的。AVL树失衡类型的判断都基于这个失衡节点的。也就是说LL型需要调整的是失衡节点的左子树的左子树,LR型需要调整的是失衡节点左子树的右子树。是不是有一点绕,看看上面的旋转操作图就应该知道了。那么,对于单旋转操作也就很简单了,就是以中间节点为中心旋转。而双旋转中,因为不是一次旋转,不能在一开始就确定旋转中心点,我们在第一次旋转的过程中,只是旋转后面的两个节点,并保证节点值的大小关系,如图-4所示。后面的问题就转化成了单旋转操作了。
LL型:
针对图-2所示的LL型失衡情况,可以看到节点15上是没有右孩子的,这个时候转成平衡之后,不用考虑节点15的右孩子与旋转平衡调节之后的节点20是否有冲突。
关键代码如下:
public class AdjustLL implements Adjustable { @Override public void adjust(AVLTree tree, Node imbalanceNode) { Node parentNode = imbalanceNode.getParent(); Node leftNode = imbalanceNode.getLeft(); // 新添加的节点 resetImbalanceNode(imbalanceNode, leftNode); resetLeftNode(tree, leftNode, imbalanceNode, parentNode); resetParentNode(parentNode, leftNode); } /* * 重置失衡节点 * * @param imbalanceNode * 失衡节点 */ private void resetImbalanceNode(Node imbalanceNode, Node leftNode) { ... ... } /* * 重置失衡节点的左节点 * * @param leftNode * 左节点 * @param imbalanceNode * 失衡节点 * @param parentNode * 父节点 */ private void resetLeftNode(AVLTree tree, Node leftNode, Node imbalanceNode, Node parentNode) { ... ... } /* * 重置父节点 * * @param parentNode * 父节点 * @param leftNode * 左节点 */ private void resetParentNode(Node parentNode, Node leftNode) { ... ... } }
RR型:
类似LL型,详细代码请参见下面的GitHub源码。
RL型:
对于RL型的就比较麻烦一些了,比如我们现在插入的序列是这样的:{20, 15, 30, 40, 25, 23}。那么在调整平衡之前,我们的树结构是这样的(如图-6所示):
图-6 RL型失衡
这时我们就不能直接进行一次左旋或是右旋就可以搞定的了。虽然我们不能一步直接达到我们的要求,但是分两步就可以了呀。操作图示参见图-7.
图-7 RL型失衡的旋转过程
第一次右旋的关键代码:
/* * 第一次右旋 * (此处方法调用的顺序不要修改) * * @param tree * AVL树 * @param imbalanceNode * 失衡节点 * @param rightChildNode * 失衡节点的右孩子 * @param rightLeftNode * 失衡节点右孩子的左孩子 */ private void firstRightAdjust(AVLTree tree, Node imbalanceNode, Node rightChildNode, Node rightLeftNode) { firstRightChildAdjust(rightChildNode, rightLeftNode); firstRightLeftAdjust(imbalanceNode, rightChildNode, rightLeftNode); firstImbalanceAdjust(imbalanceNode, rightLeftNode); } /* * 调整失衡节点 * * @param imbalanceNode * 失衡节点 * @param rightLeftNode * 失衡节点右孩子的左孩子 */ private void firstImbalanceAdjust(Node imbalanceNode, Node rightLeftNode) { imbalanceNode.setRight(rightLeftNode); imbalanceNode.resetHeight(); imbalanceNode.resetBF(); } /* * 调整失衡节点的右孩子 * * @param rightChildNode * 失衡节点的右孩子 * @param rightLeftNode * 失衡节点右孩子的左孩子 */ private void firstRightChildAdjust(Node rightChildNode, Node rightLeftNode) { rightChildNode.setParent(rightLeftNode); rightChildNode.setLeft(null); rightChildNode.resetHeight(); rightChildNode.resetBF(); } /* * 调整失衡节点右孩子的左孩子 * * @param imbalanceNode * 失衡节点 * @param rightChildNode * 失衡节点的右孩子 * @param rightLeftNode * 失衡节点右孩子的左孩子 */ private void firstRightLeftAdjust(Node imbalanceNode, Node rightChildNode, Node rightLeftNode) { rightLeftNode.setRight(rightChildNode); rightLeftNode.setParent(imbalanceNode); rightLeftNode.resetHeight(); rightLeftNode.resetBF(); }第二次左旋的关键代码:
/* * 第二次左旋 * * @param tree * AVL树 * @param imbalanceNode * 失衡节点 * @param rightChildNode * 失衡节点的右孩子 * @param rightLeftNode * 失衡节点右孩子的左孩子 */ private void secondLeftAdjust(AVLTree tree, Node imbalanceNode, Node rightChildNode, Node rightLeftNode) { Node parentNode = imbalanceNode.getParent(); secondRightChildAdjust(rightChildNode); secondImbalanceAdjust(imbalanceNode, rightLeftNode); secondRightLeftAdjust(tree, imbalanceNode, rightLeftNode, parentNode); } /* * 调整失衡节点 * * @param imbalanceNode * 失衡节点 * @param rightLeftNode * 失衡节点右孩子的左孩子 */ private void secondImbalanceAdjust(Node imbalanceNode, Node rightLeftNode) { if (rightLeftNode.getLeft() != null) { imbalanceNode.setRight(rightLeftNode.getLeft()); } imbalanceNode.setParent(rightLeftNode); imbalanceNode.resetHeight(); imbalanceNode.resetBF(); } /* * 调整失衡节点的右孩子 * * @param rightChildNode * 失衡节点的右孩子 */ private void secondRightChildAdjust(Node rightChildNode) { rightChildNode.resetHeight(); rightChildNode.resetBF(); } /* * 调整失衡节点右孩子的左孩子 * * @param tree * AVL树 * @param imbalanceNode * 失衡节点 * @param rightLeftNode * 失衡节点右孩子的左孩子 * @param parentNode * 失衡节点的父节点 */ private void secondRightLeftAdjust(AVLTree tree, Node imbalanceNode, Node rightLeftNode, Node parentNode) { rightLeftNode.setParent(parentNode); if (parentNode == null) { tree.resetRoot(rightLeftNode); } rightLeftNode.setLeft(imbalanceNode); rightLeftNode.resetHeight(); rightLeftNode.resetBF(); }
LR型:
类似RL型,详细代码请参见下面的GitHub源码。
Ref
- 《数据结构(C语言版)--清华大学出版社》
- 《大话数据结构--清华大学出版社》