数据结构07-AVL树

作者: 最爱的火 | 来源:发表于2018-05-10 10:02 被阅读20次

    数据结构07-AVL树

    一、AVL树的基本概念

    1.AVL树

    AVL树是一种每一个节点的左子树与右子树的高度差最多等于1的自平衡二叉查找树。

    AVL树查找效率比一般的二叉查找树,但是插入删除效率低。

    2.平衡因子

    平衡因子(BF:BalanceFactor)是二叉树上节点的左子树深度减去右子树深度的值。

    3.最小不平衡子树

    最小不平衡子树是距离插入节点最近,且平衡因子的绝对值大于1的节点为根的子树。

    新增一个节点A后,如果树的深度+1,那么A的所有父节点的平衡因子的绝对值都会+1,距离A最近,且平衡因子的绝对值大于1的节点B就是导致整棵树不平衡的因素,因为B的所有父节点的平衡因子,在插入B之前肯定为0。

    二、AVL树的操作

    AVL树的插入经常需要进行旋转:左旋和右旋。

    1.左旋

    左旋就是将节点A变成A的右子节点B的左子节点,并将B原来的左子节点变出A的右子节点,即把A向左下移一位。

    public void leftRoate(Node<E> node) {
        if (node == null) {
            return;
        }
    
        Node<E> right = node.right;
        // 1.把node的右子节点的左节点变成node的右子节点
        node.right = right.left;
        if (right.left != null) {
            right.left.parent = node;
        }
    
        // 2.把node的右子节点变出node的父节点的子节点
        right.parent = node.parent;
        if (node.parent == null) {
            root = right;
        } else if (node.parent.right == node) {
            node.parent.right = right;
        } else if (node.parent.left == node) {
            node.parent.left = right;
        }
    
        // 3.把node变成node的右子节点的左子节点
        right.left = node;
        node.parent = right;
    }
    

    2.右旋

    右旋就是将节点A变成A的左子节点B的右子节点,并将B原来的右子节点变出A的左子节点,即把A向右下移一位。

    public void rightRoate(Node<E> node) {
        if (node == null) {
            return;
        }
    
        Node<E> left = node.left;
        // 1.把node的左子节点的右节点变成node的左子节点
        node.left = left.right;
        if (left.right != null) {
            left.right.parent = node;
        }
    
        // 2.把node的左子节点变出node的父节点的子节点
        left.parent = node.parent;
        if (node.parent == null) {
            root = left;
        } else if (node.parent.left == node) {
            node.parent.left = left;
        } else if (node.parent.right == node) {
            node.parent.right = left;
        }
    
        // 3.把node变成node的左子节点的右子节点
        left.right = node;
        node.parent = left;
    }
    

    三、AVL树的插入

    AVL树的插入很复杂,可以分为三步:添加、检查平衡、修正平衡。

    这里把新增节点记作A,把A的父节点记作B,把找到的最小不平衡子树记作C。

    1.添加

    第一步的添加跟二叉搜索树的插入一样。

    2.检查平衡

    (1)如果A是左子节点,则B的平衡因子+1;如果A是右子节点,则B的平衡因子-1。

    (2)判断B的平衡因子:

    • 如果=0,说明整颗树的深度没变,则B的所有父节点的平衡因子不受影响,所以整棵树还是平衡的,不需要修正;
    • 如果=1或=-1,说明整颗树的深度变了,则遍历其父节点,并重复(1)、(2)的操作,直到找到平衡因子的绝对值=2的父节点,即为最小不平衡子树C。

    3.修正平衡

    修正上一步找到的最小不平衡子树C,可以分为4种情况:

    • 单向右旋RR:由于在C的左子节点的左子树上插入节点,C的平衡因子由1增至2,致使以C为根的子树失去平衡,则需进行一次C的右旋转操作;
    • 单向左旋LL:由于在C的右子节点的右子树上插入节点,C的平衡因子由-1变为-2,致使以C为根的子树失去平衡,则需进行一次C的左旋转操作;
    • 先左后右LR:由于在C的左子节点的右子树上插入节点,C的平衡因子由1增至2,致使以C为根的子树失去平衡,则需进行两次旋转,先对C的左子节点左旋,再对C右旋。
    • 先右后左RL:由于在C的右子节点的左子树上插入节点,C的平衡因子由-1变为-2,致使以C为根的子树失去平衡,则需进行两次旋转,先对C的右子节点右旋,再对C左旋。

    4.代码实现

    添加元素:

    public void put(E data) {
        Node<E> now = new Node<E>(data);
        if (root == null) {
            root = now;
            return;
        }
    
        Node<E> parent = root;
        // 添加:像二叉查找树一样添加
        while (parent != null) {
            if (data.compareTo(parent.data) < 0) {
                if (parent.left == null) {
                    parent.left = now;
                    now.parent = parent;
                    break;
                } else {
                    parent = parent.left;
                }
            } else if (data.compareTo(parent.data) > 0) {
                if (parent.right == null) {
                    parent.right = now;
                    now.parent = parent;
                    break;
                } else {
                    parent = parent.right;
                }
            } else {
                return;
            }
        }
    
        // 检查平衡,修正位置
        while (parent != null) {
            // 如果新增节点是左子节点,则其父节点的平衡因子+1
            if (data.compareTo(parent.data) < 0) {
                parent.bran++;
                // 如果新增节点是右子节点,则其父节点的平衡因子-1
            } else {
                parent.bran--;
            }
    
            if (parent.bran == 0) {
                break;
            } else if (Math.abs(parent.bran) == 2) {
                // 出现平衡问题
                fixAfterInsertion(parent);
                break;
            } else {
                parent = parent.parent;
            }
        }
    }
    

    修正位置:

    private void fixAfterInsertion(Node<E> parent) {
        if (parent.bran == 2) {
            leftBranch(parent);
        }
        if (parent.bran == -2) {
            rightBranch(parent);
        }
    }
    

    对左子树操作:

    public void leftBranch(Node<E> node) {
        if (node == null) {
            return;
        }
    
        Node<E> left = node.left;
        //单向右旋
        if (left.bran == LH) {
            rightRoate(node);
            //平衡因子都变成0
            node.bran = EH;
            left.bran = EH;
    
            //先左旋,再右旋。
        } else if (left.bran == RH) {
            Node<E> lr = left.right;
            leftRoate(left);
            rightRoate(node);
            //2次旋转后,left变出lr的左子节点,node变出lr的右子节点
            if (lr.bran == LH) {
                node.bran = RH;
                left.bran = EH;
                lr.bran = EH;
            } else if (lr.bran == RH) {
                node.bran = EH;
                left.bran = LH;
                lr.bran = EH;
                // lr为叶子节点时
            } else if (lr.bran == EH) {
                node.bran = EH;
                left.bran = EH;
                lr.bran = EH;
            }
        }
    }
    

    对右子树操作:

    public void rightBranch(Node<E> node) {
        if (node == null) {
            return;
        }
    
        Node<E> right = node.right;
        //单向左旋
        if (right.bran == RH) {
            leftRoate(node);
            //平衡因子都变成0
            node.bran = EH;
            right.bran = EH;
            //先右旋,再左旋。
        } else if (right.bran == LH) {
            Node<E> rl = right.left;
            rightRoate(right);
            leftRoate(node);
            //2次旋转后,right变出rl的右子节点,node变出rl的左子节点
            if (rl.bran == LH) {
                node.bran = EH;
                right.bran = RH;
                rl.bran = EH;
            } else if (rl.bran == RH) {
                node.bran = LH;
                right.bran = EH;
                rl.bran = EH;
                // rl为叶子节点时
            } else if (rl.bran == EH) {
                node.bran = EH;
                right.bran = EH;
                rl.bran = EH;
            }
        }
    }
    

    四、AVL树的删除

    从AVL树中删除可以通过把要删除的节点向下旋转成一个叶子节点,接着直接剪除这个叶子节点来完成。

    代码暂无...

    五、AVL树的查找

    AVL树的查找与二叉搜索树的查找一样。

    最后

    代码地址:https://gitee.com/yanhuo2008/Common/blob/master/Tool/src/main/java/gsw/tool/datastructure/tree/TreeAVL.java

    数据结构与算法专题:https://www.jianshu.com/nb/25128590

    喜欢请点赞,谢谢!

    相关文章

      网友评论

        本文标题:数据结构07-AVL树

        本文链接:https://www.haomeiwen.com/subject/pbxzrftx.html