Loading

32-平衡二叉树(AVL)

1. 引入

  • 给你一个数列 {1,2,3,4,5,6},要求创建一颗二叉排序树(BST)
  • 上述 BST 存在的问题分析
    • 左子树全部为空,从形式上看,更像一个单链表
    • 插入速度没有影响
    • 查询速度明显降低,不能发挥 BST 的优势 // 因为每次还需要比较左子树,其查询速度比单链表还慢
  • [解决方案] 平衡二叉树(AVL)

2. 平衡二叉树

  • 平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree), 可以保证查询效率较高。
  • 特点
    • 它是一棵空树或它的左右两个子树的高度差的绝对值不超过 1
    • 左右两个子树都是一棵平衡二叉树
    • 注意,平衡二叉树中结点与其左右子结点仍旧满足 [二叉排序树] 的关系
  • 平衡二叉树的常用实现方法
    • 红黑树
    • AVL
    • 替罪羊树
    • Treap
    • 伸展树
  • 举例说明

3. 思路分析

3.1 单旋转

  • 左旋转

  • 右旋转

3.2 双旋转

单旋转可能引发的问题

当符合旋转条件 右子树高度 - 左子树高度 > 1 时,要先做判断!若其中 {右子树的左子树} 高度 > {右子树的右子树} 高度,则要先对 [当前结点的右子结点] 进行“右旋转”,再对 [当前结点] 进行“左旋转”的操作。

当符合旋转条件 左子树高度 - 右子树高度 > 1 时,要先做判断!若其中 {左子树的右子树} 高度 > {左子树的左子树} 高度,则还要先对 [当前结点的左子结点] 进行“左旋转”,再对 [当前结点] 进行“右旋转”的操作。

4. 代码实现

public class AVLTreeDemo {
    public static void main(String[] args) {
        // int[] arr = {4, 3, 6, 5, 7, 8};   // 左旋转
        // int[] arr = {10, 12, 8, 9, 7, 6}; // 右旋转
        // int[] arr = {10, 11, 7, 6, 8, 9}; // 双旋转(先左再右)
        int[] arr = {10, 4, 17, 12, 18, 11}; // 双旋转(先右再左)
        AVLTree tree = new AVLTree();
        for(int i = 0; i < arr.length; i++)
            tree.add(new Node(arr[i]));
        tree.infixOrder();
        System.out.printf("[AVL] 树的高度:%d, 左子树的高度:%d, 右子树的高度:%d\n"
            , tree.root.height(), tree.root.leftHeight(), tree.root.rightHeight());
        System.out.println("root: " + tree.root);
    }
}

class AVLTree  {
    public Node root;

    public void add(Node node) {
        if (root == null) root = node;
        else root.add(node);
    }

    public void infixOrder() {
        if (root != null) root.infixOrder();
        else System.out.println("空树");
        System.out.println();
    }
}

class Node {
    public int value; // 结点权值
    public Node left;
    public Node right;

    public Node(int value) {
        this.value = value;
    }

    // 返回以该结点为根结点的子树的高度
    public int height() {
        // + 1: Math.max统计的是子树高度, 根结点本身也算 1 层
        return Math.max(left == null ? 0 : left.height(),
                right == null ? 0 : right.height()) + 1;
    }

    // 返回左子树的高度
    public int leftHeight() {
        return left == null ? 0 : left.height();
    }

    // 返回右子树的高度
    public int rightHeight() {
        return right == null ? 0 : right.height();
    }

    public void leftRotate() {
        // 1. 创建 [新的结点], 其 value 为 [当前结点] 的value
        Node newNode = new Node(value);
        // 2. [新的结点] 的左子结点 = [当前结点] 的左子结点
        newNode.left = left;
        // 3. [新的结点] 的右子结点 = [当前结点] 的右子结点的左子结点
        newNode.right = right.left;
        // 4. [当前结点] 的 value = 其右子结点的value
        value = right.value;
        // 5. [当前结点] 的右子结点 替换为 其右子结点的右子结点
        right = right.right;
        // 6. [当前结点] 的左子结点 替换为 [新的结点]
        left = newNode;
    }

    public void rightRotate() {
        // 1. 创建 [新的结点], 其 value 为 [当前结点] 的value
        Node newNode = new Node(value);
        // 2. [新的结点] 的右子结点 = [当前结点] 的右子结点
        newNode.right = right;
        // 3. [新的结点] 的左子结点 = [当前结点] 的左子结点的右子结点
        newNode.left = left.right;
        // 4. [当前结点] 的 value = 其的左子结点的value
        value = left.value;
        // 5. [当前结点] 的左子结点 替换为 其左子结点的左子结点
        left = left.left;
        // 6. [当前结点] 的右子结点 替换为 [新的结点]
        right = newNode;
    }

    // (递归)添加结点
    public void add(Node node) {
        if (node == null) return;
        // 判断 node 的 value 和当前子树根结点的 value 的大小关系
        if (node.value < this.value) {
            if(this.left != null) this.left.add(node);
            else this.left = node;
        } else {
            if (this.right != null) this.right.add(node);
            else this.right = node;
        }

        // 当添加一个结点后,递归返回过程中都得查看当前树结构是否还能满足 AVL 树结构
        // a. 右子树 > 左子树 → 左旋转
        if (rightHeight() - leftHeight() > 1) {
            // 右子树的左子树 > 右子树的右子树
            if (right.leftHeight() > right.rightHeight())
                right.rightRotate(); // 得先来一次右旋转
            leftRotate();
            return;
        }

        // b. 左子树 > 右子树 → 右旋转
        if (leftHeight() - rightHeight() > 1) {
            // 左子树的右子树 > 左子树的左子树
            if (left.rightHeight() > left.leftHeight())
                left.leftRotate(); // 得先来一次左旋转
            rightRotate();
        }
    }

    // 中序遍历
    public void infixOrder() {
        if (this.left != null) this.left.infixOrder();
        System.out.print(this + " ");
        if (this.right != null) this.right.infixOrder();
    }

    @Override
    public String toString() {
        return "[value=" + value + "]";
    }
}

// 上述代码仅是在添加结点过程中实现平衡二叉树,删除结点后维系平衡二叉树的代码木有

posted @ 2020-02-18 21:04  tree6x7  阅读(105)  评论(0编辑  收藏  举报