平衡二叉树(AVL)分析与实现

一、平衡二叉树的简介

平衡二叉树的缩写为AVL树,它是一种高度平衡二叉搜索树,对于平衡二叉树来说,它满足两个基本约束:
(1)二叉搜索树的约束:对于任意一个节点,左子树的关键字都小于根节点,右子树的关键字都大于根节点
(2)平衡性约束:对于任意一个节点,左右孩子的高度之差的绝对值不超过1

1.1 相关概念

  1. 高度

    平衡二叉树的高度定义和普通树的高度定义相同:树的中某个节点高度定义为:从该节点出发,到其可达的最远的那个叶子节点的路径中的边数。空节点的高度定义为-1,叶子节点的高度定义为0

  2. 平衡因子

    所谓的平衡因子,就是某个结点左右子树的高度之差,在一棵正常的平衡二叉树中平衡因子只可能取值为:
    0:表示左右子树一样高
    1:表示左子树比右子树高1
    -1:表示左子树比右子树低1

1.2 平衡二叉树的示例:

在这里插入图片描述

从这个图中我们可以清楚的观察到一个事实,那就是对于任意一个节点来说,它的高度只取决于它的左右孩子的高度值中最大的那一个

二、AVL树的插入和删除算法

几乎对任意一个数据结构,我们都会着重讨论该数据结构的一些基本操作,而AVL树也不例外,AVL树的基本操作和BST(二叉搜索树)是一致的,都包括插入、删除、查找等基本操作,唯一不同的地方在于AVL树相较于BST多了平衡性这个限制,正是因为这个限制,我们会发现AVL树的插入和删除算法相较于BST来说,更为复杂,因为删除和插入,可能导致平衡性被破坏

2.1 关于插入算法对平衡性破坏的讨论

2.1.1 左左插入

在插入关键字2之前,结点5的左孩子高度为1,右孩子高度为0,插入2之后,节点5的左孩子高度为2,右孩子高度为0,此时左右孩子高度之差为2,满足失衡条件,因此结点5失衡

在这里插入图片描述

2.1.2 左右插入

在插入15之前,结点18的左孩子高度是1,右孩子高度是0,左右孩子高度之差绝对值为1,没有失衡,而插入结点15之后,结点18的左孩子高度为2,右孩子高度是0,高度之差的绝对值为2,因此结点18失衡

在这里插入图片描述
左孩子插入情况总结:

在左孩子插入情况中,如果某个结点的左孩子的高度比右孩子高度大1,那么在以该结点为根的avl树的左孩子插入一个新的关键字,就一定会导致该根节点失衡,其他情况,一定不会导致其失衡。
其他情况有:
H[L] = H[R]
H[L] = H[R] - 1
其中L和R是某结点的左右子树,H表示他们的高度

2.1.3 右右插入

分析同左左插入,这两种情况是镜像对称的
在这里插入图片描述

2.1.4 右左插入

分析同左右插入,这两种情况是镜像对称的

在这里插入图片描述

2.2 关于删除算法对平衡性破坏的讨论

AVL树的删除算法和BST的删除算法的基本思想是一致的,如果要删除AVL树中的某个结点,如果这个结点有左右两个孩子,那么我们只需要找到该结点的右子树上的最小关键字结点T,用T替换该结点,然后删除T即可,对于只有一个孩子的结点以及叶子结点,只需要调整该结点的父亲结点的孩子指针的指向即可完成删除。但是AVL树的删除算法复杂就复杂在,删除一个结点可能导致该结点到根节点的路径上的某个结点失去平衡,我们需要对该结点进行的平衡性进行修复。
如下图所示:删除36后,结点18失衡

在这里插入图片描述

2.3 平衡性的修复

通过上面的分析,我们可以发现,由于平衡性的限制,导致我们在对avl树进行插入和删除的时候,都有可能造成某个结点的平衡性被破坏,因此需要对该结点进行平衡性的修复
具体的修复思想就是:
(1)如果是插入算法:
找到从新插入的结点到根的路径上第一个平衡性被破坏的结点,然后通过对该结点的适当旋转即可修复该结点的平衡性,并且第一个被破坏平衡性的结点的平衡性一旦被修复,整棵树的平衡性也就修复了(后面会解释为什么)
(2)如果是删除算法
从被删除结点的父亲结点出发,一直向根结点回溯,只要遇到平衡性被破坏的结点,就通过旋转对该节点的平衡性进行修复,一直到根节点,如果都没有找到失衡的结点,那么整棵树的平衡性就恢复了

2.3.1 单次旋转修复

单次旋转修复用来修复满足如下条件的失衡结点:
如果某个结点的平衡因子为2,其左子树的平衡因子为1,或者某个结点的平衡因子为-2,其右子树的平衡因子为-1
在这里插入图片描述

2.3.1.1 单次右旋修复

考虑某个一般的结构:其中k2是某棵avl树中的某个结点

现在我们有如下的前提:
如果我们在X中插入一个新的关键字,那么k2将会成为这棵平衡二叉树中从被插入关键字到根结点路径上第一个失衡的结点
同时我们假设结点X的原始高度是M(未插入新关键字之前),Y的高度是N,Z的高度是C,那么其他结点的高度将会是如图所示,为什么呢?
现在解释:
首先k1的高度取决于X和Y哪个更高,这里N不可能大于M,理由很简单,试想一下我们的前提条件是在X中插入一个新的关键字,才导致的k2失衡,也就是说,没插入新的关键字之前k2是平衡的,进一步说,就是因为X的高度增加了,才导致的k2失衡,如果N一开始就比M大,那么,我们不用插入新的关键字,k2也已经失衡了,所以N一定小于等于M,所以k1的高度是M+1
接着我们讨论k2的情况,因为我们是在k2的左子树的左孩子上插入一个新的结点导致的失衡,所以在插入之前一定是左子树的高度大于右子树,且高度差一定为1,也就是说M = C,k2的高度也就是M+2了

在这里插入图片描述

既然已经推断出了失衡之前每一个结点的高度,那么现在来看插入一个新关键字之后的情况

在这里插入图片描述

现在讨论单向右旋转操作
单向右旋是如何进行的,通过观察图可以很容易看出来,难点在于旋转过后某些结点的高度变化了,现在我们就是要说明,通过单向右旋调整,旋转后的子树高度和插入新关键字之前的高度是一样的,正是因为这种高度的恢复,才证明了通过旋转调整第一个失衡的结点,可以恢复整棵树的平衡
现在说明旋转后每一个结点的变化:
X和Y和Z的高度是不变的(当然X和插入关键字之前的高度肯定变化了,多了1)
从前面的讨论中可以得知,N不会大于M,所以N<=M,所以k2的高度一定为M+1,如此一来k1的高度必为M+2,这就说明旋转调整之后,以k1为根的子树的高度和插入关键字之前整棵子树的高度完全一致,整棵树的高度就像没有发生过变化一样,并且旋转之后的子树也是平衡的

在这里插入图片描述

2.3.1.2 单次左旋修复

略,分析同单次右旋,二者完全镜像对称

2.3.2 双旋转修复

双旋转修复用来修复满足如下条件的失衡结点:
如果某个结点的失衡因子为2,其左子树的平衡因子为-1,或者某个结点的平衡因子为-2,其右子树的平衡因子为1

考虑某个一般结构:其中k2是某棵avl树中的某个结点

现在我们有如下的前提:
如果我们在Y中插入一个新的关键字,那么k2将会成为这棵平衡二叉树中从被插入关键字到根结点路径上第一个失衡的结点
同时我们假设结点X的原始高度是M(未插入新关键字之前),Y的高度是N,Z的高度是C,那么其他结点的高度将会是如图所示,为什么呢?
现在解释:
首先k1的高度取决于X和Y哪个更高,这里M不可能大于N,理由很简单,试想一下我们的前提条件是在Y中插入一个新的关键字,才导致的k2失衡,也就是说,没插入新的关键字之前k2是平衡的,进一步说,就是因为Y的高度增加了,才导致的k2失衡,如果M一开始就比N大,那么,我们不用插入新的关键字,k2也已经失衡了,所以M一定小于等于N,所以k1的高度是N+1
接着我们讨论k2的情况,因为我们是在k2的左子树的左孩子上插入一个新的结点导致的失衡,所以在插入之前一定是左子树的高度大于右子树,且高度差一定为1,也就是说N = C,k2的高度也就是N+2了

在这里插入图片描述

既然已经推断出了失衡之前每一个结点的高度,那么现在来看插入一个新关键字之后的情况

在这里插入图片描述

现在讨论双旋转修复,这里讨论的是一次左旋一次右旋的情况
首先我们将结点Y分解为三部分,分别是根节点k3,左孩子和右孩子,然后我们将k1进行单次左旋,之后再将k3进行单次右旋,得到的子树结构如下图所示

在这里插入图片描述

现在分析最终子树的高度变化
首先X的高度M不会发生变化,因为M<=N(之前分析的),又因为在插入之前K1是平衡的,所以N-1<=M<=N,否则k1就将成为不平衡的,对于Z来说,其高度不变,对于A和B来说,因为Y的高度是N+1(插入新的关键字导致Y的高度增加了1),所以A和B至少有一个为N,另外一个为N-1(否则Y将会成为第一个不平衡的点),所以他俩的高度至少是N-1,因此对于k2来说,其高度为N+1,对于k1,g根据X和A的取值范围,我们可以很容易得到k1的取值范围,因此结合k1和k2,可以得出k3的高度为N=2,因此我们可以发现双旋转之后,整棵子树的高度和插入之前也保持了一致,并且整棵树旋转后依然是平衡的

2.3.3 旋转修复的正确性总结

通过对旋转操作中,子树中结点高度的变化分析,我们可以得出如下的结论,假设在插入新的关键字之前整棵子树的高度是H,插入之后子树根节点失衡,通过旋转调整之后,整棵子树的高度重新进行了恢复,和插入之前保持了一致,并且通过旋转,整棵子树重新平衡。正是因为这个性质,旋转才能对AVL树的平衡性的修复起到作用。

三、AVL树的基本实现(java版本)

/**
 * @author 西城风雨楼
 */
public class AvlTree<K extends Comparable<K>, V> {
    private TreeNode<K, V> root;

    /**
     * 插入一个新的关键字
     *
     * @param key   键
     * @param value 值
     */
    public void insert(K key, V value) {
        TreeNode<K, V> cur = root;
        TreeNode<K, V> parent = null;

        while (cur != null) {
            int cmp = cur.key.compareTo(key);
            if (cmp < 0) {
                parent = cur;
                cur = cur.right;
            } else if (cmp > 0) {
                parent = cur;
                cur = cur.left;
            } else {
                cur.value = value;
                return;
            }
        }
        // 默认创建出来的结点的parent是null,left是null,right是null
        TreeNode<K, V> newNode = new TreeNode<>(key, value, 0);
        // 这一步很重要,如果parent==null,那么newNode就是root结点
        newNode.parent = parent;
        if (parent == null) {
            root = newNode;
            return;
        }
        if (parent.key.compareTo(key) < 0) {
            parent.right = newNode;
        } else {
            parent.left = newNode;
        }
        // 这里需要进行修复
        fixBalance(newNode);
    }

    /**
     * 从某个结点出发向根节点回溯,返回这条路径上的第一个不平衡的结点
     *
     * @param node 返回第一个不平衡的结点
     * @return 如果一直回溯到根结点都没有发现不平衡的结点,那么返回null
     */
    private TreeNode<K, V> findFirstUnBalancedNode(TreeNode<K, V> node) {
        while (node != null) {
            updateHeight(node);
            if (unbalanced(node)) {
                break;
            }
            node = node.parent;
        }
        return node;
    }

    /**
     * 判断某个结点是否平衡
     *
     * @param node 待判断的结点
     * @return 如果平衡返回true,否则返回false
     */
    public boolean unbalanced(TreeNode<K, V> node) {
        if (node == null) {
            return true;
        }
        int factor = getBalanceFactor(node);
        return factor == 2 || factor == -2;
    }

    /**
     * 修复因为插入操作而导致的结点失衡
     *
     * @param newNode 被插入的结点
     */
    private void fixBalance(TreeNode<K, V> newNode) {
        // 找到第一个不平衡的结点
        TreeNode<K, V> firstUnBalanced = findFirstUnBalancedNode(newNode);
        // 判断当前结点失衡的原因是哪一种
        // 第一种是左左型,这种情况的特点是:当前结点的平衡因子是2,但是左孩子的平衡因子是1
        // 第二种是左右型,这种情况的特点是:当前结点的平衡因子是2,但是左孩子的平衡因子是-1
        // 第三种是右右型,这种情况的特点是:当前结点的平衡因子是-2,但是右孩子的平衡因子是-1
        // 第四种是右左型,这种情况的特点是:当前结点的平衡因子是-2,但是右孩子的平衡因子是1
        while (firstUnBalanced != null) {
            // 如果循环不结束,说明当前还有不平衡的结点
            int factor = getBalanceFactor(firstUnBalanced);
            if (factor == 2) {
                int factorOfLeft = getBalanceFactor(firstUnBalanced.left);
                if (factorOfLeft == 1) {
                    if (firstUnBalanced.parent == null) {
                        // 如果当前是根节点不平衡,那么需要更新根节点的引用
                        root = singleRightRotate(firstUnBalanced);
                    } else {
                        // 否则更新当前不平衡的结点的父亲结点的引用
                        firstUnBalanced.parent = singleRightRotate(firstUnBalanced);
                    }
                } else if (factorOfLeft == -1) {
                    if (firstUnBalanced.parent == null) {
                        root = doubleRightRotate(firstUnBalanced);
                    } else {
                        firstUnBalanced.parent = doubleLeftRotate(firstUnBalanced);
                    }
                } else {
                    throw new RuntimeException("不合法的左孩子平衡因子");
                }
            } else if (factor == -2) {
                int factorOfRight = getBalanceFactor(firstUnBalanced.right);
                if (factorOfRight == 1) {
                    if (firstUnBalanced.parent == null) {
                        root = doubleLeftRotate(firstUnBalanced);
                    } else {
                        firstUnBalanced.parent = doubleLeftRotate(firstUnBalanced);
                    }
                } else if (factorOfRight == -1) {
                    if (firstUnBalanced.parent == null) {
                        root = singleLeftRotate(firstUnBalanced);
                    } else {
                        firstUnBalanced.parent = singleLeftRotate(firstUnBalanced);
                    }
                } else {
                    throw new RuntimeException("不合法的右孩子平衡因子");
                }
            } else {
                throw new RuntimeException("不合法的非平衡结点的平衡因子");
            }
            firstUnBalanced = findFirstUnBalancedNode(firstUnBalanced);
        }
    }

    /**
     * 计算指定结点的平衡因子
     *
     * @param node 结点
     * @return 平衡因子
     */
    private int getBalanceFactor(TreeNode<K, V> node) {
        if (node == null) {
            return 0;
        }
        return getHeight(node.left) - getHeight(node.right);
    }

    /**
     * 单向右旋
     *
     * @param node 待旋转的结点
     * @return 返回旋转之后新的根节点
     */
    private TreeNode<K, V> singleRightRotate(TreeNode<K, V> node) {
        TreeNode<K, V> left = node.left;
        node.left = left.right;
        // 如果node的左子树的右孩子不为空
        if (left.right != null) {
            // 将其挂在node的左孩子下面
            left.right.parent = node;
        }
        // 将left挂到node的parent下面
        left.parent = node.parent;
        if (node.parent == null) {
            root = left;
        } else if (node == node.parent.left) {
            node.parent.left = left;
        } else {
            node.parent.right = left;
        }
        // 将node挂到left的右孩子下面
        left.right = node;
        node.parent = left;
        // 首先更新node的高度
        updateHeight(node);
        // 然后更新left的高度
        updateHeight(left);
        return left;
    }

    /**
     * 单向左旋
     *
     * @param node 待旋转的结点
     * @return 返回旋转之后的新的根节点
     */
    private TreeNode<K, V> singleLeftRotate(TreeNode<K, V> node) {
        TreeNode<K, V> right = node.right;
        node.right = right.left;
        if (right.left != null) {
            right.left.parent = node;
        }

        // 将right挂到node的parent下面
        right.parent = node.parent;
        if (node.parent == null) {
            root = right;
        } else if (node == node.parent.left) {
            node.parent.left = right;
        } else {
            node.parent.right = right;
        }
        // 把node挂到right的左孩子下面
        right.left = node;
        node.parent = right;
        updateHeight(node);
        updateHeight(right);
        return right;
    }

    /**
     * 双向左旋,所谓的双向左旋,是先做一次右旋,再左旋
     *
     * @param node 待双向左旋的结点
     * @return 返回旋转后新的根节点
     */
    private TreeNode<K, V> doubleLeftRotate(TreeNode<K, V> node) {
        // 先将node的右子树做右旋
        node.right = singleRightRotate(node.right);
        // 然后将node做左旋
        return singleLeftRotate(node);
    }

    /**
     * 双向右旋,所谓的双向右旋,是先做一次左旋,再右旋
     *
     * @param node 待双向右旋的结点
     * @return 返回旋转后新的根节点
     */
    private TreeNode<K, V> doubleRightRotate(TreeNode<K, V> node) {
        // 先将node左子树做左旋
        node.left = singleLeftRotate(node.left);
        // 再将node做右旋
        return singleRightRotate(node);
    }

    /**
     * 返回树结点的高度
     *
     * @param node 结点
     * @return 如果node为空结点,那么返回-1,否则返回其高度
     */
    private int getHeight(TreeNode<K, V> node) {
        return node == null ? -1 : node.height;
    }

    private void updateHeight(TreeNode<K, V> node) {
        node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
    }


    /**
     * 删除指定的键值对
     *
     * @param key 键
     * @return 返回删除的值
     */
    public V delete(K key) {
        TreeNode<K, V> cur = root;

        while (cur != null) {
            int cmp = cur.key.compareTo(key);
            if (cmp < 0) {
                cur = cur.right;
            } else if (cmp > 0) {
                cur = cur.left;
            } else {
                break;
            }
        }
        if (cur == null) {
            // 如果不存在对应的键
            return null;
        }
        // 如果存在对应的键,需要判断当前cur是哪一种情况
        // 第一种情况,cur没有孩子
        if (cur.left == null && cur.right == null) {
            if (cur.parent == null) {
                root = null;
                return cur.value;
            } else if (cur == cur.parent.left) {
                cur.parent.left = null;
            } else if (cur == cur.parent.right) {
                cur.parent.right = null;
            }
            fixBalance(cur.parent);
            cur.parent = null;
            return cur.value;
        }

        if (cur.left == null) {
            cur.right.parent = cur.parent;
            // 如果cur至少有一个孩子
            if (cur.parent == null) {
                root = cur.right;
                return cur.value;
            } else if (cur == cur.parent.left) {
                cur.parent.left = cur.right;
            } else {
                cur.parent.right = cur.right;
            }
            fixBalance(cur.parent);
            cur.parent = null;
            return cur.value;
        } else if (cur.right == null) {
            cur.left.parent = cur.parent;
            if (cur.parent == null) {
                root = cur.left;
                return cur.value;
            } else if (cur == cur.parent.left) {
                cur.parent.left = cur.left;
            } else {
                cur.parent.right = cur.left;
            }
            fixBalance(cur.parent);
            cur.parent = null;
            return cur.value;
        }

        // 剩下的就是cur有两个孩子
        TreeNode<K, V> min = findMin(cur.right);
        cur.key = min.key;
        V del = cur.value;
        cur.value = min.value;
        // 修改min.right的父指针
        if (min.right != null) {
            min.right.parent = min.parent;
        }
        if (min == min.parent.left) {
            min.parent.left = min.right;
        } else {
            min.parent.right = min.right;
        }
        fixBalance(min.parent);
        min.parent = null;
        return del;
    }

    private TreeNode<K, V> findMin(TreeNode<K, V> root) {
        while (root != null && root.left != null) {
            root = root.left;
        }
        return root;
    }

    /**
     * 先序遍历
     *
     * @return 返回先序序列
     */
    public String preOrder() {
        StringBuilder res = new StringBuilder();
        if (root == null) {
            return res.toString();
        }
        TreeNode<K, V> cur = root;
        while (cur != null) {
            if (cur.left != null) {
                TreeNode<K, V> mostRight = cur.left;
                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null) {
                    res.append(cur.value).append(" ");
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                } else {
                    mostRight.right = null;
                }
            } else {
                res.append(cur.value).append(" ");
            }
            cur = cur.right;
        }
        return res.toString();
    }

    /**
     * 中序序列
     *
     * @return 返回中序序列
     */
    public String inOrder() {
        StringBuilder res = new StringBuilder();
        if (root == null) {
            return res.toString();
        }

        TreeNode<K, V> cur = root;
        while (cur != null) {
            if (cur.left != null) {
                TreeNode<K, V> mostRight = cur.left;
                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null) {
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                } else {
                    res.append(cur.value).append(" ");
                    mostRight.right = null;
                }
            } else {
                res.append(cur.value).append(" ");
            }
            cur = cur.right;
        }
        return res.toString();
    }

    /**
     * avl树结点类型
     *
     * @param <K> 键类型
     * @param <V> 值类型
     */
    private static class TreeNode<K extends Comparable<K>, V> {
        public K key;
        public V value;
        public int height;
        public TreeNode<K, V> left;
        public TreeNode<K, V> right;
        public TreeNode<K, V> parent;

        public TreeNode(K key, V value, int height) {
            this.key = key;
            this.value = value;
            this.height = height;
        }
    }


    public static void main(String[] args) {
        AvlTree<Integer, Integer> avlTree = new AvlTree<>();
        avlTree.insert(1, 1);
        avlTree.insert(2, 2);
        avlTree.insert(3, 3);
        avlTree.insert(4, 4);
        avlTree.insert(5, 5);
        avlTree.insert(6, 6);
        avlTree.insert(7, 7);
        avlTree.insert(16, 16);
        avlTree.insert(15, 15);
        avlTree.insert(14, 14);
        avlTree.insert(13, 13);
        avlTree.insert(12, 12);
        avlTree.insert(11, 11);
        avlTree.insert(10, 10);
        avlTree.insert(9, 9);
        avlTree.insert(8, 8);
        System.out.println("先序:");
        System.out.println(avlTree.preOrder());
        System.out.println("中序:");
        System.out.println(avlTree.inOrder());
        System.out.println(avlTree.delete(1));
        System.out.println(avlTree.delete(3));
        System.out.println(avlTree.delete(2));
        System.out.println("after delete: ");
        System.out.println("先序:");
        System.out.println(avlTree.preOrder());
        System.out.println("中序:");
        System.out.println(avlTree.inOrder());
    }
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值