安卓数据结构07-AVL树

数据结构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

喜欢请点赞,谢谢!

    原文作者:最爱的火
    原文地址: https://www.jianshu.com/p/7da021740dc1
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞