• 【数据结构与算法】第四篇:AVL树



    一.二叉搜索树退化成链表与AVL树

    上节我们提到二叉搜索树的复杂度为O(logn),能够很好的解决链表O(n)的复杂度,但是二叉搜索树仍然有很大的提升空间,比如说如果我们为二叉搜索树添加元素,但是我们添加的元素都是有规律的从小到大或者从大到小就会使其退化为链表
    在这里插入图片描述

    当然情况可能并不像上面那样极端,比如下面这面图,虽然和来链表有一些差异,但是已经十分接近链表了,二叉搜索树的性能没有得到最大化的体现

    在这里插入图片描述
    上面这种现象,我们称其为失去平衡,那么如何来评判是否失去平衡呢?我们引入了平衡因子的概念。这个概念很简单,就是节点左子树高度减去节点右子树的高度,如果差值的绝对值小于等于1,就是平衡的,反之就是失衡的。
    AvL树就是一种二叉搜索树的改良的树,它继承与二叉搜索树,而且节点类型新增了高度属性,最重要的是能实现自平衡功能,保证了代码的运行效率。

    二、平衡对比

    通过对比我们可以很明显看出差距

    在这里插入图片描述

    三.简单的继承结构

    在这里插入图片描述具体代码细节可以移步代码仓库----->gitee代码仓库

    四.添加导致的失衡

    添加了13导致失衡
    在这里插入图片描述代码检验是否因添加导致失衡

     @Override
        protected void afterAdd(Node<E> node) {
            while((node=node.parent)!=null){
                if(isBalance(node)){
                    upHeight(node);
                }else {
                    //恢复最先失衡的节点就可以恢复平衡整棵树(这点要和删除节点进行区分)
                    reBalance(node);
                    break;
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    五.删除导致的失衡

    在这里插入图片描述代码检验是否因删除导致失衡

      @Override
        protected void afterRemove(Node<E> node) {
            while((node=node.parent)!=null){
                if(isBalance(node)){
                    upHeight(node);
                }else {
                    //恢复最先失衡的节点不足以恢复整棵树的平衡
                    //所以不用写break跳出循环。
                    reBalance(node);
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    六.恢复平衡

    由于添加导致的失衡和删除导致的失衡的恢复逻辑是一致的所以这里我们以添加导致的失衡来进行各种情况的探讨

    (1) LL–>(右旋转)

    ◼ g.left = p.right
    ◼ p.right = g
    ◼ 让p成为这棵子树的根节点
    ◼ 仍然是一棵二叉搜索树:T0 < n < T1 < p < T2 < g < T3
    ◼ 整棵树都达到平衡

    在这里插入图片描述
    还要注意维护的属性
    T2、p、g 的 parent 属性
    先后更新 g、p 的高度

    (2)RR–>(左旋转)

    ◼ g.right = p.left
    ◼ p.left = g
    ◼ 让p成为这棵子树的根节点
    ◼ 仍然是一棵二叉搜索树:T0 < g < T1 < p < T2 < n < T3
    ◼ 整棵树都达到平衡

    在这里插入图片描述
    ◼ 还需要注意维护的内容
    T1、p、g 的 parent 属性
    先后更新 g、p 的高度

    (3)LR

    先对parent 进行左旋转然后对grand进行右旋转
    在这里插入图片描述

    (4)RL

    先对parent进行右旋转然后对grand进行左旋转
    在这里插入图片描述

    (4)旋转代码逻辑整合

    /**
         * ◼ g.right = p.left
         * ◼ p.left = g
         * ◼ 让p成为这棵子树的根节点
         * ◼ 仍然是一棵二叉搜索树:T0 < g < T1 < p < T2 < n < T3
         * ◼ 整棵树都达到平衡
         * ----------------------------------
         * ◼ 还需要注意维护的内容
         *  T1、p、g 的 parent 属性
         *  先后更新 g、p 的高度
         * @param grand
         */
        private void rotateLeft(Node<E> grand){
            //获取图中对应的p
            Node<E> parent=grand.right;
            //获取图中的T1子树
            Node<E> child=parent.left;
            grand.right=child;
            parent.left=grand;
            afterRotate(grand,parent,child);
        }
        private void rotateRight(Node<E> grand){
            Node<E> parent=grand.left;
            Node<E> child=parent.right;
            //注意:
            grand.left=child;
            parent.right=grand;
            afterRotate(grand,parent,child);
        }
        private void afterRotate(Node<E> grand,Node<E> parent,Node<E> child){
            //更新parent,之前的parent已经成为了新的根节点
            parent.parent=grand.parent;
            //parent已将成为了新的节点,所以要连接之前节点的上层部分
            if(grand.isLeftChild()){
                //grand.parent这根新目前是没有变的,因为虽然parent.left=grand;但是它的parent一直没有动
                grand.parent.left=parent;
            }else if(grand.isRightChild()){
                grand.parent.right=parent;
            }else{
                //之前的grand本身就是根节点没有上层部分
                root=parent;
            }
            //更新parent,注意顺序
            //子树可能为null
            if(child!=null) {
                child.parent = grand;
            }
            grand.parent=parent;
            updateHeight(grand);
            //不要写错
            updateHeight(parent);
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    整体代码

    package Tree;
    
    import java.util.Comparator;
    
    public class AVL<E> extends BST<E>{
        /**
         * 注意:继承是继承除构造方法以外的成员
         * 所以要在子类中帮助父类完成构造方法,否则无法传入构造器
         * @param comparator
         */
        public AVL(Comparator<E> comparator) {
            super(comparator);
        }
        public AVL(){this(null);}
    
    
        @Override
        protected void afterAdd(Node<E> node) {
            while ((node = node.parent) != null) {
                if (isBalance(node)) {/*判断是否平衡*/
                    updateHeight(node);
                } else {
                    //恢复第一个不平衡的节点,就可以了
                reBalance2(node);
                break;
                }
            }
        }
    
        /**
         * ◼ 可能会导致父节点或祖先节点失衡(只有1个节点会失衡),其他节点,都不可能失衡
         *为什么只会造成一个节点失衡,---->因为既然会失衡那么肯定是删除左右子树中较短的那个树枝
         * 但是一个节点的高度由高的那个树枝决定,所以大概率父节点会失衡,(祖父节点可能失衡--特殊情况),但只是失衡一个节点
         * 对比;
         * (1)添加导致的失衡:-->可能导致父节点以上所有的祖先节点都是失衡,但是只需修复第一个失衡的节点即可
         * (2)删除导致的失衡:-->只会导致一个节点失衡,但是旋转后可能会使所有祖父节点都失衡(最坏情况 O(ln(n))的复杂度)
         *
         * 为什么会旋转后可能会使所有祖父节点都失衡?-->因为存在一种情况,旋转后使其整颗子树的高度减少1,只是以上所有都失衡
         * @param node
         */
        @Override
        protected void afterRemove(Node<E> node) {
            //node被删除但是parent 并没有销毁
            while((node=node.parent)!=null){
                if(isBalance(node))
                {
                    updateHeight(node);
                }else {
                    reBalance2(node);
                    //和添加不同的是-->这里一直检查到底不会break;
                }
            }
    
        }
        private void reBalance2(Node<E> grand){
            //判断是LL LR RL RR,就要知道节点的方向,在父类节点中定义方法
            Node<E> parent = ((AVLNode<E>)grand).tallerHeight();
    		Node<E> node = ((AVLNode<E>)parent).tallerHeight();
    		if (parent.isLeftChild()) { // L
    			if (node.isLeftChild()) { // LL
    				rotateRight(grand);
    			} else { // LR
    				rotateLeft(parent);
    				rotateRight(grand);
    			}
    		} else { // R
    			if (node.isLeftChild()) { // RL
    				rotateRight(parent);
    				rotateLeft(grand);
    			} else { // RR
    				rotateLeft(grand);
    			}
    		}
    
        }
    //===========================================
        /**
         * ◼ g.right = p.left
         * ◼ p.left = g
         * ◼ 让p成为这棵子树的根节点
         * ◼ 仍然是一棵二叉搜索树:T0 < g < T1 < p < T2 < n < T3
         * ◼ 整棵树都达到平衡
         * ----------------------------------
         * ◼ 还需要注意维护的内容
         *  T1、p、g 的 parent 属性
         *  先后更新 g、p 的高度
         * @param grand
         */
        private void rotateLeft(Node<E> grand){
            //获取图中对应的p
            Node<E> parent=grand.right;
            //获取图中的T1子树
            Node<E> child=parent.left;
            grand.right=child;
            parent.left=grand;
            afterRotate(grand,parent,child);
        }
        private void rotateRight(Node<E> grand){
            Node<E> parent=grand.left;
            Node<E> child=parent.right;
            //注意:
            grand.left=child;
            parent.right=grand;
            afterRotate(grand,parent,child);
        }
        private void afterRotate(Node<E> grand,Node<E> parent,Node<E> child){
            //更新parent,之前的parent已经成为了新的根节点
            parent.parent=grand.parent;
            //parent已将成为了新的节点,所以要连接之前节点的上层部分
            if(grand.isLeftChild()){
                //grand.parent这根新目前是没有变的,因为虽然parent.left=grand;但是它的parent一直没有动
                grand.parent.left=parent;
            }else if(grand.isRightChild()){
                grand.parent.right=parent;
            }else{
                //之前的grand本身就是根节点没有上层部分
                root=parent;
            }
            //更新parent,注意顺序
            //子树可能为null
            if(child!=null) {
                child.parent = grand;
            }
            grand.parent=parent;
            updateHeight(grand);
            //不要写错
            updateHeight(parent);
    
        }
        //=====================以上是旋转,以下是统一旋转逻辑========================================
    
        public void reBalance(Node<E> grand){
            //判断是LL LR RL RR,就要知道节点的方向,在父类节点中定义方法
            Node<E> parent = ((AVLNode<E>)grand).tallerHeight();
            Node<E> node = ((AVLNode<E>)parent).tallerHeight();
            if (parent.isLeftChild()) { // L
                if (node.isLeftChild()) { // LL
                    Rotate(grand,node,node.right,parent,parent.right,grand);
                } else { // LR
                    Rotate(grand, parent, node.left, node, node.right, grand);
                }
            } else { // R
                if (node.isLeftChild()) { // RL
                    Rotate(grand, grand, node.left, node, node.right, parent);
                } else { // RR
                    Rotate(grand, grand, parent.left, parent, node.left, node);
                }
            }
    
        }
        //两端a,g不受旋转影响。->如果仅对于AVL树可以不用处理啊a,g
        private void Rotate(
                Node<E> r,
                Node<E> b,Node<E> c,
                Node<E> d,
                Node<E> e,Node<E> f
    
        ){
            //先解决根节点的问题
            d.parent=r.parent;
            if(r.isLeftChild())
            {
                r.parent.left=d;
            }else if(r.isRightChild()){
                r.parent.right=d;
            }else{
                root=r;
            }
    
            //b-c
            b.right=c;
            //旋转前c父节点不一定是parent
            if(c!=null){
                c.parent=b;
            }
            //更新局部根节点的高度
            updateHeight(b);
            //e-f
            f.left=e;
            if(e!=null){
                e.parent=f;
            }
            updateHeight(f);
            //b-d-f
            d.left=b;
            d.right=f;
            //维护parent
            b.parent=d;
            f.parent=d;
            updateHeight(d);
        }
    
        private void updateHeight(Node<E> node){
           ((AVLNode<E>)node).updateHeight();
        }
    
        private boolean isBalance(Node<E> node){
            //平衡因子的绝对值是否小于一
            int a=((AVLNode<E>)node).balanceFactor();
           return Math.abs(a)<=1;
        }
        //这就是为什么在二叉树父类中单封装一个方法--佩服!!!
        @Override
        protected Node<E> createNode(E element, Node<E> parent) {
            return new  AVLNode<E>(element,parent);
        }
    
        /**
         * 因为涉及到平衡因子的判定,所以说,AvL的节点需要在原本二叉搜索树的(普通节点)
         * 额外添加height属性,以高度差判定平衡因子进而判定是否失衡,但是不适合在父类直接添加,因为会造成无用属性反复创建
         * 所以需要在AVL类中重新定义一个特有类
         */
        //内部类一般定义成static
        public static  class AVLNode<E> extends Node<E>{
            //帮助父类完成构造方法
            public AVLNode(E element, Node<E> parent) {
                super(element, parent);
            }
            //这里要注意:叶子节点的高度按理说本来应该是0,但是如果为零的话,计算平衡因子的时候就会有逻辑错误
            //所以初始化叶子节点的高度一定设置成1;
            int height=1;
            public int balanceFactor(){
                int leftHeight=left==null?0:((AVLNode<E>)left).height;
                //注意:一定要注意左右不要一味复制粘贴
                int rightHeight=right==null?0:((AVLNode<E>)right).height;
                return leftHeight-rightHeight;
            }
    
            //真正更新前节点的高度
            public void updateHeight()
            {
                int leftHeight = left == null ? 0 : ((AVLNode<E>)left).height;
                //要注意这里是right
    			int rightHeight = right == null ? 0 : ((AVLNode<E>)right).height;
    			height=1 + Math.max(leftHeight, rightHeight);
            }
            //用于确定是L还是R
            public Node<E> tallerHeight(){
                int leftHeight=left==null?0:((AVLNode<E>)left).height;
                int rightHeight=left==null?0:((AVLNode<E>)right).height;
                if(leftHeight>rightHeight) return left;
                if(leftHeight<rightHeight)return right;
                return isLeftChild()?left:right;
            }
            @Override
            public String toString() {
                String src=null;
                if(parent!=null)
                {
                    src=parent.element.toString();
                }
                return element+"(P="+src+")"+"height="+height;
    
            }
    
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259

    在这里插入图片描述

  • 相关阅读:
    uniapp 使用svg
    项目:【Boost搜索引擎】
    Abnova MYOC (人)抗体对化学性质和应用说明
    Servlet知识点总结-DX的笔记
    SRC中逻辑漏洞检查总结
    EPC在新能源光伏电站的优势和功能流程
    大模型的规模扩展是否可持续?
    Vue城市选择器示例(省市区三级)
    java计算机毕业设计山西农谷企业产品推广展网源码+系统+数据库+lw文档+mybatis+运行部署
    Flink学习13:Flink外接kafka数据源
  • 原文地址:https://blog.csdn.net/qq_61571098/article/details/126524269