Java数据结构:AVL平衡二叉搜索树

一、什么是(AVL)平衡二叉搜索树?

平衡二叉搜索树在二叉树的概念基础上增加了平衡的概念,所谓平衡就是左右子树的高度差不能大于 1(小于或者等于1都可称作平衡的)。所以为了让二叉树达到平衡,又可以引入四种调节平衡的操作:左旋转、右旋转、左平衡、右平衡。

二、四种旋转的具体过程及如何实现?

1、左旋转:
当右孩子的右子树太高的时候,用左旋转进行调整,如图:
在这里插入图片描述
代码实现参考:

/**
 * 以参数node为根节点进行左旋操作,把旋转后的树的根节点返回
 */
private AVLNode<T> leftRotate(AVLNode<T> node){
    AVLNode<T> child = node.getRight();
    node.setRight(child.getLeft());
    child.setLeft(node);
    node.setHeight(maxHeight(node.getLeft(), node.getRight()) + 1);
    child.setHeight(maxHeight(child.getLeft(), child.getRight()) + 1);
    return child;
}

2、右旋转:
当左孩子的左子树太高的时候,用右旋转进行调整,过程如图:

在这里插入图片描述
代码实现参考:

/**
 * 以参数node为根节点进行右旋操作,把旋转后的树的根节点返回
 */
private AVLNode<T> rightRotate(AVLNode<T> node){
    AVLNode<T> child = node.getLeft();
    node.setLeft(child.getRight());
    child.setRight(node);
    node.setHeight(maxHeight(node.getLeft(), node.getRight()) + 1);
    child.setHeight(maxHeight(child.getLeft(), child.getRight()) + 1);
    return child;
}

3、左平衡:
当左孩子的右子树太高的时候,用左平衡调整,即先左旋再右旋:

在这里插入图片描述
代码实现参考:

/**
 * 以参数node为根节点进行左平衡操作,把旋转后的树的根节点返回
 */
private AVLNode<T> leftBalance(AVLNode<T> node){
    node.setLeft(leftRotate(node.getLeft()));
    return rightRotate(node);
}

4、右平衡:
当右孩子的左子树太高的时候,用右平衡操作进行调整,即先右旋再左旋:

在这里插入图片描述
代码实现参考:

/**
 * 以参数node为根节点进行右平衡操作,把旋转后的树的根节点返回
 */
private AVLNode<T> rightBalance(AVLNode<T> node){
    node.setRight(rightRotate(node.getRight()));
    return leftRotate(node);
}

三、用法:

在进行二叉搜索树的插入或者删除操作的时候,可能因为插入值或者删除某个节点元素二导致二叉搜索树失衡,这样就到了用上述四种调整的时候啦!
1、在插入元素的时候:

  • 从根节点root开始与待插入元素data比较,data>root向右子树寻找,data<root向左子树寻找,直到找到合适data值插入的位置;

  • 插入元素后比较左子树高度-右子树高度 是否大于1,大于1,左子树高进入下列情况1,右子树高进入下列情况2:

  • 情况1:
    若左孩子的左子树高于右子树==》用右旋转;
    若左孩子的右子树高于左子树==》用左平衡;

  • 情况2:
    若右孩子的右子树高于左子树==》用左旋转;
    若右孩子的左子树高于右子树==》用右平衡;

综合应用于插入删除操作,参考代码如下:

/**
 * 递归实现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, null, null, 1);
    }

    if(root.getData().compareTo(data) > 0){
        root.setLeft(insert(root.getLeft(), data));
        // 判断root节点是否失衡 #1
        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 if(root.getData().compareTo(data) < 0){
        root.setRight(insert(root.getRight(), data));
        // 判断root节点是否失衡 #2
        if(height(root.getRight()) - height(root.getLeft()) > 1){
            if(height(root.getRight().getRight())
                    >= height(root.getRight().getLeft())){
                // 右孩子的右子树太高
                root = leftRotate(root);
            } else {
                // 右孩子的左子树太高
                root = rightBalance(root);
            }
        }
    }

    // 递归回溯过程中,更新节点的高度值 #3
    root.setHeight(maxHeight(root.getLeft(), root.getRight()) + 1);
    return root;
}

/**
 * 实现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));
        // #1
        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){
        // #2
        root.setRight(remove(root.getRight(), data));
        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 {
        if(root.getLeft() != null && root.getRight() != null){
            // #3 左右子树哪个高,删除哪个,为了防止删除带来的旋转操作,提高效率
            if(height(root.getLeft()) >= height(root.getRight())){
                // 用前驱替换
                AVLNode<T> pre = root.getLeft();
                while(pre.getRight() != null){
                    pre = pre.getRight();
                }
                root.setData(pre.getData());
                root.setLeft(remove(root.getLeft(), pre.getData())); //删除前驱
            } else {
                // 用后继替换
                AVLNode<T> post = root.getRight();
                while(post.getLeft() != null){
                    post = post.getLeft();
                }
                root.setData(post.getData());
                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;
            }
        }
    }

    // 递归回溯过程中,更新节点的高度值 #4
    root.setHeight(maxHeight(root.getLeft(), root.getRight()) + 1);
    return root;
}

四、时间复杂度及优缺点?

AVL树的插入操作需要O(log n)的遍历时间和至多两次旋转;
AVL树的删除操作需要O(log n)的遍历时间的至多O(log 2 n)的旋转;
AVL的插入删除都是O(log 2 n)的时间复杂度。
缺点:
AVL树为了保持树的平衡,可能会进行很多次旋转操作,所以在数据量超大的时候,可能时间花费也会很大。但是注意,它不一定比后面所学的红黑树时间复杂度就大,不一定效率就不高,而是取决于具体数据具体情况。

发布了45 篇原创文章 · 获赞 11 · 访问量 4826

猜你喜欢

转载自blog.csdn.net/weixin_44187963/article/details/96887682
今日推荐