平衡二叉树(AVL)

来源:互联网 发布:d5600和d7100 知乎 编辑:程序博客网 时间:2024/06/10 05:46

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

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

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

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

平衡二叉树的操作

1. 查找操作

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

2. 插入操作

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

1) 绕某元素左旋转

                                 80                                    90

                                 /  \             左旋              /    \

                               60 90          ----->         80     120

                                    /  \                           /  \       /

                                  85 120                    60  85 100

                                        /

                                      100     

                               a)  BST树                              b ) AVL树

     分析一下:在插入数据100之前,a图的BST树只有80节点的平衡因子是-1(左高-右高),但整棵树还是平衡的。加入100之后,80节点的平衡因子就成为了-2,此时平衡被破坏。需要左旋转成b图。

     当树中节点X的右孩子的右孩子上插入新元素,且平衡因子从-1变成-2后,就需要绕节点X进行左旋转。

2) 绕某元素右旋转 

                                100                                   85

                                 /  \              右旋             /    \

                              85  120        ------->     60    100

                              /  \                                      \     /  \

                            60 90                                 80  90 120

                              \

                              80

                             a) B ST树                                b) AVL树

     当树中节点X的左孩子的左孩子上插入新元素,且平衡因子从1变成2后,就需要绕节点X进行右旋转。

3) 绕某元素的左子节点左旋转,接着再绕该元素自己右旋转。此情况下就是左旋与右旋 的结合,具体操作时可以分解成这两种操作,只是围绕点不一样而已。

                                                     

                            100                             100                                90

                             /  \             左旋           /  \             右旋          /    \

                          80  120       ------>      90  120        ------>     80   100

                          / \                                  /                               /  \      \

                       60 90                            80                              60  85  120

                            /                               / \

                          85                            60 85

      当树中节点X的左孩子的右孩子上插入新元素,且平衡因子从1变成2后,就需要先绕X的左子节点Y左旋转,接着再绕X右旋转


4) 绕某元素的右子节点右旋转,接着再绕该元素自己左旋转。此情况下就是 右旋与左旋 的结合,具体操作时可以分解成这两种操作,只是围绕点不一样而已

                               80                               80                                 85

                               /   \             旋      /  \                            /  \   

                            60  100      ------>      60 85            ------->          80 100

                                   /  \                                 \                              /     /   \     

                                85  120                        100                           60    90 120

                                   \                                   /  \

                                   90                           90  120

       当树中节点X的右孩子的左孩子上插入新元素,且平衡因子从-1变成-2后,就需要先绕X的右子节点Y右旋转,接着再绕X左旋转

平衡二叉树性能分析


平衡二叉树的性能优势:

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

平衡二叉树的缺陷:

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

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

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

package net.hr.algorithm.search;/**平衡因子枚举类*/enum BalanceFactor{LH("左子树高"),EH("左右等高"),RH("右子树高");private String illustration="";private BalanceFactor(String s){this.illustration=s;}public String toString(){return this.illustration;}}/** * 平衡二叉树结点 */class AVLNode<E extends Comparable<E>>{/**结点关键字*/E key=null;/**结点的平衡因子*/BalanceFactor bFactor=BalanceFactor.EH;/**结点的直接父亲*/AVLNode<E> parent=null;/**结点的左右孩子*/AVLNode<E> lchild,rchild=null;AVLNode(E k){this.key=k;}/** * 格式输出结点 */public String toString(){//String fomateStr="";//if(this.lchild==null)String lchildStr=(this.lchild==null)?"null":this.lchild.key.toString();String rchildStr=(this.rchild==null)?"null":this.rchild.key.toString();return this.key+"[lchild="+lchildStr+",rchild="+rchildStr+"]";}}/** * 平衡二叉查找树 * @author heartraid */public class AVL<E extends Comparable<E>> {/**树根*/private AVLNode<E> root=null;/**当前树是否变高*/public boolean isTaller=false;public AVL(){}public boolean insert(E key){System.out.print("插入["+key+"]:");if(key==null) return false;if(root==null){System.out.println("插入到树根。");root=new AVLNode<E>(key);return true;}else{System.out.print("搜索路径[");return insertAVL(key,root);}}private boolean insertAVL(E key,AVLNode<E> node){System.out.print(node.key+" —>");// 树中存在相同的key,不需要插入if(node.key.compareTo(key)==0){System.out.println("].  搜索有相同关键字,插入失败");isTaller=false;return false;}else{//左子树搜索if(node.key.compareTo(key)>0){//当前node的左孩子为空,则插入到结点的做孩子并修改结点的平衡因子为LHif(node.lchild==null){System.out.println("].  插入到"+node.key+"的左孩子");AVLNode<E> newNode=new AVLNode<E>(key);node.lchild=newNode; //设置左孩子结点newNode.parent=node; //设置父亲结点isTaller=true; //树长高了}//左孩子不为空,则继续搜索下去else{insertAVL(key,node.lchild);}//当前如果树长高了,说明是因为左孩子的添加改变了平衡因子(左高)。if(isTaller){System.out.print("          树变化了,"+node.key+"的平衡因子变化");switch(node.bFactor){    //原来结点平衡因子是LH(bf=1),则左高以后bf=2,因此需要做左平衡旋转case LH: {System.out.println("[LH=1 ——> LH=2]. 出现了不平衡现象[左比右高2]");System.out.println("          ★ 以"+node.key+"为根将树进行左平衡处理");leftBalance(node);isTaller=false; break;}//原来结点平衡因子是EH(bf=0),则左高了以后bf=1,不需要平衡处理。case EH:{System.out.println("[EH=0 ——> LH=1]. 没有不平衡现象");node.bFactor=BalanceFactor.LH;isTaller=true;break;}//原来结点平衡因子是RH(bf=-1),则左高以后bf=0,不需要平衡处理。case RH:{System.out.println("[RH=-1 ——> EH=0]. 没有不平衡现象");node.bFactor=BalanceFactor.EH;isTaller=false;break;}}//end switch}//end if}//end if//右子树搜索else{if(node.rchild==null){System.out.println("].  插入到"+node.key+"的右孩子");AVLNode<E> newNode=new AVLNode<E>(key);node.rchild=newNode; //设置右孩子结点newNode.parent=node; //设置父亲结点isTaller=true; //树长高了}else{insertAVL(key,node.rchild);}//当前如果树长高了,说明是因为右孩子的添加改变了平衡因子(右高)。if(isTaller){System.out.print("          树变化了,"+node.key+"的平衡因子变化");switch(node.bFactor){    //原来结点平衡因子是LH(bf=1),则右高以后bf=0,不需要平衡处理。case LH: {System.out.println("[LH=1 ——> EH=0]. 没有不平衡现象");node.bFactor=BalanceFactor.EH;isTaller=false;break;}//原来结点平衡因子是EH(bf=0),则右高了以后bf=-1,不需要平衡处理。case EH:{System.out.println("[EH=0 ——> RH=-1]. 没有不平衡现象");node.bFactor=BalanceFactor.RH;isTaller=true;break;}//原来结点平衡因子是RH(bf=-1),则右高以后bf=0,因此需要做右平衡旋转。case RH:{System.out.println("[RH=-1 ——> RH=-2]. 出现了不平衡现象[左比右矮2]");rightBalance(node);isTaller=false; break;}}//end switch}//end if(isTaller)}//end elsereturn true;}//end else}/** * 左平衡旋转处理 * 先对node的左子树进行单左旋处理,在对node树进行单右旋处理 *  *     100                      100                     90         *     /  \           左旋       /  \          右旋     /  \         *    80  120   ------>  90  120   ------> 80  100           *   / \                         /                        /  \     \         *  60 90                   80                     60  85  120         *     /                        / \         *    85                    60 85 *  * @param node 需要做处理的子树的根结点 */private void leftBalance(AVLNode<E> node){// node.parent指向新的孩子结点AVLNode<E> lc=node.lchild;//lc指向node的左孩子结点switch(lc.bFactor){case LH:{  //新结点插入在node的左孩子的左子树上,则需要单右旋处理System.out.println("           ┖ 对"+node.key+"进行单右旋转处理");node.bFactor=lc.bFactor=BalanceFactor.EH;rRotate(node);break;}case RH:{  //新结点插入在node的左孩子的右子树上,需要双旋处理System.out.println("            ┖ 对"+node.key+"的左子树进行单左旋转处理,再对其本身树进行单右循环处理");AVLNode<E> rd=lc.rchild; //rd指向node左孩子的右子树根switch(rd.bFactor){ //修改node与其左孩子的平衡因子case LH:{node.bFactor=BalanceFactor.RH;lc.bFactor=BalanceFactor.EH;break;}case EH:{node.bFactor=lc.bFactor=BalanceFactor.EH;break;}case RH:{node.bFactor=BalanceFactor.EH;lc.bFactor=BalanceFactor.LH;break;}}//switchrd.bFactor=BalanceFactor.EH;lRotate(node.lchild);rRotate(node);break;}}}/** * 右平衡旋转处理 *  *    80                         80                        85           *   /  \            右 旋      /  \        左 旋        /  \              *  60  100    ------>  60   85   ------->   80  100         *      /  \                       \                       /   /  \                *     85  120                100                 60  90  120         *      \                          /  \         *      90                     90  120  *  * @param node */private void rightBalance(AVLNode<E> node){AVLNode<E> lc=node.rchild;//lc指向node的右孩子结点switch(lc.bFactor){case RH:{  //新结点插入在node的右孩子的右子树上,则需要单左旋处理node.bFactor=lc.bFactor=BalanceFactor.EH;lRotate(node);break;}case LH:{  //新结点插入在node的右孩子的左子树上,需要双旋处理AVLNode<E> rd=lc.lchild; //rd指向node右孩子的左子树根switch(rd.bFactor){ //修改node与其右孩子的平衡因子case LH:{node.bFactor=BalanceFactor.EH;lc.bFactor=BalanceFactor.RH;break;}case EH:{node.bFactor=lc.bFactor=BalanceFactor.EH;break;}case RH:{node.bFactor=BalanceFactor.LH;lc.bFactor=BalanceFactor.EH;break;}}//switchrd.bFactor=BalanceFactor.EH;rRotate(node.rchild);lRotate(node);break;}}}/** * 对以node为根的子树进行单右旋处理,处理后node.parent指向新的树根,即旋转之前 * node的左孩子结点 *      100<-node.parent           80<-node.parent *      /                                      /  \ *     80             ———>         60   100 *    /  \                                  / *   60  85                            85 */private void rRotate(AVLNode<E> node){AVLNode<E> lc=node.lchild;//lc指向node的左孩子结点node.lchild=lc.rchild;lc.rchild=node;if(node.parent==null){root=lc;}else if(node.parent.lchild.key.compareTo(node.key)==0)node.parent.lchild=lc;else node.parent.rchild=lc;}/** * 对以node为根的子树进行单左旋处理,处理后node.parent指向新的树根,即旋转之前 * node的右孩子结点 *      100<-node.parent        110<-node.parent *        \                                  /  \ *        110        ————>   100  120 *        /  \                               \ *      105  120                      105 */private void lRotate(AVLNode<E> node){AVLNode<E> rc=node.rchild;//lc指向node的右孩子结点node.rchild=rc.lchild;rc.lchild=node;if(node.parent==null){root=rc;}else if(node.parent.lchild.key.compareTo(node.key)==0)    node.parent.lchild=rc;else node.parent.rchild=rc;}/** * 得到BST根节点 * @return BST根节点f */    public AVLNode<E> getRoot(){    return this.root;    } /** * 递归前序遍历树 */public void preOrderTraverse(AVLNode<E> node){if(node!=null){System.out.println(node);preOrderTraverse(node.lchild);preOrderTraverse(node.rchild);}}/** * 测试 * @param args */public static void main(String[] args) {AVL<Integer> avl=new AVL<Integer>();avl.insert(new Integer(80));avl.insert(new Integer(60));avl.insert(new Integer(90));avl.insert(new Integer(85));avl.insert(new Integer(120));avl.insert(new Integer(100));System.out.println("前序遍历AVL:");avl.preOrderTraverse(avl.getRoot());}}

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 法院给传票不去怎么办 心悦会员过期了怎么办 美宝旅行证过期怎么办 施工员证过期3年怎么办 电脑上没声音了怎么办 电脑音频被删了怎么办 微信绑定不上qq怎么办 微信绑定qq频繁怎么办 qq绑定不了微信怎么办 微信绑定不了qq怎么办 微信号登录不上怎么办 微信号登不上去怎么办 微信忘记密码怎么办啊 微信密码忘记了怎么办 维a酸乳膏掉皮怎么办 用维a酸乳膏脱皮怎么办 微信红包发不了怎么办 红包一次发3个怎么办 红包没领删除了怎么办 不主动还钱的人怎么办 老婆要离婚我不想离怎么办 男人有外遇女人该怎么办 QQ里有人假冒你怎么办 微信有人冒充我怎么办 有人冒充我的qq怎么办 微信有人冒充你怎么办 海底捞8折会员卡怎么办 西安公交卡坏了怎么办 吃生大蒜胃疼怎么办 养竹子的水臭了怎么办 雾霾引起的咳嗽怎么办 招财竹叶子发黄怎么办 养的富贵竹水臭怎么办 3年水竹叶子发黄怎么办 蒸柜下面漏蒸汽怎么办 美国自驾游驾照怎么办 装修抽屉门大了怎么办 当私人教练老了怎么办 铁艺花架不赚钱怎么办 请的关公不要了怎么办 星月放久了黄了怎么办