大话数据结构-查找-平衡二叉树

注:本文同步发布于稀土掘金。

6 平衡二叉树(AVL树)

  在进行二叉排序树的构造时,我们可能会因为根结点的选择,构造出不同的二叉树,如下所示,对于同一个数组,可以因根结点的不同,构造出的二叉树完全不一样:

image.png

  这两种二叉树的查找时间复杂度不同,左侧的时间复杂度为O(logn),右侧则为O(n),显然左侧二叉树的查找效率会更高。

  为了提高查找效率,我们提出了平衡二叉树:平衡二叉树,Self-Balancing Binary Search Tree或Height-Balanced Binary Search Tree,是一种特殊的二叉排序树,其中每一个结点的左子树和右子树的高度差至多等于1。

  二叉树上结点的左子树深度减去右子树深度的值被称为平衡因子BF(Balance Factor),而平衡二叉树上所有结点的平衡因子只可能是-1、0或1,否则该二叉查找树不能被称为平衡二叉树。

  距离插入结点最近的,且平衡因子绝对值大于1的结点为根的子树,我们称之为最小不平衡子树。如下图所示,当新插入结点37时,距离它最近的平衡因子绝对值超过1的结点是58,所以从58开始以下的子树为最小不平衡子树,如下图所示:

image.png

6.1 平衡二叉树实现原理

  平衡二叉树构建的基本思想,就是在构建二叉排序树的过程中,每当插入一个结点时,先检查是否因插入而破坏了树的平衡性,若是,则找出最小不平衡树,在保持二叉排序树特性的前提下,调整最小不平衡子树中各结点之间的链接关系,进行相应的旋转,使之成为新的平衡子树。

  根据平衡二叉树的特性,插入结点后,若树中出现BF大于1或者小于-1的结点时,需要对该结点进行旋转操作,如以下场景,为更清晰地理解,我们在每个结点下面添加三个框,从左至右分别是左子树深度、BF以及右子树深度:

  如何旋转呢?规则是:结点的BF大于1时,围绕左子树往右转;结点的BF小于-1时,围绕右子树往左转;转完后若仍出现BF大于1或者小于-1的结点时,继续转,直至平衡。

  我们分别来对以上四种场景进行旋转,首先是以下场景:

  结点3的BF大于1,于是围绕其左孩子右转,先将结点3的左子树深度改为0,BF同步调整(左子树深度减去右子树深度),同时将其转为左孩子的右子结点:

  然后计算新的父结点,即结点2的右子树深度,公式是Math.max(rightChild.leftDeep, rightChild.rightDeep) + 1,即结点2的右孩子的最高深度加1,此处是Math.max(0,0)+1=1,同步调整结点2的BF:

  此时所有结点的BF都在-1~1之间,已旋转完毕。

  若在进行右旋时,发现左孩子有右子结点怎么办?如下图:

  我们需要将结点4围绕结点2右旋,理论上应该旋转作为结点2的右子结点,但结点2已有右子结点。

  这时,我们比较结点4与结点2的右孩子,可以发现,结点4的值一定会大于结点2的右孩子(否则它就应该在结点4的右子树上了),于是我们直接将结点4作为结点2的右孩子的右孩子,同步修改涉及的三个结点的BF、左子树深度和右子树深度,如下图:

  这时发现结点2的BF小于-1,所以需要继续左转:

  涉及的结点BF都在-1~1之间,旋转结束。

  于是可以有以下结论:

  (1) 插入后,应检查插入结点、插入结点的父结点以及所有祖宗结点的BF;

  (2) 当出现结点的BF小于-1时,以该结点的右孩子为中心,进行左旋转;当出现结点的BF大于1时,以该结点的左孩子为中心,进行右旋转;

  (3) 向左旋转时,若右孩子已有左孩子,则被旋转的结点作为右孩子的左孩子的左孩子;向右旋转时,若左孩子已有右孩子,则被旋转的结点作为左孩子的右孩子的右孩子;

  (4) 被左旋的结点的右子树深度应重置为0;被右旋的结点的左子树深度应重置为0;

  (5) 所有结点的左子树深度,应为其左孩子、右孩子的左子树深度、右子树深度中的最大值加1;

  (6) 旋转后重新检查被旋转的结点的父结点以及祖父结点,若再次出现BF不在-1~1之间的结点时,按照旋转规则继续旋转;

  下面我们依据以上规则,将以下数组转化成平衡二叉树:

  将元素3置入树中,不需要任何处理;将元素2转入树中,按规则它应为结点3的左孩子:

  接下来插入元素1,它应该为结点2的子结点:

  这时发现,结点3的BF为2,于是将其右旋:

  然后插入元素4,所有结点的BF值均正常:

  然后插入元素5,插入后结点3的BF为-2,向左旋转:

  然后插入元素6,结点2的BF为-2,于是向左旋转:

  旋转后,结点3的BF为2,于是向右旋转,使得所有结点的BF在-1和1之间:

  继续插入结点7,发现结点5的BF为-2,于是将它向左旋转:

  继续插入结点10,所有结点的BF都正常:

  插入结点9,发现结点7、6、4的BF都为-2,从下往上开始处理,先将结点7向左旋转:

  旋转后结点10的BF变成了2,将其右转,旋转后所有结点的BF都正常:

  插入结点8,发现结点6和结点4的BF都是-2,先将结点6左转:

  发现结点9的BF为2,于是将其右转:

  然后发现结点8的BF为-2,于是将其左转,所有结点的BF都在-1至1之间,插入结束:

  结点定义代码实现如下所示:

import lombok.Data;

/**
 * Tree Node
 *
 * @author Korbin
 * @date 2023-04-23 11:35:08
 **/
@Data
public class TreeNode<T> {

    /**
     * balance factor
     **/
    private int balanceFactor;

    /**
     * value of node
     **/
    private T data;

    /**
     * left child
     **/
    private TreeNode<T> leftChild;

    /**
     * left deep
     **/
    private int leftDeep;

    /**
     * parent node
     **/
    private TreeNode<T> parent;

    /**
     * right child
     **/
    private TreeNode<T> rightChild;

    /**
     * right deep
     **/
    private int rightDeep;

    /**
     * get balance factor, it must be leftDeep - rightDeep
     **/
    public int getBalanceFactor() {
        return leftDeep - rightDeep;
    }

    @Override
    public String toString() {

        return "Data is " + data + ", parent is " + ((null != parent) ? parent.getData() : "null") +
                ", left child is " + ((null != leftChild) ? leftChild.getData() : "null") + ", right child is " +
                ((null != rightChild) ? rightChild.getData() : "null") + ", left deep is " + leftDeep +
                ", right deep is " + rightDeep + ", balance factor is " + getBalanceFactor();
    }

}

  完整的平衡二叉树生成代码如下所示:

/**
 * Binary Sort Tree
 *
 * @author Korbin
 * @date 2023-04-23 11:21:57
 **/
public class BinarySortTree<T extends Comparable<T>> {

    /**
     * root node
     **/
    private TreeNode<T> root;

    /**
     * 从插入的结点开始向上回溯到root,若碰到结点的BF小于-1则向左旋转,若有结点的BF大于1则向右旋转
     *
     * @param insertNode 插入的结点
     * @author Korbin
     * @date 2023-11-15 18:49:54
     **/
    public void checkAndRotate(TreeNode<T> insertNode) {

        System.out.println("check " + insertNode.getData());

        TreeNode<T> parent = insertNode.getParent();
        while (null != parent) {
            if (null != parent.getLeftChild() && parent.getLeftChild().getData().equals(insertNode.getData())) {
                parent.setLeftDeep(Math.max(insertNode.getLeftDeep(), insertNode.getRightDeep()) + 1);
            } else if (null != parent.getRightChild() &&
                    parent.getRightChild().getData().equals(insertNode.getData())) {
                parent.setRightDeep(Math.max(insertNode.getLeftDeep(), insertNode.getRightDeep()) + 1);
            }

            if (parent.getBalanceFactor() < -1) {
                System.out.println("将结点" + parent.getData() + "向左旋转");
                leftRotate(parent);
                checkAndRotate(parent);
            } else if (parent.getBalanceFactor() > 1) {
                System.out.println("将结点" + parent.getData() + "向右旋转");
                rightRotate(parent);
                checkAndRotate(parent);
            } else {
                insertNode = parent;
                parent = parent.getParent();
            }
        }
    }

    /**
     * create tree from array
     * <p>
     * the first element of data is the root node value
     *
     * @param data                            array
     * @param isSelfBalancingBinarySearchTree is a self balancing binary search tree
     * @author Korbin
     * @date 2023-04-23 11:38:06
     **/
    public void createTreeFromArray(T[] data, boolean isSelfBalancingBinarySearchTree) {

        // set the first element as the root node
        root = new TreeNode<>();
        root.setData(data[0]);
        root.setLeftDeep(0);
        root.setRightDeep(0);

        TreeNode<T> node = root;
        // iterate data
        for (int i = 1; i < data.length; i++) {
            TreeNode<T> newNode = new TreeNode<>();
            newNode.setData(data[i]);
            newNode.setLeftDeep(0);
            newNode.setRightDeep(0);

            while (null != node) {
                if (data[i].compareTo(node.getData()) <= 0) {
                    // the value of data[i] little than node data or equals to node data
                    TreeNode<T> leftNode = node.getLeftChild();
                    if (null == leftNode) {
                        // if node has no left child
                        // set data[i] as node's left child
                        node.setLeftChild(newNode);
                        newNode.setParent(node);
                        break;
                    } else {
                        // if node has left child
                        // set node as left child and continue
                        node = leftNode;
                    }
                } else {
                    // the value of data[i] greater than node data
                    TreeNode<T> rightNode = node.getRightChild();
                    if (null == rightNode) {
                        // if node has no right child
                        // set data[i] as node's right child
                        node.setRightChild(newNode);
                        newNode.setParent(node);
                        break;
                    } else {
                        // if node has right child
                        // set node as right child and continue
                        node = rightNode;
                    }
                }
            }

            if (isSelfBalancingBinarySearchTree) {
                // 插入后从插入的节点位置开始往上回溯,直到Root结点,判断是否存在不平衡结点
                checkAndRotate(newNode);
            }

            node = root;
        }

    }

    /**
     * find node who's value equals key
     *
     * @param key key to find
     * @return searched node or null
     * @author Korbin
     * @date 2023-04-23 14:36:49
     **/
    public TreeNode<T> findNode(T key) {
        return findNode(root, key);
    }

    /**
     * find node who's value equals key in tree
     *
     * @param tree root node of tree
     * @param key  key to find
     * @return searched node or null
     * @author Korbin
     * @date 2023-04-23 14:35:31
     **/
    private TreeNode<T> findNode(TreeNode<T> tree, T key) {

        if (null == tree) {
            return null;
        }

        if (key.equals(tree.getData())) {
            // found node
            return tree;
        }

        if (key.compareTo(tree.getData()) < 0) {
            // find from left tree
            return findNode(tree.getLeftChild(), key);
        } else {
            // find from right tree
            return findNode(tree.getRightChild(), key);
        }

    }

    /**
     * 向左旋转
     *
     * @param node 要被旋转的结点
     * @author Korbin
     * @date 2023-11-15 18:49:34
     **/
    public void leftRotate(TreeNode<T> node) {
        // 要左转的是bf小于-1的节点
        // 需要将其转到其右子节点下方,作为右子节点的左子节点
        // 存在两种情况:1 其右子节点无左子节点;2 其右子节点有左子节点;

        // 取出父节点
        TreeNode<T> parent = node.getParent();
        // 取出其右子节点
        TreeNode<T> rightChild = node.getRightChild();

        // 取出右子节点的左子节点
        TreeNode<T> rightChildLeftChild = rightChild.getLeftChild();

        // 如果右子节点没有左子节点,则直接转
        if (null == rightChildLeftChild) {
            // 当前节点的rightDeep重置为0
            node.setRightDeep(0);
            node.setRightChild(null);

            // 当前节点的父改为其右子节点
            node.setParent(rightChild);
            // 当前节点的右子节点作为新的父亲,先设置leftChild
            rightChild.setLeftChild(node);
            // 然后因为左边增加了节点,所以leftDeep要变化
            // 新的leftDeep应为新的左子节点,即node的最高深度,再加上左子节点这一深度
            rightChild.setLeftDeep(Math.max(node.getLeftDeep(), node.getRightDeep()) + 1);

            if (null != parent) {
                // rightChild的父节点发生变化
                rightChild.setParent(parent);

                // 先判断节点是左节点还是右节点
                if (null != parent.getLeftChild() && parent.getLeftChild().getData().equals(node.getData())) {
                    // 左节点
                    parent.setLeftChild(rightChild);
                    // 父亲的leftDeep,为其子的leftDeep和rightDeep中最大的值,加上子节点本身这个深度为1的节点
                    parent.setLeftDeep(Math.max(rightChild.getLeftDeep(), rightChild.getRightDeep()) + 1);
                } else {
                    // 右节点
                    parent.setRightChild(rightChild);
                    parent.setRightDeep(Math.max(rightChild.getLeftDeep(), rightChild.getRightDeep()) + 1);
                }

            } else {
                rightChild.setParent(null);
                root = rightChild;
            }

        } else {
            // 如果右子节点有左子节点,则该左子节点肯定大于当前节点
            // 设置当前节点的rightDeep为0
            node.setRightDeep(0);
            node.setRightChild(null);

            // 把当前节点转为“右孩子的左孩子“的子节点
            rightChildLeftChild.setLeftChild(node);
            node.setParent(rightChildLeftChild);

            // 设置“右孩子的左孩子“的rightDeep
            rightChildLeftChild.setLeftDeep(Math.max(node.getLeftDeep(), node.getRightDeep()) + 1);

            // 设置右孩子的leftDeep
            rightChild.setLeftDeep(Math.max(rightChildLeftChild.getLeftDeep(), rightChildLeftChild.getRightDeep()) + 1);

            // 设置parent
            if (null != parent) {
                rightChild.setParent(parent);
                if (null != parent.getLeftChild() && parent.getLeftChild().getData().equals(node.getData())) {
                    // 原来为左孩子
                    parent.setLeftChild(rightChild);
                    parent.setLeftDeep(Math.max(rightChild.getLeftDeep(), rightChild.getRightDeep()) + 1);
                } else {
                    // 原来为右孩子
                    parent.setRightChild(rightChild);
                    parent.setRightDeep(Math.max(rightChild.getLeftDeep(), rightChild.getRightDeep()) + 1);
                }

            } else {
                rightChild.setParent(null);
                root = rightChild;
            }

        }
    }

    /**
     * 向右旋转
     *
     * @param node 要被旋转的结点
     * @author Korbin
     * @date 2023-11-15 18:49:14
     **/
    public void rightRotate(TreeNode<T> node) {
        // 要右转的是bf大于1的节点
        // 需要将其转到其左子节点下方,作为左子节点的右子节点
        // 存在两种情况:1 其左子节点无右子节点;2 其左子节点有右子节点;

        // 取出父节点
        TreeNode<T> parent = node.getParent();
        // 取出其左子节点
        TreeNode<T> leftChild = node.getLeftChild();

        // 取出左子节点的右子节点
        TreeNode<T> leftChildRightChild = leftChild.getRightChild();

        // 如果左子节点没有右子节点,则直接转
        if (null == leftChildRightChild) {
            // 当前节点的leftDeep重置为0
            node.setLeftDeep(0);
            node.setLeftChild(null);

            // 当前节点的父改为其左子节点
            node.setParent(leftChild);

            // 当前节点的左子节点作为新的父亲,先设置rightChild
            leftChild.setRightChild(node);
            // 然后因为右边增加了节点,所以rightDeep要变化
            // 新的rightDeep应为新的右子节点,即node的最高深度,再加上右子节点这一深度
            leftChild.setRightDeep(Math.max(node.getLeftDeep(), node.getRightDeep()) + 1);

            if (null != parent) {
                // leftChild的父节点发生变化
                leftChild.setParent(parent);

                // 先判断节点是左节点还是右节点
                if (null != parent.getLeftChild() && parent.getLeftChild().getData().equals(node.getData())) {
                    // 左节点
                    parent.setLeftChild(leftChild);
                    // 父亲的leftDeep,为其子的leftDeep和rightDeep中最大的值,加上子节点本身这个深度为1的节点
                    parent.setLeftDeep(Math.max(leftChild.getLeftDeep(), leftChild.getRightDeep()) + 1);
                } else {
                    // 右节点
                    parent.setRightChild(leftChild);
                    parent.setRightDeep(Math.max(leftChild.getLeftDeep(), leftChild.getRightDeep()) + 1);
                }

            } else {
                leftChild.setParent(null);
                root = leftChild;
            }

        } else {
            // 如果左子节点有右子节点,则该右子节点肯定小于当前节点
            // 设置当前节点的leftDeep为0
            node.setLeftDeep(0);
            node.setLeftChild(null);

            // 把当前节点转为“左孩子的右孩子“的子节点
            leftChildRightChild.setRightChild(node);
            node.setParent(leftChildRightChild);

            // 设置“左孩子的右孩子“的rightDeep
            leftChildRightChild.setRightDeep(Math.max(node.getLeftDeep(), node.getRightDeep()) + 1);

            // 设置左孩子的rightDeep
            leftChild.setRightDeep(Math.max(leftChildRightChild.getLeftDeep(), leftChildRightChild.getRightDeep()) + 1);

            // 设置parent
            if (null != parent) {
                leftChild.setParent(parent);
                if (null != parent.getLeftChild() && parent.getLeftChild().getData().equals(node.getData())) {
                    // 原来为左孩子
                    parent.setLeftChild(leftChild);
                    parent.setLeftDeep(Math.max(leftChild.getLeftDeep(), leftChild.getRightDeep()) + 1);
                } else {
                    // 原来为右孩子
                    parent.setRightChild(leftChild);
                    parent.setRightDeep(Math.max(leftChild.getLeftDeep(), leftChild.getRightDeep()) + 1);
                }

            } else {
                leftChild.setParent(null);
                root = leftChild;
            }

        }

    }

}
  • 24
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值