AVL树

文章目录

  • 一、AVL树
    • 1、AVL树本质上还是一棵二叉搜索树,有以下的特点:
    • 2、为了达到节点的平衡,AVL树引入了四种旋转操作:
  • 二、定义AVL树
  • 三、AVL树的四种旋转方式
    • 1、左旋
    • 2、右旋
    • 3、左平衡
    • 4、右平衡
  • 四、递归实现AVL树的增删操作
    • 1、插入
    • 2、删除

一、AVL树

  在计算机科学中,AVL树是最先发明的自平衡二叉搜索树,在AVL树中任何结点的两个子树的高度差不大于1,所以它也会被称为高度平衡树。增加和删除可能需要通过一次或多次树的旋转来重新平衡这个树。

1、AVL树本质上还是一棵二叉搜索树,有以下的特点:

  • 首先本身是一棵二叉搜索树;
  • 带有平衡条件:每个节点的左右子树的高度差不超过1;

也就是说,AVL树,本质上是带了平衡功能的二叉搜索树(AVL = BST + 平衡)

2、为了达到节点的平衡,AVL树引入了四种旋转操作:

  • 左孩子的左子树太高 ------------- 右旋
    AVL树_第1张图片
  • 右孩子的右子树太高 ------------- 左旋
    AVL树_第2张图片
  • 左孩子的右子树太高 ------------- 左平衡(先左旋、再右旋)

这里要注意的是,左旋操作之后,node.child这个结点会发生改变,代码中相应的要对node进行setLeft()操作来改变node的左孩子;
在这里插入图片描述

  • 右孩子的左子树太高 ------------- 右平衡(先右旋、再左旋)

同样的,这里也应该对node进行setRight()操作来改变node的右孩子;
在这里插入图片描述

二、定义AVL树

AVL树的数据结构:

  • height() 是为了拿到节点的高度,在node.getHeight()的基础上对node为null的情况做了处理;
  • maxHeight() 是比较node1和node2为根节点的两棵树的最大高度,方便拿到node1和node2最近公共祖先节点的高度;
class AVL<T extends Comparable<T>> {
    private AVLNode<T> root;

    public AVL() {
        this.root = null;
    }
    // 获取以node为根节点的树的高度
    private int height(AVLNode<T> node) {
        return node == null ? 0 : node.getHeight();
    }
    // 获取以node1h和node2为根节点的子树的最大高度值
    private int maxHeight(AVLNode<T> node1, AVLNode<T> node2) {
        return height(node1) > height(node2) ? height(node1) : height(node2);
    }

}
/**
 * @Author Daria
 * @Description AVL树的节点类型:在BST树的基础上新增了每个节点的高度属性
 * @Date 2019/7/11 -- 23:33
 */
class AVLNode<T extends Comparable<T>> {
    private T data;
    private AVLNode<T> left;
    private AVLNode<T> right;
    private int height; //结点的高度

    public AVLNode(T data, AVLNode<T> left, AVLNode<T> right, int height) {
        this.data = data;
        this.left = left;
        this.right = right;
        this.height = height;
    }
    public AVLNode(T data, int height) {
        this.data = data;
        this.left = null;
        this.right = null;
        this.height = height;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public AVLNode<T> getLeft() {
        return left;
    }

    public void setLeft(AVLNode<T> left) {
        this.left = left;
    }

    public AVLNode<T> getRight() {
        return right;
    }

    public void setRight(AVLNode<T> right) {
        this.right = right;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }
}

三、AVL树的四种旋转方式

需要AVL树进行旋转操作的情况,其简单图示和伪代码在上面有说到;

1、左旋

    // 左旋 --> 右孩子的右子树太高
    public AVLNode<T> leftRotate(AVLNode<T> node) {
        AVLNode<T> child = node.getRight(); //拿到node节点的右孩子,为child
        node.setRight(child.getLeft()); //将child的左孩子变成node的右孩子
        child.setLeft(node); //再将child的左孩子设为node
        node.setHeight(maxHeight(node.getLeft(), node.getRight()) + 1); //更新node的高度
        child.setHeight(maxHeight(child.getLeft(), child.getRight()) + 1); //更新child的高度
        return child;
    }

2、右旋

    // 右旋 --> 左孩子的左子树太高
    public AVLNode<T> rightRotate(AVLNode<T> node) {
        AVLNode<T> child = node.getLeft(); //拿到node节点的左孩子,记为child
        node.setLeft(child.getRight()); //将child的右孩子变成node的左孩子
        child.setRight(node); //将child的右孩子设为node
        node.setHeight(maxHeight(node.getLeft(), node.getRight()) + 1); //更新node的高度
        child.setHeight(maxHeight(child.getLeft(), child.getRight()) + 1); //更新child的高度
        return child;
    }

3、左平衡

    // 左平衡 --> 左孩子的右子树太高
    public AVLNode<T> leftBalance(AVLNode<T> node) {
        // 以node.left为根节点左旋
        node.setLeft(leftRotate(node.getLeft()));
        // 以node为根节点右旋
        return rightRotate(node);
    }

4、右平衡

    // 右平衡 --> 右孩子的左子树太高
    public AVLNode<T> rightBalance(AVLNode<T> node) {
        //以node.right为根节点右旋
        node.setRight(rightRotate(node.getRight()));
        //以node为根节点左旋
        return leftRotate(node);
    }

四、递归实现AVL树的增删操作

1、插入

    //递归实现AVL树的插入操作
    public void insert(T data) {
        this.root = insert(this.root, data);
    }
    //以root为起始节点,寻找合适的位置插入data,然后把子树的根节点返回
    private AVLNode<T> insert(AVLNode<T> root, T data) {
        if (root == null) { //创建根节点
            return new AVLNode<>(data, 1);
        }
        
        if (root.getData().compareTo(data) > 0) {
            root.setLeft(insert(root.getLeft(), data));
            //判断root结点是否失衡
            if (height(root.getLeft()) - height(root.getRight()) > 1) { //若在此处插入成功则root的左子树高度会发生改变,从而导致root节点失衡
                // 判断data是root左子树的左孩子还是右孩子
                if (height(root.getLeft().getLeft()) >= height(root.getLeft().getRight())) { //左孩子的左子树太高
                    root = rightRotate(root);//右旋
                } else { //左孩子的右子树太高,左平衡
                    root = leftBalance(root);
                }
            }
        } else if (root.getData().compareTo(data) < 0) {
            root.setRight(insert(root.getRight(), data));
            //判断root结点是否失衡
            if (height(root.getRight())- height(root.getLeft()) > 1) { //若在此处插入成功则root的右子树高度会发生改变,从而导致root节点失衡
                if (height(root.getRight().getRight()) >= height(root.getRight().getLeft())) { //右孩子的右子树太高
                    root = leftRotate(root);//左旋
                } else { //右孩子的左子树太高,右平衡
                    root = rightBalance(root);
                }
            }
        }
        //回溯过程中更新结点的高度值
        root.setHeight(maxHeight(root.getLeft(), root.getRight()) + 1);
        return root;
    }

2、删除

    //递归实现AVL树的删除操作
    public void remove(T data) {
        this.root = remove(this.root, data);
    }

    private AVLNode<T> remove(AVLNode<T> root, T data) {
        if (root == null) {
            return null;
        }
        if (root.getData().compareTo(data) > 0) {
            root.setLeft(remove(root.getLeft(), data));
            //若在此处删除成功,root的右子树与左子树的高度差可能会发生改变,导致root失衡
            if (height(root.getRight())- height(root.getLeft()) > 1) {
                if (height(root.getRight().getRight()) >= height(root.getRight().getLeft())) { //右孩子的右子树太高
                    root = leftRotate(root);//左旋
                } else { //右孩子的左子树太高,右平衡
                    root = rightBalance(root);
                }
            }
        } else if (root.getData().compareTo(data) < 0){
            root.setRight(remove(root.getRight(), data));
            //判断root结点是否失衡
            if (height(root.getLeft()) - height(root.getRight()) > 1) {
                if (height(root.getLeft().getLeft()) >= height(root.getLeft().getRight())) { //左孩子的左子树太高
                    root = rightRotate(root);//右旋
                } else { //左孩子的右子树太高,左平衡
                    root = leftBalance(root);
                }
            }
        } else { //root.getData().compareTo(data) == 0,root就是要删除的节点,在这里进行具体的删除操作
            if (root.getLeft() != null && root.getRight() != null) { //root的左右孩子都不为null
                //先判断root左子树和右子树的高度,若左子树高就用root前驱的值覆盖root,右子树高就用root后继的值覆盖root
                //这样可以防止删除带来的旋转操作,提高效率
                if (height(root.getLeft()) >= height(root.getRight())) { //左子树高,删除前驱
                    AVLNode<T> pre = root.getLeft();
                    while (pre.getRight() != null) {
                        pre = pre.getRight();
                    }
                    root.setData(pre.getData()); //用pre的值覆盖root的值
                    root.setLeft(remove(root.getLeft(), pre.getData())); //删除前驱pre

                } else { //右子树高就删除后继
                    AVLNode<T> post = root.getRight();
                    while (post.getLeft() != null) {
                        post = post.getLeft();
                    }
                    root.setData(post.getData()); //用post的值覆盖root的值
                    root.setRight(remove(root.getRight(), post.getData()));
                }

            } else { //要删除的节点只有一个孩子,将其孩子返回给父节点,写入相应地址域
                if (root.getLeft() != null) {
                    return root.getLeft();
                } else if (root.getRight() != null) {
                    return root.getRight();
                } else {
                    return null; //root没有孩子节点
                }
            }
        }
        //回溯过程中更新结点的高度值
        root.setHeight(maxHeight(root.getLeft(), root.getRight()) + 1);
        return root;
    }

你可能感兴趣的:(Java数据结构)