ImageVerifierCode 换一换
格式:DOCX , 页数:16 ,大小:596.98KB ,
资源ID:9997482      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/9997482.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(java数据结构与算法之平衡二叉树AVL树的设计与实现分析.docx)为本站会员(b****7)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

java数据结构与算法之平衡二叉树AVL树的设计与实现分析.docx

1、java数据结构与算法之平衡二叉树AVL树的设计与实现分析java数据结构与算法之平衡二叉树(AVL树)的设计与实现普通二叉查找树的问题在开篇,我们提到过,普通二叉树(二叉查找树)在操作的时间复杂度上不一定遵循O(n),也有可能是O(n),这是为什么呢?在上一篇中,我们明明插入都按照一定规则比较的呀,其实那是因为我们在上篇测试时执行了随机插入的操作,如果此时利用上篇实现的二叉搜索树将一段已排序好的数据一个个插入后,就会发现如下情况了:从图中我们可以发现,把已排序的1-9数据进行正序和倒序插入后,树的结构已变成单向左子树或者右子树了,如果我们在往里插入已排序的数据,那么单向左子树或者右子树越来越

2、长,此时已跟单链表没有什么区别了,因此对此结构的操作时间复杂度自然就由O(n)变成O(n)了,这也就是普通二叉查找树不是严格意义上O(n)的原因。那么该如何解决这个问题呢?事实上一种解决的办法就是要有一个称为平衡的附加结构条件即:任何结点的深度不得过深,而这种数据结构就是我们本篇要分析的平衡二叉树(AVL),它本身也是一种二叉查找树,只不过不会出现前面我们分析的情形罢了,接下来我们就来分析一下这棵平衡二叉树。平衡二叉树的定义通过上面的分析,我们明白的普通二叉查找树的不足,也知道了如何去解决这个缺点,即构建树时要求任何结点的深度不得过深(子树高度相差不超过1),而最终这棵树就是平衡二叉树(Bal

3、anced Binary Tree),它是G.M. Adelson-Velsky 和 E.M. Landis在1962年在论文中发表的,因此又叫AVL树。这里我们还需要明确一个概念,AVL树只是实现平衡二叉树的一种方法,它还有很多的其他实现方法如红黑树、替罪羊树、Treap、伸展树等,后面我们还会分析其他树的实现。ok,接着来了解一下AVL树的特性:一棵AVL树是其每个结点的左子树和右子树的高度最多相差1的二叉查找树(空树的高度为-1),这个差值也称为平衡因子(其取值可以是1,0,-1,平衡因子是某个结点左右子树层数的差值,有的书上定义是左边减去右边,有的书上定义是右边减去左边,这样可能会有正

4、负的区别,但是这个并不影响我们对平衡二叉树的讨论)。如下图图(1)显然就是一棵平衡二叉树,它每个结点的左子树和右子树的高度最多相差1,同时也是一棵二叉查找树,而图二虽然也是一棵二叉查找树,但是它每个结点的左子树和右子树的高度相差却到达了2,因此不是平衡二叉树。理解了平衡二叉树的概念后,我们在思考一下,那些操作可能引起平衡发生变化呢?显然只有那些引起结点数量变化的操作才可能导致平衡被改变,也就是删除和插入操作了,如下图,我们把6插入到图a后,结构变成了图b,这时原本的平衡二叉树就失去平衡了。显然图b已失去平衡,如果发生这样的情况,我们就必须考虑插入元素后恢复二叉树的平衡性质,实际上也总是可以通过

5、对树进行简单的修复来让其重新恢复到平衡,而这样的简单操作我们就称之为旋转,当然旋转也有单旋转和双旋转之分,下面我们将会一一分析,这里有点需要明白的是,无论是插入还是删除,只有那些从插入或者删除点到根结点的路径上的结点的平衡才有可能被改变,因为只有这些结点的子树才可能发生变化,所以最终也只需针对这些点进行平衡修复操作即可。平衡二叉树的设计与实现ok,有了旋转的概念后,我们接着了解如何通过旋转来修复一棵失衡的二叉树,这里假设结点X是失衡点,它必须重新恢复平衡,由于任意结点的孩子结点最多有两个,而且导致失衡的必要条件是X结点的两棵子树高度差为2(大于1),因此一般只有以下4种情况可能导致X点失去平衡

6、: 在结点X的左孩子结点的左子树中插入元素 在结点X的左孩子结点的右子树中插入元素 在结点X的右孩子结点的左子树中插入元素 在结点X的右孩子结点的右子树中插入元素 以上4种情况,其中第情况和第情况是对称的,可以通过单旋转来解决,而第种情况和第情况是对称的,需要双旋转来解决。在分析这四种情况前,我们先看看AVL的结点该如何设计的,其声明如下:package com.zejian.structures.Tree.AVLTree;/* * Created by zejian on 2016/12/25. * Blog : 原文地址,请尊重原创 * 平衡二叉搜索树(AVL树)节点 */public c

7、lass 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.lef

8、t=left; this.right=right; this.data=data; this.height = height; 可以看出,为了满足平衡二叉树的特性,需要在原来的二叉搜索树(BST)的结点中添加一个height的字段表示高度,方便我们计算,这里强调一下,高度和深度一组相反的概念,高度是指当前结点到叶子结点的最长路径,如所有叶子结点的高度都为0,而深度则是指从根结点到当前结点的最大路径,如根结点的深度为0。这里约定空结点(空子树)的高度为-1,叶子结点的高度为0,非叶子节点的高度则根据其子树的高度而计算获取,如下图:ok,了解上述的内容,下面就来分析4种可能失衡的情景。平衡二叉树的

9、单旋转算法与实现左左单旋转(LL)情景分析从下图可以看出,结点X并不能满足AVL树的性质,因为它的左子树比右子树深2层,这种情况就是典型的LL情景,此时需要通过右向旋转来修复失衡的树,如图1,X经过右旋转后变成图2,W变为根结点,X变为W的右子树,同时W的右子树变为X的左子树,树又重新回到平衡,各个结点的子树高度差都已在正常范围。一般情况下,我们把X结点称为失衡点,修复一棵被破坏的AVL树时,找到失衡点是很重要的并把通过一次旋转即可修复平衡的操作叫做单旋转,从图3和图4可知,在原始AVL树插入7结点后,结点9变为失衡点,树再满足AVL性质,因此需要对9结点进行左左单旋转(即向右旋转)后,得到图

10、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; /重新计

11、算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的实例情景,原始的AV

12、L树在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.l

13、eft),w.height)+1; w.height=Math.max(height(w.left),height(w.right)+1; /返回新的根结点 return x;平衡二叉树的双旋转算法与实现前面两种情景都已分析完,它们都是基于单旋转的算法,但这种算法存在一个问题,那就是对情景无法生效,根本问题在于子树Y太深了,如下图所示:显然经过一次单旋转的修复后无论是X或者W作为根结点都无法符合AVL树的性质,此时就需要用双旋转算法来实现了。由于子树Y是在插入某个结点后导致X结点的左右子树失去平衡,那么就说明子树Y肯定是非空的,因此为了易于理解,我们可以把子树Y看作一个根结点和两棵子树,如下图

14、所示:ok,明白了单旋转对于情景的窘境,下面我们就通过双旋转算法来解开这个窘境。左右双旋转(LR)情景分析为了重新平衡,通过上述的分析显然不能把X根结点,而X与W间的旋转也解决不了问题,那唯一的旋转就是把Y作为新根。这样的话,X、W就不得不成为Y的孩子结点,其中W作为Y的左孩子结点,而X成为Y的右孩子结点。这里我们以下图为例来分析,为了达到以上结果,需要W、Y进行单旋转(图1),这里我们可把WY组成的子树看成前面的右右旋转情景,然后进行左向旋转,得到图2,W变为Y的左子树同时Y的左子树B变成W的右子树,其他不变,到此第一次旋转完成,进行第二次旋转,以X结点向右进行旋转(同样可看作左左情景),由

15、图2得到图3,X变成Y的右孩子结点并且Y的右子树C变成X的左子树,第二次旋转完成,树也重新恢复到平衡。在左右双旋转实例图123中,在原AVL树种插入结点7后,结点8变成了失衡点,此时需要把6结点变为根结点才能重新恢复平衡。因此先进行左向旋转再进行右向旋转,最后树恢复平衡。算法代码实现如下:/* * 左右旋转(LR旋转) x(根) w y 结点 把y变成根结点 * return */private AVLNode doubleRotateWithLeft(AVLNode x) /w先进行RR旋转 x.left=singleRotateRight(x.left); /再进行x的LL旋转 retur

16、n singleRotateLeft(x);右左双旋转(RL)情景分析对于右左双旋转(RL)情景和左右双旋转(LR)情景是一对镜像,旋转的原理上一样的,这里就不废话了,给出下图协助理解即可(已很清晰了):实现代码如下:/* * 右左旋转(RL旋转) * param w * return */private AVLNode doubleRotateWithRight(AVLNode w) /先进行LL旋转 w.right=singleRotateLeft(w.right); /再进行RR旋转 return singleRotateRight(w);好,到此4种情况都已分析完毕,接着我们就利用这种

17、四种情况来重写AVL树的插入操作过程。平衡二叉树插入操作的实现实际上,有了上述四种情况后,编写插入操作的编码细节并不会太困难,这里我们给出主要思路和代码实现即可(很清晰的注释),与BST(二叉查找树)的插入实现原理一样,使用递归算法,根据值大小查找到插入位置,然后进行插入操作,插入完成后,我们需要进行平衡判断,评估子树是否需要进行平衡修复,需要则利用上述的四种情景套入代码即可,最后要记得重新计算插入结点路径上的高度。代码实现如下:/* 插入方法* param data*/Overridepublic void insert(T data) if (data=null) throw new Ru

18、ntimeException(data cant not be null ); this.root=insert(data,root);private AVLNode insert(T data , AVLNode p) /说明已没有孩子结点,可以创建新结点插入了. if(p=ull) p=new AVLNode(data); else if(pareTo(p.data)0)/向左子树寻找插入位置 p.left=insert(data,p.left); /插入后计算子树的高度,等于2则需要重新恢复平衡,由于是左边插入,左子树的高度肯定大于等于右子树的高度 if(height(p.left)-h

19、eight(p.right)=2) /判断data是插入点的左孩子还是右孩子 if(pareTo(p.left.data)0)/向右子树寻找插入位置 p.right=insert(data,p.right); if(height(p.right)-height(p.left)=2) if (pareTo(p.right.data)0) /进行右左旋转 p=doubleRotateWithRight(p); else p=singleRotateRight(p); else ;/if exist do nothing /重新计算各个结点的高度 p.height = Math.max( heigh

20、t( p.left ), height( p.right ) ) + 1; return p;/返回根结点平衡二叉树删除操作的实现关于平衡二叉树的删除,我们这里给出一种递归的实现方案,和二叉查找树中删除方法的实现类似,但是在移除结点后需要进行平衡检测,以便判断是否需要进行平衡修复,主要明白的是,这种实现方式在删除时效率并不高,不过我们并不打算过多讨论它,更复杂的删除操作过程将放在红黑树中进行讨论。下面给出实现代码:/* * 删除方法 * param data */Overridepublic void move(T data) if (data=null) throw new RuntimeE

21、xception(data cant not be null ); this.root=remove(data,root);/* * 删除操作 * param data * param p * return */private AVLNode remove(T data,AVLNode p) if(p =null) return null; int result=pareTo(p.data); /从左子树查找需要删除的元素 if(result0) p.left=remove(data,p.left); /检测是否平衡 if(height(p.right)-height(p.left)=2) A

22、VLNode currentNode=p.right; /判断需要那种旋转 if(height(currentNode.left)height(currentNode.right) /LL p=singleRotateLeft(p); else /LR p=doubleRotateWithLeft(p); /从右子树查找需要删除的元素 else if(result0) p.right=remove(data,p.right); /检测是否平衡 if(height(p.left)-height(p.right)=2) AVLNode currentNode=p.left; /判断需要那种旋转 i

23、f(height(currentNode.right)height(currentNode.left) /RR p=singleRotateRight(p); else /RL p=doubleRotateWithRight(p); /已找到需要删除的元素,并且要删除的结点拥有两个子节点 else if(p.right!=null&p.left!=null) /寻找替换结点 p.data=findMin(p.right).data; /移除用于替换的结点 p.right = remove( p.data, p.right ); else /只有一个孩子结点或者只是叶子结点的情况 p=(p.le

24、ft!=null)? p.left:p.right; /更新高度值 if(p!=null) p.height = Math.max( height( p.left ), height( p.right ) ) + 1; return p;平衡二叉树的最少结点数和最多结点数问题关于最少结点数和最多结点数的问题,为了方便理解和简单化问题,这里我们假设AVL树的高度是h,N(h)表示高度为h的AVL树的结点数。对于求解高度为h的AVL树的最少结点数,则应该尽可能用最少结点数来填充该树,现在假设左子树填充到的高度为h-1,根据AVL树的特性,右子树的高度只能填充到h-2,因此高度为h的AVL树的最小结

25、点数为:N(h) = N(h-1) + N(h-2) + 1其中:N(h-1) 代表高度为h-1的左子树的最小结点数N(h-2) 代表高度为h-2的右子树的最小结点数1 代表当前结点(根)。求解上述递归公式可得(计算过程涉及线性代数的知识点,这里就不详细分析求解过程,毕竟我们主要求时间复杂度相关问题)其中n是AVL树的节点数:N(h)=O(1.618h)=h=1.44lognO(logn)通过上述的递推公式,我们也可以发现最少结点数的求解公式恰好符合斐波那契数列的规律(F(n)=F(n-1)+F(n-2)),因此求解最少结点数也就变得容易多了。接着,我们采用同样的方法计算最大结点数,为了得到最大结点数,则左右子树的高必须相等,即都填充到h-1,由于结点都充满了,那么该树不仅是AVL树而且还是一个完全二叉树了,则会有如下公式:N(h) = N(h-1) + N(h-1) + 1 = 2N(h-1) + 1最终求解该递归公式得:N(h)=O(2h)=h=lognO(logn)因此在两种情况下,AVL树的性质可以确保带有n个结点的AVL树的高度为O(logn)。这也意味着AVL树的操作在时间复杂度上近乎于O(logn),也就不可能出现BST(二叉查找树)的最糟糕情况O(n)。

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1