平衡二叉树

一、相关概念

1.1 问题引出

  • 需求:给你一个数列{1,2,3,4,5,6},要求创建一棵二叉排序树BST)。

  • 存在问题分析

    • 左子树全部为空,从形式上看,更像一个单链表。
    • 插入速度没有影响。
    • 查询速度明显降低(因为需要依次比较), 不能发挥二叉排序树的优势,因为每次还需要比较左子树,其查询速度比单链表还慢。
  • 示意图

在这里插入图片描述

  • 解决方案:使用平衡二叉树AVL)。

1.2 基本介绍

  • 概念:平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为 AVL 树, 可以保证查询效率较高。

  • 特点:它是一棵空树或它的左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树

  • 平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。

  • 示意图

在这里插入图片描述

二、基本应用

2.1 左旋转

  • 需求:给你一个元素为{4,3,6,5,7,8}的数列,创建出对应的平衡二叉树。

  • 场景:假设我们有一棵二叉排序树(如下图),当你的右子树的高度比左子树高度要高的时候,我们需要通过左旋转的方式来降低右子树的高度,从而构建出平衡二叉树。

  • 示意图

在这里插入图片描述

  • 代码示例
    /**
     * 左旋转。
     */
    private void leftRotate() {

        // 1.以当前根节点的值,创建新的节点。
        Node newNode = new Node(this.getId());

        // 2.把新节点的左子树设置为当前节点的左子树。
        newNode.setLeft(this.getLeft());

        // 3.把新节点的右子树设置为当前节点的右子树的左子树。
        Node r = this.getRight();
        newNode.setRight(r.getLeft());

        // 4.把当前节点的值替换成右子节点的值。
        this.setId(r.getId());

        // 5.把当前节点的右子树设置成当前节点右子树的右子树。
        this.setRight(r.getRight());

        // 6.把当前节点的左子树设置成新的节点。
        this.setLeft(newNode);
    }

2.2 右旋转

  • 需求:给你一个元素为{10,12,8,9,7,6}的数列,创建出对应的平衡二叉树。

  • 与左旋转类似的,我们需要降低左子树高度时,就要进行右旋转操作。

  • 示意图

在这里插入图片描述

  • 代码示例
    /**
     * 右旋转。
     * 操作方式与左旋转相反。
     */
    private void rightRotate() {
        Node newNode = new Node(this.getId());
        newNode.setRight(this.getRight());
        Node l = this.getLeft();
        newNode.setLeft(l.getRight());
        this.setId(l.getId());
        this.setLeft(l.getLeft());
        this.setRight(newNode);
    }

2.3 双旋转

  • 前面的两个数列,进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但是在某些情况下单旋转不能完成平衡二叉树的转换
  • 单旋转问题分析-示意图

在这里插入图片描述

  • 双旋转-示意图

在这里插入图片描述

  • 代码示例
    /**
     * 双旋转。
     */
    private void doubleRotate() {
        // 当添加完一个节点后,如果: (右子树的高度-左子树的高度) > 1 , 进行左旋转。
        if ((this.getRightHeight() - this.getLeftHeight()) > 1) {
            // 如果它的右子树的左子树的高度,大于它的右子树的右子树的高度。
            Node r = this.getRight();
            if (null != r && r.getLeftHeight() > r.getRightHeight()) {
                // 先对右子节点进行右旋转。
                r.rightRotate();
                // 然后再对当前节点进行左旋转。
                this.leftRotate();
            } else {
                // 否则,直接进行左旋转即可。
                this.leftRotate();
            }
            // 操作完成,退出。
            return;
        }

        // 与上一个操作相反。
        if ((this.getLeftHeight() - this.getRightHeight()) > 1) {
            Node l = this.getLeft();
            if (null != l && l.getRightHeight() > l.getLeftHeight()) {
                l.leftRotate();
                this.rightRotate();
            } else {
                this.rightRotate();
            }
        }
    }

三、完整代码

public class AVLTreeDemo {

    public static void main(String[] args) {

        // 数列。
        int[] arr = {10, 11, 7, 6, 8, 9};

        // 测试一:不使用双旋转添加。
        AVLTree noUseDoubleRotateTree = new AVLTree();
        for (int i : arr) {
            noUseDoubleRotateTree.add(new Node(i), false);
        }
        printTreeInfo(noUseDoubleRotateTree);
        System.out.println();
        // 中序遍历,结果如下:
        // 6、7、8、9、10、11
        // 树的总高度=[4],其中左子树高度=[3],右子树高度=[1],当前根节点=Node:{id:10}。

        System.out.println("-----------------------------");

        // 测试二:使用双旋转添加。
        AVLTree useDoubleRotateTree = new AVLTree();
        for (int i : arr) {
            // 添加时,开启双旋转操作。
            useDoubleRotateTree.add(new Node(i), true);
        }
        printTreeInfo(useDoubleRotateTree);
        System.out.println();
        // 中序遍历,结果如下:
        // 6、7、8、9、10、11
        // 树的总高度=[3],其中左子树高度=[2],右子树高度=[2],当前根节点=Node:{id:8}。
    }

    /**
     * 遍历打印节点及树的高度信息。
     *
     * @param avlTree 平衡二叉树
     */
    public static void printTreeInfo(AVLTree avlTree) {
        System.out.println("中序遍历,结果如下:");
        avlTree.infixOrder();
        Node root = avlTree.getRoot();
        System.out.printf(
                "树的总高度=[%d],其中左子树高度=[%d],右子树高度=[%d],当前根节点=%s。",
                root.getHeight(),
                root.getLeftHeight(),
                root.getRightHeight(),
                root
        );
    }
}


/**
 * 平衡二叉树。
 */
class AVLTree {

    private Node root;

    public Node getRoot() {
        return root;
    }

    public void setRoot(Node root) {
        this.root = root;
    }

    public void add(Node node, boolean useDoubleRotate) {
        if (null == this.getRoot()) {
            this.setRoot(node);
        } else {
            this.getRoot().add(node, useDoubleRotate);
        }
    }

    public void infixOrder() {
        if (null == this.getRoot()) {
            System.out.println("binary tree is null.");
        } else {
            this.getRoot().infixOrder();
        }
    }
}


/**
 * 节点。
 */
class Node {
    private int id;
    private Node left;
    private Node right;

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Node getLeft() {
        return left;
    }

    public void setLeft(Node left) {
        this.left = left;
    }

    public Node getRight() {
        return right;
    }

    public void setRight(Node right) {
        this.right = right;
    }

    @Override
    public String toString() {
        return "Node:{id:" + id + "}";
    }

    /**
     * 双旋转。
     *
     * @param use 决定是否使用双旋转(该参数仅仅是为了对比效果)。
     */
    private void doubleRotate(boolean use) {
        if (use) {
            // 当添加完一个节点后,如果: (右子树的高度-左子树的高度) > 1 , 进行左旋转。
            if ((this.getRightHeight() - this.getLeftHeight()) > 1) {
                // 如果它的右子树的左子树的高度,大于它的右子树的右子树的高度。
                Node r = this.getRight();
                if (null != r && r.getLeftHeight() > r.getRightHeight()) {
                    // 先对右子节点进行右旋转。
                    r.rightRotate();
                    // 然后再对当前节点进行左旋转。
                    this.leftRotate();
                } else {
                    // 否则,直接进行左旋转即可。
                    this.leftRotate();
                }
                // 操作完成,退出。
                return;
            }

            // 与上一个操作相反。
            if ((this.getLeftHeight() - this.getRightHeight()) > 1) {
                Node l = this.getLeft();
                if (null != l && l.getRightHeight() > l.getLeftHeight()) {
                    l.leftRotate();
                    this.rightRotate();
                } else {
                    this.rightRotate();
                }
            }
        }
    }

    /**
     * 左旋转。
     */
    private void leftRotate() {

        // 1.以当前根节点的值,创建新的节点。
        Node newNode = new Node(this.getId());

        // 2.把新节点的左子树设置为当前节点的左子树。
        newNode.setLeft(this.getLeft());

        // 3.把新节点的右子树设置为当前节点的右子树的左子树。
        Node r = this.getRight();
        newNode.setRight(r.getLeft());

        // 4.把当前节点的值替换成右子节点的值。
        this.setId(r.getId());

        // 5.把当前节点的右子树设置成当前节点右子树的右子树。
        this.setRight(r.getRight());

        // 6.把当前节点的左子树设置成新的节点。
        this.setLeft(newNode);
    }

    /**
     * 右旋转。
     * 操作方式与左旋转相反。
     */
    private void rightRotate() {
        Node newNode = new Node(this.getId());
        newNode.setRight(this.getRight());
        Node l = this.getLeft();
        newNode.setLeft(l.getRight());
        this.setId(l.getId());
        this.setLeft(l.getLeft());
        this.setRight(newNode);
    }

    /**
     * 得到左子树高度。
     *
     * @return int
     */
    public int getLeftHeight() {
        if (null == this.getLeft()) {
            return 0;
        } else {
            return this.getLeft().getHeight();
        }
    }

    /**
     * 得到右子树高度。
     *
     * @return int
     */
    public int getRightHeight() {
        if (null == this.getRight()) {
            return 0;
        } else {
            return this.getRight().getHeight();
        }
    }

    /**
     * 返回以该节点为根的树的高度。
     *
     * @return int
     */
    public int getHeight() {

        int leftMaxHeight;
        int rightMaxHeight;

        if (null == this.getLeft()) {
            leftMaxHeight = 0;
        } else {
            // 左子树递归获取高度。
            leftMaxHeight = this.getLeft().getHeight();
        }

        if (null == this.getRight()) {
            rightMaxHeight = 0;
        } else {
            // 右子树递归获取高度。
            rightMaxHeight = this.getRight().getHeight();
        }

        // 比较最大高度。
        return Math.max(leftMaxHeight, rightMaxHeight) + 1;
    }

    /**
     * 添加节点。
     *
     * @param node            节点
     * @param useDoubleRotate 是否使用双旋转
     */
    public void add(Node node, boolean useDoubleRotate) {

        if (null == node) {
            return;
        }

        // 判断传入的节点的值,和当前子树的根节点的值关系。
        if (this.getId() > node.getId()) {
            if (null == this.getLeft()) {
                this.setLeft(node);
            } else {
                // 左子树递归添加。
                this.getLeft().add(node, useDoubleRotate);
            }
        } else {
            if (null == this.getRight()) {
                this.setRight(node);
            } else {
                // 右子树递归添加。
                this.getRight().add(node, useDoubleRotate);
            }
        }

        // 双旋转。
        this.doubleRotate(useDoubleRotate);
    }

    /**
     * 中序遍历。
     */
    public void infixOrder() {
        if (null != this.getLeft()) {
            this.getLeft().infixOrder();
        }
        System.out.println(this);
        if (null != this.getRight()) {
            this.getRight().infixOrder();
        }
    }
}

四、结束语


“-------怕什么真理无穷,进一寸有一寸的欢喜。”

微信公众号搜索:饺子泡牛奶

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值