资源预览内容
第1页 / 共13页
第2页 / 共13页
第3页 / 共13页
第4页 / 共13页
第5页 / 共13页
第6页 / 共13页
第7页 / 共13页
第8页 / 共13页
第9页 / 共13页
第10页 / 共13页
亲,该文档总共13页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述
java数据结构与算法之平衡二叉树(AVL树)的设计与实现普通二叉查找树的问题在开篇,我们提到过,普通二叉树(二叉查找树)在操作的时间复杂度上不一定遵循O(n),也有可能是O(n),这是为什么呢?在上一篇中,我们明明插入都按照一定规则比较的呀,其实那是因为我们在上篇测试时执行了随机插入的操作,如果此时利用上篇实现的二叉搜索树将一段已排序好的数据一个个插入后,就会发现如下情况了:从图中我们可以发现,把已排序的1-9数据进行正序和倒序插入后,树的结构已变成单向左子树或者右子树了,如果我们在往里插入已排序的数据,那么单向左子树或者右子树越来越长,此时已跟单链表没有什么区别了,因此对此结构的操作时间复杂度自然就由O(n)变成O(n)了,这也就是普通二叉查找树不是严格意义上O(n)的原因。那么该如何解决这个问题呢?事实上一种解决的办法就是要有一个称为平衡的附加结构条件即:任何结点的深度不得过深,而这种数据结构就是我们本篇要分析的平衡二叉树(AVL),它本身也是一种二叉查找树,只不过不会出现前面我们分析的情形罢了,接下来我们就来分析一下这棵平衡二叉树。平衡二叉树的定义通过上面的分析,我们明白的普通二叉查找树的不足,也知道了如何去解决这个缺点,即构建树时要求任何结点的深度不得过深(子树高度相差不超过1),而最终这棵树就是平衡二叉树(Balanced Binary Tree),它是G.M. Adelson-Velsky 和 E.M. Landis在1962年在论文中发表的,因此又叫AVL树。这里我们还需要明确一个概念,AVL树只是实现平衡二叉树的一种方法,它还有很多的其他实现方法如红黑树、替罪羊树、Treap、伸展树等,后面我们还会分析其他树的实现。ok,接着来了解一下AVL树的特性:一棵AVL树是其每个结点的左子树和右子树的高度最多相差1的二叉查找树(空树的高度为-1),这个差值也称为平衡因子(其取值可以是1,0,-1,平衡因子是某个结点左右子树层数的差值,有的书上定义是左边减去右边,有的书上定义是右边减去左边,这样可能会有正负的区别,但是这个并不影响我们对平衡二叉树的讨论)。如下图图(1)显然就是一棵平衡二叉树,它每个结点的左子树和右子树的高度最多相差1,同时也是一棵二叉查找树,而图二虽然也是一棵二叉查找树,但是它每个结点的左子树和右子树的高度相差却到达了2,因此不是平衡二叉树。理解了平衡二叉树的概念后,我们在思考一下,那些操作可能引起平衡发生变化呢?显然只有那些引起结点数量变化的操作才可能导致平衡被改变,也就是删除和插入操作了,如下图,我们把6插入到图a后,结构变成了图b,这时原本的平衡二叉树就失去平衡了。显然图b已失去平衡,如果发生这样的情况,我们就必须考虑插入元素后恢复二叉树的平衡性质,实际上也总是可以通过对树进行简单的修复来让其重新恢复到平衡,而这样的简单操作我们就称之为旋转,当然旋转也有单旋转和双旋转之分,下面我们将会一一分析,这里有点需要明白的是,无论是插入还是删除,只有那些从插入或者删除点到根结点的路径上的结点的平衡才有可能被改变,因为只有这些结点的子树才可能发生变化,所以最终也只需针对这些点进行平衡修复操作即可。平衡二叉树的设计与实现ok,有了旋转的概念后,我们接着了解如何通过旋转来修复一棵失衡的二叉树,这里假设结点X是失衡点,它必须重新恢复平衡,由于任意结点的孩子结点最多有两个,而且导致失衡的必要条件是X结点的两棵子树高度差为2(大于1),因此一般只有以下4种情况可能导致X点失去平衡: 在结点X的左孩子结点的左子树中插入元素 在结点X的左孩子结点的右子树中插入元素 在结点X的右孩子结点的左子树中插入元素 在结点X的右孩子结点的右子树中插入元素 以上4种情况,其中第情况和第情况是对称的,可以通过单旋转来解决,而第种情况和第情况是对称的,需要双旋转来解决。在分析这四种情况前,我们先看看AVL的结点该如何设计的,其声明如下:package com.zejian.structures.Tree.AVLTree;/* * Created by zejian on 2016/12/25. * Blog : http:/blog.csdn.net/javazejian 原文地址,请尊重原创 * 平衡二叉搜索树(AVL树)节点 */public class AVLNode public AVLNode left;/左结点 public AVLNode right;/右结点 public T data; public int height;/当前结点的高度 public AVLNode(T data) this(null,null,data); public AVLNode(AVLNode left, AVLNode right, T data) this(left,right,data,0); public AVLNode(AVLNode left, AVLNode right, T data, int height) this.left=left; this.right=right; this.data=data; this.height = height; 可以看出,为了满足平衡二叉树的特性,需要在原来的二叉搜索树(BST)的结点中添加一个height的字段表示高度,方便我们计算,这里强调一下,高度和深度一组相反的概念,高度是指当前结点到叶子结点的最长路径,如所有叶子结点的高度都为0,而深度则是指从根结点到当前结点的最大路径,如根结点的深度为0。这里约定空结点(空子树)的高度为-1,叶子结点的高度为0,非叶子节点的高度则根据其子树的高度而计算获取,如下图:ok,了解上述的内容,下面就来分析4种可能失衡的情景。平衡二叉树的单旋转算法与实现左左单旋转(LL)情景分析从下图可以看出,结点X并不能满足AVL树的性质,因为它的左子树比右子树深2层,这种情况就是典型的LL情景,此时需要通过右向旋转来修复失衡的树,如图1,X经过右旋转后变成图2,W变为根结点,X变为W的右子树,同时W的右子树变为X的左子树,树又重新回到平衡,各个结点的子树高度差都已在正常范围。一般情况下,我们把X结点称为失衡点,修复一棵被破坏的AVL树时,找到失衡点是很重要的并把通过一次旋转即可修复平衡的操作叫做单旋转,从图3和图4可知,在原始AVL树插入7结点后,结点9变为失衡点,树再满足AVL性质,因此需要对9结点进行左左单旋转(即向右旋转)后,得到图4,我们发现此时并没有操作树的根结点(6),实际上这是因为正常情况下,不必从树的根结点进行旋转,而是从插入结点处开始,向上遍历树,并更新和修复在这个路径上的每个结点的平衡及其平衡信息(高度)即可。其代码实现如下,比较简单:/* * 左左单旋转(LL旋转) w变为x的根结点, x变为w的右子树 * param x * return */private AVLNode singleRotateLeft(AVLNode x) /把w结点旋转为根结点 AVLNode w= x.left; /同时w的右子树变为x的左子树 x.left=w.right; /x变为w的右子树 w.right=x; /重新计算x/w的高度 x.height=Math.max(height(x.left),height(x.right)+1; w.height=Math.max(height(w.left),x.height)+1; return w;/返回新的根结点右右单旋转(RR)情景分析接着再来看看右右单旋转(RR)的情景,如下图,可以发现与左左单旋转的情况恰好是一种镜像关系,同样结点X并不能满足AVL树的性质,在这样的情景下,需要对X结点进行左旋转来修复树的平衡,如图1经左旋转后变了图2,此时X变为了根结点,W变为X的左孩子,X的左子树变为W的右子树,而树又重新恢复了平衡。如图3和图4的实例情景,原始的AVL树在12处插入结点18后,结点10就变成了失衡点,因为10的左子树和右子树的高度相差2,显然不符合AVL树性质,需要对结点10进行右右单旋转修复(向左旋转),然后得到图4,此时树重新回到了平衡,这便是右右单旋转(RR)的修复情景。代码实现如下:/* * 右右单旋转(RR旋转) x变为w的根结点, w变为x的左子树 * return */private AVLNode singleRotateRight(AVLNode w) AVLNode x=w.right; w.right=x.left; x.left=w; /重新计算x/w的高度 x.height=Math.max(height(x.left),w.height)+1; w.height=Math.max(height(w.left),height(w.right)+1; /返回新的根结点 return x;平衡二叉树的双旋转算法与实现前面两种情景都已分析完,它们都是基于单旋转的算法,但这种算法存在一个问题,那就是对情景无法生效,根本问题在于子树Y太深了,如下图所示:显然经过一次单旋转的修复后无论是X或者W作为根结点都无法符合AVL树的性质,此时就需要用双旋转算法来实现了。由于子树Y是在插入某个结点后导致X结点的左右子树失去平衡,那么就说明子树Y肯定是非空的,因此为了易于理解,我们可以把子树Y看作一个根结点和两棵子树,如下图所示:ok,明白了单旋转对于情景的窘境,下面我们就通过双旋转算法来解开这个窘境。左右双旋转(LR)情景分析为了重新平衡,通过上述的分析显然不能把X根结点,而X与W间的旋转也解决不了问题,那唯一的旋转就是把Y作为新根。这样的话,X、W就不得不成为Y的孩子结点,其中W作为Y的左孩子结点,而X成为Y的右孩子结点。这里我们以下图为例来分析,为了达到以上结果,需要W、Y进行单旋转(图1),这里我们可把WY组成的子树看成前面的右右旋转情景,然后进行左向旋转,得到图2,W变为Y的左子树同时Y的左子树B变成W的右子树,其他不变,到此第一次旋转完成,进行第二次旋转,以X结点向右进行旋转(同样可看作左左情景),由图2得到图3,X变成Y的右孩子结点并且Y的右子树C变成X的左子树,第二次旋转完成,树也重新恢复到平衡。在左右双旋转实例图123中,在原AVL树种插入结点7后,结点8变成了失衡点,此时需要把6结点变为根结点才能重新恢复平衡。因此先进行左向旋转再进行右向旋转,最后树恢复平衡。算法代码实现如下:/* * 左右旋转(LR旋转) x(根) w y 结点 把y变成根结点 * return */private AVLNode doubleRotateWithLeft(AVLNode
收藏 下载该资源
网站客服QQ:2055934822
金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号