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

AVL树是一种自平衡的二叉搜索树,通过平衡因子和旋转操作保持树的平衡,确保查找、插入和删除操作的时间复杂度在平均和最坏情况下均为O(logn)。本文详细介绍了AVL树的性质、失衡判断及四种旋转方式,并提供了完整的Java实现代码。
摘要由CSDN通过智能技术生成

平衡二叉树——AVL树

1、二叉搜索树的缺点

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

在这里插入图片描述

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

在这里插入图片描述

​ (理想状态下的二叉搜索树,左右子树高度相差不超过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型:失衡节点在基准节点左子树的左孩子上

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

在这里插入图片描述

  • 右旋

在这里插入图片描述

在这里插入图片描述

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

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

LL在这里插入图片描述
LR在这里插入图片描述
RL在这里插入图片描述
RR在这里插入图片描述

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树如下:

在这里插入图片描述

插入节点7后

在这里插入图片描述

删除节点6后

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值