平衡二叉树 (简单易懂)

简介: 平衡二叉树 (简单易懂)


一、概念

平衡二叉树(Balanced Binary Tree)是一种特殊的二叉搜索树(Binary Search Tree,BST),它在插入和删除节点时通过自平衡的方式来维持树的平衡性。平衡性的维护可以确保树的高度保持在较小的范围内,从而保证了树的基本操作(例如搜索、插入和删除)的平均时间复杂度为 O(log n)。

平衡二叉树的主要特点是任意节点的左子树和右子树的高度差(平衡因子)不超过1。换句话说,对于树中的每个节点,它的左子树和右子树的高度差的绝对值最多为1。

平衡二叉树的平衡性质可以通过旋转操作来维护。旋转操作包括左旋(Left Rotation)、右旋(Right Rotation)、左右旋(Left-Right Rotation)和右左旋(Right-Left Rotation)。这些操作可以使不平衡的树重新平衡,从而确保树的平衡性。

平衡二叉树的优点在于它提供了较快的搜索、插入和删除操作的平均性能,相比于非平衡的二叉搜索树

二、性质

  1. 二叉搜索树性质: 对于树中的每个节点,其左子树上的所有节点值都小于它,右子树上的所有节点值都大于它。
  2. 平衡性质: 对于树中的每个节点,它的左子树和右子树的高度差(平衡因子)最多为1。换句话说,任何节点的左右子树高度之差不超过1。

平衡二叉树的平衡性质确保了树的高度始终保持在较小的范围内,从而保持了较快的搜索、插入和删除操作。

三、插入操作

插入一个节点时,首先按照二叉搜索树的规则找到插入位置。然后,从插入位置向上回溯,更新每个祖先节点的高度,并检查平衡性质是否被破坏。如果发现某个节点的平衡因子超过1或小于-1,需要通过旋转操作来修复平衡性。

四、旋转操作

平衡二叉树的旋转操作有四种:左旋、右旋、左右旋、右左旋。这些旋转操作可以将不平衡的树重新平衡。下面简要介绍左旋和右旋:

  1. 左旋(LL旋转): 当一个节点的右子树比左子树高度高,且右子树的左子树高度不低于右子树的右子树时,进行左旋。左旋是通过将当前节点的右子树提升为新的根,并将原根作为新根的左子树来实现的。
  2. 右旋(RR旋转): 当一个节点的左子树比右子树高度高,且左子树的右子树高度不低于左子树的左子树时,进行右旋。右旋是通过将当前节点的左子树提升为新的根,并将原根作为新根的右子树来实现的。

五、删除操作

删除节点时,首先按照二叉搜索树的规则找到要删除的节点。删除节点后,从删除位置向上回溯,更新每个祖先节点的高度,并检查平衡性质是否被破坏。如果发现某个节点的平衡因子超过1或小于-1,同样需要通过旋转操作来修复平衡性。

六、代码实现

class TreeNode {
    int val;
    int height; // 节点的高度
    TreeNode left;
    TreeNode right;
    public TreeNode(int val) {
        this.val = val;
        this.height = 1;
        this.left = null;
        this.right = null;
    }
}
public class AVLTree {
    private TreeNode root;
    public AVLTree() {
        this.root = null;
    }
    // 获取节点高度
    private int height(TreeNode node) {
        return (node == null) ? 0 : node.height;
    }
    // 更新节点高度
    private void updateHeight(TreeNode node) {
        if (node != null) {
            node.height = 1 + Math.max(height(node.left), height(node.right));
        }
    }
    // 获取平衡因子
    private int getBalanceFactor(TreeNode node) {
        return (node == null) ? 0 : height(node.left) - height(node.right);
    }
    // 左旋
    private TreeNode leftRotate(TreeNode y) {
        TreeNode x = y.right;
        TreeNode T2 = x.left;
        // 执行旋转
        x.left = y;
        y.right = T2;
        // 更新高度
        updateHeight(y);
        updateHeight(x);
        return x;
    }
    // 右旋
    private TreeNode rightRotate(TreeNode x) {
        TreeNode y = x.left;
        TreeNode T2 = y.right;
        // 执行旋转
        y.right = x;
        x.left = T2;
        // 更新高度
        updateHeight(x);
        updateHeight(y);
        return y;
    }
    // 插入节点
    public TreeNode insert(TreeNode root, int val) {
        if (root == null) {
            return new TreeNode(val);
        }
        // 执行标准的BST插入
        if (val < root.val) {
            root.left = insert(root.left, val);
        } else if (val > root.val) {
            root.right = insert(root.right, val);
        } else {
            // 不允许插入相同的值
            return root;
        }
        // 更新节点高度
        updateHeight(root);
        // 获取平衡因子
        int balance = getBalanceFactor(root);
        // 平衡维护
        // 左子树高度大于右子树,且插入发生在左子树的左侧,需要进行右旋
        if (balance > 1 && val < root.left.val) {
            return rightRotate(root);
        }
        // 右子树高度大于左子树,且插入发生在右子树的右侧,需要进行左旋
        if (balance < -1 && val > root.right.val) {
            return leftRotate(root);
        }
        // 左子树高度大于右子树,且插入发生在左子树的右侧,需要先左旋再右旋
        if (balance > 1 && val > root.left.val) {
            root.left = leftRotate(root.left);
            return rightRotate(root);
        }
        // 右子树高度大于左子树,且插入发生在右子树的左侧,需要先右旋再左旋
        if (balance < -1 && val < root.right.val) {
            root.right = rightRotate(root.right);
            return leftRotate(root);
        }
        return root;
    }
    // 对外提供的插入接口
    public void insert(int val) {
        root = insert(root, val);
    }
    // 中序遍历
    private void inOrderTraversal(TreeNode root) {
        if (root != null) {
            inOrderTraversal(root.left);
            System.out.print(root.val + " ");
            inOrderTraversal(root.right);
        }
    }
    // 对外提供的中序遍历接口
    public void inOrderTraversal() {
        inOrderTraversal(root);
    }
    public static void main(String[] args) {
        AVLTree avlTree = new AVLTree();
        // 插入一些节点进行测试
        avlTree.insert(10);
        avlTree.insert(20);
        avlTree.insert(30);
        avlTree.insert(15);
        avlTree.insert(5);
        // 中序遍历,检查树的平衡性
        avlTree.inOrderTraversal();
    }
}

七、复杂度

平衡二叉树的高度始终保持在O(log n)范围内,因此搜索、插入和删除操作的平均时间复杂度为O(log n)。

总之,平衡二叉树通过维护平衡性质,提高了搜索、插入和删除操作的效率,确保树的高度保持在较小范围内。

相关文章
|
存储 算法 编译器
一篇文章教会你什么是二叉搜索树(上)
二叉搜索树概念 二叉搜索树(Binary Search Tree,BST)是一种二叉树的特殊形式,它具有以下关键性质:
|
7月前
|
搜索推荐 算法 大数据
​【数据结构与算法】冒泡排序:简单易懂的排序算法解析
​【数据结构与算法】冒泡排序:简单易懂的排序算法解析
|
搜索推荐
[数据结构 -- 手撕排序算法第七篇] 递归实现归并排序
[数据结构 -- 手撕排序算法第七篇] 递归实现归并排序
|
8月前
|
数据库 索引
【全网最易懂的红黑树讲解】一眼看懂二叉树、平衡树、红黑树,一文打尽
【全网最易懂的红黑树讲解】一眼看懂二叉树、平衡树、红黑树,一文打尽
|
存储 算法
数据结构练习题——树和二叉树(算法设计题)
以二叉链表作为二叉树的存储结构,编写以下算法: (1)统计二叉树的叶结点个数。 [题目分析]如果二叉树为空,返回0,如果二叉树不为空且左右子树为空,返回1,如果二叉树不为空,且左右子树不同时为空,返回左子树中叶子节点个数加上右子树中叶子节点个数。 [算法描述]
346 0
|
8月前
|
C++
【数据结构&C++】超详细一文带小白轻松全面理解 [ 二叉平衡搜索树-AVL树 ]—— [从零实现&逐过程分析&代码演示&简练易懂]
【数据结构&C++】超详细一文带小白轻松全面理解 [ 二叉平衡搜索树-AVL树 ]—— [从零实现&逐过程分析&代码演示&简练易懂]
【数据结构】论如何拿捏快速排序?(含非递归)
【数据结构】论如何拿捏快速排序?(含非递归)
53 0
|
算法 关系型数据库 MySQL
数据结构与算法——二叉树+带你实现表达式树(附源码)
📖作者介绍:22级树莓人(计算机专业),热爱编程<目前在c++阶段,因为最近参加新星计划算法赛道(白佬),所以加快了脚步,果然急迫感会增加动力>——目标Windows,MySQL,Qt,数据结构与算法,Linux,多线程,会持续分享学习成果和小项目的 📖作者主页:king&南星 📖专栏链接:数据结构 🎉欢迎各位→点赞👏 + 收藏💞 + 留言🔔​ 💬总结:希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🐾 ———————————————— 版权声明:本文为CSDN博主「热爱编程的小K」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
数据结构与算法——二叉树+带你实现表达式树(附源码)