平衡二叉树——AVL树(Java实现)

平衡二叉树——AVL树

文章目录

    • 平衡二叉树——AVL树
      • 1、二叉搜索树的缺点
      • 2、AVL树的性质
      • 3、为何能保持平衡
        • 3.1、判断失衡的方式——平衡因子
        • 3.2、四种失衡的情况
        • 3.3、解决失衡的方法——旋转
        • 3.4、四种情况对应的旋转方式
      • 4、平衡二叉树实现
        • 4.1、节点结构
        • 4.2、AVL树完整代码
        • 4.3、测试

1、二叉搜索树的缺点

平衡二叉树是二叉搜索树的改进版,二叉搜索树用来快速的对数据进行查找,可以将原先O(n)的复杂度降低到O(logn),但这是在理想情况下的结果,若在原序列是有序的情况下,构建出的二叉搜索树就是线性的,与原先的线性表结构没有差异,时间复杂度会降低为O(n)。因此为避免这种情况下,需要对二叉树保持平衡

平衡二叉树——AVL树(Java实现)_第1张图片

​ (最糟糕的情况下的二叉搜索树)

平衡二叉树——AVL树(Java实现)_第2张图片

​ (理想状态下的二叉搜索树,左右子树高度相差不超过1)

AVL树是最早发明的一种平衡二叉树。AVL树得名于其发明者Adelson-Velsky与Evgenii Landis,其通过每个节点的左右子树的高度差来判断是否失衡,因此也被称为高度平衡树。要注意的是AVL树只是平衡二叉树的一种

2、AVL树的性质

  • AVL树是二叉搜索树,具有二叉搜索树的性质
  • AVL树的子树也是AVL树
  • AVL树的任一节点的两颗子树的最大高度差不超过1
  • AVL树插入、查找和删除在平均情况和最坏情况下时间复杂度都是O(logn)

3、为何能保持平衡

在最初遇到这个问题时,是不是有人与我一样一位需要用到一些精妙的数据结构,事实上并不是,平衡二叉树保持平衡的奥秘就在于在每次对节点进行插入和删除时会对失衡的节点进行调整。需要知道应当如何调整,首先需要知到如何判断失衡以及失衡的几种情况

3.1、判断失衡的方式——平衡因子

  • 平衡因子的定义:某节点的左子树与右子树的高度差即该节点的平衡因子

    在平衡二叉树中平衡因子应当只有-1、0、1三个值,其他情况则视为失衡,需要重新平衡。在插入或删除一个节点时,若二叉树中的每一个节点的平衡因子都为-1、0、1,那么该二叉树是平衡的,否则需要重新平衡

3.2、四种失衡的情况

二叉搜索树的失衡情况总结下来有四种:LL型、LR型、RL型与RR型,不同的情况适用不同的处理方法

  • LL型:失衡节点在基准节点左子树的左孩子上

平衡二叉树——AVL树(Java实现)_第3张图片

  • LR型:失衡节点在基准节点左子树的右孩子上

平衡二叉树——AVL树(Java实现)_第4张图片

  • RL型:失衡节点在基准节点右子树的左孩子上

平衡二叉树——AVL树(Java实现)_第5张图片

  • RR型:失衡节点在基准节点右子树的右孩子上

平衡二叉树——AVL树(Java实现)_第6张图片

3.3、解决失衡的方法——旋转

  • 左旋

平衡二叉树——AVL树(Java实现)_第7张图片

平衡二叉树——AVL树(Java实现)_第8张图片

  • 右旋

平衡二叉树——AVL树(Java实现)_第9张图片

平衡二叉树——AVL树(Java实现)_第10张图片

3.4、四种情况对应的旋转方式

上一节中的左旋与右旋显然分别对应着RR与LL的情况,因此LL仅需要进行一次右旋便能重新平衡,RR仅需要进行一次左旋就能重新平衡。而LR与RL的情况则较为复杂,一次旋转并不能完成重新平衡,需要进行两次旋转

LL 平衡二叉树——AVL树(Java实现)_第11张图片
LR 平衡二叉树——AVL树(Java实现)_第12张图片
RL 平衡二叉树——AVL树(Java实现)_第13张图片
RR 平衡二叉树——AVL树(Java实现)_第14张图片

4、平衡二叉树实现

4.1、节点结构

class Node {
    int val;
    Node left;
    Node right;
    int height;

    public Node(int val) {
        this.val = val;
        height = 1;
        left = null;
        right = null;
    }
}

4.2、AVL树完整代码

public class AVLTree {
    private Node rNode;

    /**
     * 获取节点的高度,若节点为空,返回0
     *
     * @param p 指定节点
     * @return 节点的高度
     */
    private int getHeight(Node p) {
        return p == null ? 0 : p.height;
    }

    /**
     * 右旋,适用于LL的情况
     *
     * @param base 旋转的基准节点(失衡的子树的根节点)
     * @return 返回旋转完成后新的根节点
     */
    public Node rightRotate(Node base) {
        Node left = base.left;
        base.left = left.right;
        left.right = base;
        base.height = Math.max(getHeight(base.left), getHeight(base.right)) + 1;
        left.height = Math.max(getHeight(left.left), base.height) + 1;
        return left;
    }

    /**
     * 右旋,适用于RR的情况
     *
     * @param base 旋转的基准节点(失衡的子树的根节点)
     * @return 返回旋转完成后新的根节点
     */
    public Node leftRotate(Node base) {
        Node right = base.right;
        base.right = right.left;
        right.left = base;
        base.height = Math.max(getHeight(base.left), getHeight(base.right)) + 1;
        right.height = Math.max(base.height, getHeight(right.right)) + 1;
        return right;
    }

    /**
     * 先左旋再右旋,适用于LR的情况
     *
     * @param base 旋转的基准节点(失衡的子树的根节点)
     * @return 返回旋转完成后新的根节点
     */
    public Node leftRightRotate(Node base) {
        base.left = leftRotate(base.left);
        return rightRotate(base);
    }

    /**
     * 先右旋再左旋,适用于RL的情况
     *
     * @param base 旋转的基准节点(失衡的子树的根节点)
     * @return 返回旋转完成后新的根节点
     */
    public Node rightLeftRotate(Node base) {
        base.right = rightRotate(base.right);
        return leftRotate(base);
    }

    /**
     * 插入新的值
     */
    public void add(int val) {
        rNode = add(rNode, val);
    }
    private Node add(Node root, int val) {
        if (root == null) {
            root = new Node(val);//添加节点
        } else {
            //进入左子树
            if (val < root.val) {
                //递归查找合适得到位置插入
                root.left = add(root.left, val);
                //插入结束后判断当前节点是否失衡
                if (getHeight(root.left) - getHeight(root.right) > 1) {
                    if (val < root.left.val) {
                        root = rightRotate(root);
                    }//LL的情况,右旋
                    else {
                        root = leftRightRotate(root);
                    }//LR的情况,先左旋再右旋
                }
            }
            //进入右子树
            else if (val > root.val) {
                //递归查找合适的位置插入
                root.right = add(root.right, val);
                //插入结束后判断当前节点是否失衡
                if (getHeight(root.left) - getHeight(root.right) < -1) {
                    if (val > root.right.val) {
                        root = leftRotate(root);
                    }//RR的情况,左旋
                    else {
                        root = rightLeftRotate(root);
                    }//RL的情况,先右旋再左旋
                }
            } else {
                System.out.println("插入的值已存在,请勿重复添加");
            }
        }
        //重新获得节点的高度
        root.height = Math.max(getHeight(root.left), getHeight(root.right)) + 1;
        return root;//返回插入后新的根节点
    }

    /**
     * 获取指定值的节点
     */
    public Node get(int val) {
        Node p = rNode;
        while (true) {
            if (p == null) {
                return null;
            } else {
                if (p.val == val) {
                    return p;
                } else if (val < p.val) {
                    p = p.left;
                } else {
                    p = p.right;
                }
            }
        }
    }

    /**
     * 删除指定节点
     */
    public void remove(int val) {
        rNode = remove(rNode, val);
    }
    private Node remove(Node root, int val) {
        if (root == null) {
            return null;
        }
        //当前节点即需要删除的节点
        if (val == root.val) {
            if (root.left != null && root.right != null) {
                if (root.left.height < root.right.height) {
                    int min = minimum(root.right);
                    root.val = min;
                    root.right = remove(root.right, min);
                } else {
                    int max = maximum(root.left);
                    root.val = max;
                    root.left = remove(root.left, max);
                }
            }else{
                Node tmp = root;
                root = root.left == null ? root.right : root.left;
                tmp = null;
            }
        }
        //需要删除的节点在左子树中
        else if (val < root.val) {
            root.left = remove(root.left, val);
            //删除了左子树的节点,因此需要检查右子树是否失衡
            if (getHeight(root.left) - getHeight(root.right) < -1) {
                //RR的情况,进行一次左旋
                if (getHeight(root.right.left) < getHeight(root.right.right)) {
                    root = leftRotate(root);
                }
                //RL的情况,先进行右旋再进行左旋
                else {
                    root = rightLeftRotate(root);
                }
            }
        }
        //需要删除的节点在右子树中
        else {
            root.right = remove(root.right, val);
            //删除了右子树的节点,因此需要检查左子树是否失衡
            if (getHeight(root.left) - getHeight(root.right) > 1) {
                //LL的情况,进行一次右旋
                if (getHeight(root.left.left) > getHeight(root.left.right)) {
                    root = rightRotate(root);
                }
                //LR的情况,先进行左旋再进行右旋
                else {
                    root = leftRightRotate(root);
                }
            }
        }
        return root;
    }

    /**
     * 获取左子树的最大值
     */
    private int maximum(Node root) {
        while (root.right != null) {
            root = root.right;
        }
        return root.val;
    }

    /**
     * 获取右子树的最小值
     */
    private int minimum(Node root) {
        while (root.left != null) {
            root = root.left;
        }
        return root.val;
    }

    /**
     * 中序遍历(按顺序输出所有值)
     */
    public void preOrder() {
        preOrder(rNode);
        System.out.println();
    }
    private void preOrder(Node root) {
        if (root == null) {
            return;
        }
        preOrder(root.left);
        System.out.print(root.val + " ");
        preOrder(root.right);
    }

    /**
     * 层序遍历
     */
    public void sequeueTrav() {
        Queue<Node> queue = new LinkedList<Node>();
        queue.offer(rNode);
        while (!queue.isEmpty()) {
            Node tmp = queue.poll();
            if (tmp == null) {
                System.out.print("null ");
            } else {
                System.out.print(tmp.val + " ");
                queue.offer(tmp.left);
                queue.offer(tmp.right);
            }
        }
        System.out.println();
    }
}

4.3、测试

public static void main(String[] args) {
    AVLTree tree = new AVLTree();
    int[] array = new int[]{2,1,0,3,4,5,6,16,15,14,13,12,11,10,8,9};
    for (int i:array){
        tree.add(i);
    }
    tree.preOrder();
    tree.sequeueTrav();
    tree.add(7);//插入7
    tree.preOrder();
    tree.sequeueTrav();
    tree.remove(6);//删除6
    tree.preOrder();
    tree.sequeueTrav();
}
  • 输出
0 1 2 3 4 5 6 8 9 10 11 12 13 14 15 16 
6 3 13 1 5 11 15 0 2 4 null 9 12 14 16 null null null null null null 8 10 null null null null null null null null null null 
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 
6 3 13 1 5 9 15 0 2 4 null 8 11 14 16 null null null null null null 7 null 10 12 null null null null null null null null null null 
0 1 2 3 4 5 7 8 9 10 11 12 13 14 15 16 
7 3 13 1 5 9 15 0 2 4 null 8 11 14 16 null null null null null null null null 10 12 null null null null null null null null 

最初建立好的AVL树如下:

平衡二叉树——AVL树(Java实现)_第15张图片

插入节点7后

平衡二叉树——AVL树(Java实现)_第16张图片

删除节点6后

平衡二叉树——AVL树(Java实现)_第17张图片

你可能感兴趣的