看图说话之平衡二叉排序树

    本文在看图说话之二叉排序树的基础上介绍了平衡二叉排序树,结构性较好的二叉排序树其插入和删除操作的时间复杂度接近Log(N),但是存在某些特定的情况二叉排序树会退化到单链表的情况,比如由顺序或者逆序或者基本有序的数组构建的排序二叉树,此时插入和删除操就上升到了Log(N)的时间复杂度。下图所示就是结构性良好和退化的二叉排序树。

图1 最优BST树形结构和最差BST树形

    为了解决二叉排序树的这种退化的情况,在其基础上提出了平衡二叉排序树,实现平衡二叉排序树的方法有很多种,其中最基础就是AVL树,本文详细的介绍下AVL树实现平衡的基本原理以保证二叉排排序树具有Log(N)的时间复杂界。

一丶平衡二叉排序树

    平衡二叉排序树如其名字在二叉树上约定了平衡条件,通过下图这个平衡二叉树和非平衡二叉树来说明平衡二叉树的平衡条件。


图2 平衡和非平衡二叉树

    平衡性,是指每个节点的左子树高度和右子树高度之差不超过1,即 Math.abs(Hieght(node.left) –Height(node.right))<1。对于图2中的平衡二叉树而言,其上每个节点都满足这个性质。图2中的非平衡树,之所以不是平衡树,是因为在根节点处平衡性遭到了破坏,其左子树高度和右子树高度之差为2。

二丶AVL树平衡性调整策略

    我觉得研究AVL树调整平衡性策略的正确姿势应该包括两步,第一步研究导致二叉排序树不平衡的情况是哪几种。第二步针对具体的不平衡情况如何调整。
先研究二叉排序树的不平衡情况,然后针对二叉排序树的不平衡特点给出解决方案:

(1) 向节点的左儿子的左子树插入元素导致的平衡性破坏。


图3 第一种非平衡情况

    可以看到,插入元素1后,是节点10处的平衡性遭到了破坏。这里要强调一下,在更一般的情况下,插入元素有破坏平衡性的可能,而平衡性被破坏的节点可能发生在插入路径上的每一个节点,可能是父节点,可能是其他节点。
对于该种平衡性破坏给出的解决方法称为左旋转其过程如下图所示:


图 4Single左旋转平衡性调整

    通过图4可以清晰的看出Single左旋转调整平衡性的特点,利用文字描述过于拗口,利用伪代码描述如下:假设平衡性被破坏的节点是node。
//调整node节点平衡性
node = SingleLeftRotation(node);
//balacne函数定义如下
public node SingleLeftRotation( (Nodenode){
        if(node ==null) return null;
           Node newRoot= node.left;
           node.left = newRoot.right;
           newRoot.right = node;
           return newRoot;
}

(2)向节点的右儿子的右子树插入元素导致的平衡性破坏。

图5第二种非平衡情况

该种情况下的非平衡性情况十分清晰,并且和第一种非平衡性情况是对称的,不妨成这种非平衡性破坏为“右右“,第一种非平衡性破坏为”左左“。该种情况下的调整过程如图所示,这调整过程称为single右旋转。


图6 single右旋转调整

上述调整过程用伪代码实现如下:

//调整node节点平衡性
node = SingleRightRotation(node);
//balacne函数定义如下
public node SingleRightRotation( (Nodenode){
        if(node==null) return null;
           Node newRoot= node.right;
           node.right = newRoot.left;
           newRoot.left= node;
           return newRoot;
}

(3)向节点的左儿子的右子树插入元素导致平衡性破坏


图7 第三种平衡破坏情况

这种不平衡性的情况,形象的将其称之为“左右“不平衡,该情况的不平衡的过程如下图所示:


图 8 double左旋转调整

    图8清晰的说明了调整平衡性的过程,在掌握的single左旋转和single右旋转的基础上看懂上述过程丝毫不苦难,上述过程称为double左旋转利用伪代码描述如下:
//调整node节点平衡性
node = dounleLeftRotation (node);
//balacne函数定义如下
public node dounleLeftRotation(Nodenode){
           node.left=singleRightRotation(node.left);
           node = singleLeftRotation(node);
           return node;
}

(4)向节点的右儿子的左子树插入元素导致平衡性破坏


图9 第四种不平衡情况

该种平衡情况是向右儿子的左子树插入元素导致不平衡,这种情况形象的称之为“右左“不平衡,和”左右“不平衡是对称的处理的手段都是类似的。下图详细的描述了这种情况如何调整平衡


图10 double右旋转

通过图10可以清晰的看到调整平衡的过程,上述过程和double左旋转是对称的操作,利用伪代码描述如下:
//调整node节点平衡性
node = dounleRightRotation (node);
//balacne函数定义如下
public node dounleLeftRotation(Nodenode){
           node.left=singleLeftRotation(node.left);
           node = singleRightRotation(node);
           return node;
}
三丶AVL树完整的java实现
public classAVLTree {
    publicNode root = null;
     publicstatic void main(String []args){
       int[] elements = new int[]{1,2,3,4,5};
       AVLTree avl = new AVLTree();
       /****测试插入,建树和中序遍历操作***/
       avl.buildTree(elements);
       /********测试二叉树的高度***************/
       System.out.println(avl.root.height);
    //  bst.midTrace();
    //  System.out.println();
       /****测数删除操作之无儿子***************/
    //  bst.delete(9);
    //  bst.midTrace();
       /****测数删除操作之只有一个儿子***************/
    //  bst.delete(4);
    //  bst.midTrace();
       /****测数删除操作只有两个儿子***************/
    //  bst.delete(5);
    //  bst.midTrace();
    }
   
    //节点类型定义
    publicstatic class Node{
       publicint element;
       publicNode left;
       publicNode right;
       //平衡二叉排序树,为了保证平衡,对每个节点维护了高度信息
       publicint  height;
       publicNode(int element){
           this.element = element;
           left = null;
           right = null;
           height = 0;
       }
    }
    //外部使用的插入方法
    publicvoid insert(int element){
       root=insert(element,root);
    }
    //内部使用的插入方法
    private  Node insert(int element,Node root){
       if(root==null)
           return new Node(element);
       intdelta = element-root.element;
       if(delta<0){
           root.left = insert(element,root.left);
       }elseif(delta>0){
           root.right = insert(element,root.right);
       }else{
           //不做处理
       }
       returnbalanced(root);
    }
    //外部使用的建树方法
    publicvoid buildTree(int [] elements){
       if(root==null){
           for(int i=0;i<elements.length;i++){
              insert(elements[i]);
           }
       }else{
           //若树以存在,则不能建
       }
    }
    publicvoid delete(int element){
       delete(element,root);
    }
    publicNode delete(int element,Node root){
       //未定位到删除元素
       if(root==null) return null;
       intdelta = element-root.element;
       if(delta<0){
           root.left = delete(element,root.left);
       }elseif(delta>0){
           root.right =delete(element,root.right);
       }else{
           if(root.left!=null&&root.right!=null){
              Node node = findMin(root.right);
              root.element =node.element;
              root.right = delete(node.element,root.right);
           }else{
              root = root.left!=null?root.left:root.right;
           }
       }
       returnbalanced(root);
    }
    //中序遍历二叉树
    publicvoid midTrace(){
       if(root!=null){
           print(root);
       }
    }
    privatevoid print(Node node){
       if(node!=null){
           print(node.left);
           System.out.print(node.element+" ");
           print(node.right);
       }
    }
    //找到该节点下子树的最小节点。
    publicNode findMin(Node node){
       if(node.left==null)
           return node;
       else
          
           return findMin(node.left);
    }
    publicNode balanced(Node node){
       node.height= Math.max(getHeight(node.left),getHeight(node.right))+1;
       if(getHeight(node.left)-getHeight(node.right)>1){
           if(getHeight(node.left.left)>getHeight(node.left.right)){
              node = singleLeftRotation(node);
           }else{
              node = doubleLeftRotation(node);
           }
       }
       if(getHeight(node.right)-getHeight(node.left)>1){
           if(getHeight(node.right.left)>getHeight(node.right.right)){
              node =doubleLeftRotation(node);
           }else{
              node = singleRightRotation(node);
           }
       }
       returnnode;
    }
    publicint getHeight(Node node){
       returnnode==null?-1:node.height;
    }
    publicNode singleLeftRotation(Node node){
       Node newRoot = node.left;
       node.left = newRoot.right;
       //左右子树变化更新高度
       node.height = Math.max(getHeight(node.left),getHeight(node.right))+1;
       newRoot.right=node;
       //左右子树变化更新高度
       newRoot.height=  Math.max(getHeight(newRoot.left),getHeight(newRoot.right))+1;
       returnnewRoot;
    }
    publicNode singleRightRotation(Node node){
       Node newRoot = node.right;
       node.right = newRoot.left;
       //左右子树变化更新高度
       node.height = Math.max(getHeight(node.left),getHeight(node.right))+1;
       newRoot.left=node;
       //左右子树变化更新高度
       newRoot.height=  Math.max(getHeight(newRoot.left),getHeight(newRoot.right))+1;
       returnnewRoot;
    }
    publicNode doubelRightRotation(Node node){
       node.right = singleLeftRotation(node.right);
       returnsingleRightRotation(node);
    }
    publicNode doubleLeftRotation(Node node){
       node.left = singleRightRotation(node.left);
       returnsingleLeftRotation(node);
    }
}

Reference:
[1] 数据结构与算法 java语言描述
[2] 原文博客链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容