平衡树之 AVL 树

二叉搜索树的局限

由于二叉树的结构特性,将数据存储到二叉搜索树中,其时间复杂度可以从存储在线性结构的的 O(N) 变成 O(log2 N) 。但这只是在理想的情况下的效率(如图1 左),在实际的操作,树的结构会不断的变换,极端的情况下,可以变为线性结构,时间复杂度近乎于 O(N)。 在数据量非常大情况下,查询速度会非常之低,这不是我们希望的结果。于是平衡树的概念被提出来了。

AVL_1.drawio

图1

什么是平衡树

平衡树(Balanced Tree)是一种特殊的二叉搜索树,它通过自动调整节点的插入和删除操作,以保持树的平衡性。在平衡树中,任何节点的左右子树的高度差不超过一个预定义常数(自平衡)。

平衡树的概念是由 G. M. Adelson-Velsky和Evgenii Landis 两位计算机科学家提出来的,并提供了平衡树的实现–AVL 树, 该树的名称就是由两位作者的姓氏命名的,他们在1962年的论文 An algorithm for the organization of information 中公开了这一数据结构。

当然,平衡树不止有 AVL 树一种,像后面出现的 2-3 树, 2-3-4 树, 红黑树等都是平衡树的实现。

AVL 树的特性

AVL树具有以下特性:

  1. 每个节点都存储一个关键字值。
  2. 对于任意节点,它的左子树和右子树都是AVL树。
  3. 对于任意节点,其左子树中的关键字值小于等于节点的关键字值,而其右子树中的关键字值大于等于节点的关键字值。
  4. 每个节点都有一个平衡因子(Balance Factor),它表示其左子树的高度减去右子树的高度。平衡因子可以是 -1、0 或 1。
  5. 对于AVL树中的每个节点,其平衡因子必须为 -1、0 或 1。如果一个节点的平衡因子不在这个范围内,那么它就不是AVL树,需要进行平衡操作以恢复平衡性。

由第 3 点看出 AVL 是一棵二叉搜索树,根据二叉搜索树的特性,AVL 树针对 4 种情况 提供了左旋右旋的操作,以保证树结构在插入和删除操作后,始终保持自平衡(第 4、5 点)。

因此,在最坏的情况下, AVL 树的查询、插入、删除操作的效率都为 O(log N)。

旋转 --AVL 树的核心操作

AVL 树在查询操作上与 一般二叉搜索树没有区别,而在插入和删除操作后,由于树的平衡性被打破,需要重新调整树的结构,以保证自平衡。

通过旋转调整平衡

如图2 所示,新增加一个键值为1的节点后, 节点6 的左子树的高度1由原来的 2 变成了 3 ,而其右子树的高度为 1。 左右子树的高度差 大于了 1, 不再符合一个 AVL 树的定义。 因此,需要在操作后,进行旋转。

在这个例子中,需要进行向右旋转,根节点由原来键值为6 的节点,变成了 键值为 4 的节点。

从 ① 到 ③ 的变换后,该树依旧为一颗二叉搜索树,这个性质非常的重要。

image-20230530151931270

图2

上面是 一个具体的例子,现在把旋转操作抽象出来,在插入和删除后,将会出现四种不平衡的类型。 我们分成四种情况进行讨论,分别为 左左、右右、 左右、右左。

左左 和 右右

出现这两种情况时(图3),比较容易处理,只需要进行一次旋转操作。

当增加、删除操作后,计算左右子树高度差,if(左子树的高度 - 右子树高度 > 1):
	if (child < left) :
		即为左左情况,需要进行右旋
		
当增加、删除操作后,计算左右子树高度差,if(左子树的高度 - 右子树高度 < -1):
	if (child > right) :
		即为右右情况,需要进行左旋

图3

左右 和 左右

出现这两种情况时(图4),需要进行两次旋转操作。

当增加、删除操作后,计算左右子树高度差,if(左子树的高度 - 右子树高度 > 1):
	if (child > left) :
		即为左左情况:
			① 对 left 子树进行左旋操作
			② 对 root 子树进行右旋操作 
		
当增加、删除操作后,计算左右子树高度差,if(左子树的高度 - 右子树高度 < -1):
	if (child < right) :
		即为右右情况:
			① 对 right 子树进行右旋操作
			② 对 root 子树进行左旋操作 

左右的情况比

image-20230530170207250

图4

可以看出,上面四种情况使用到了两种操作, 左旋右旋,下面是左旋和右旋的实现。

左旋实现

## 下面是左旋的伪代码实现 

AVLNode leftRoute(AVLNode root) {
	AVLNode newRoot = root.right; // right 节点作为最终返回的节点
    AVLNode subTree = newRoot.left; // 临时存储如下图的紫色部分,等下作为 root 的右子树
    root.right = subTree;
    newRoot.left = root;
    
    return newRoot;	// 将调整好的树重新返回
}

image-20230530171618522

右旋实现

## 下面是右旋的伪代码实现 
    
AVLNode rightRoute(AVLNode root) {
	AVLNode newRoot = root.left; // left 节点作为最终返回的节点
    AVLNode subTree = newRoot.right; // 临时存储如下图的紫色部分,等下作为 root 的左子树
    root.left = subTree;
    newRoot.right = root;
    
    return newRoot;	// 将调整好的树重新返回
}

image-20230530171455755

可看到左旋和右旋操作的代码量非常的简单,而调整左右,和右左两种情况,只需要两次调用上面的方法即可。

左右情况的实现

## 下面是左右情况的伪代码实现

AVLNode leftRight(AVLNode root) {
    leftRoute(root.left);
    rightRoute(root);
}

image-20230530173718975

右左情况的实现

## 下面是左右情况的伪代码实现

AVLNode rightLeft(AVLNode root) {
    rightRoute(root.right);
    leftRoute(root);
}

image-20230530173855583

以上就是 AVL 树的核心操作了,AVL 树实现二叉搜索树实现的区别就是,在增加和删除和结束后,需要进行判断高度差是否 > 1,如果 > 1 则需要根据上面四种情况进行旋转操作。

构建一颗 AVL 树

下面是 AVL 树的 Java 实现,在这里 key 为 int 类型,方便更专注与数据结构本身的学习。在实际的应用中,可根据需要将在节点中增加泛型,实现 Comparable 方法。

// AVLNode表示AVL树的节点
class AVLNode {
    int key;            // 节点的关键字值
    int height;         // 节点的高度
    AVLNode left;       // 左子节点
    AVLNode right;      // 右子节点

    public AVLNode(int key) {
        this.key = key;
        this.height = 1;
        this.left = null;
        this.right = null;
    }
}

// AVLTree表示AVL树
public class AVLTree {
    AVLNode root;       // 树的根节点

    // 获取节点的高度
    private int getHeight(AVLNode node) {
        if (node == null)
            return 0;
        return node.height;
    }

    // 获取节点的平衡因子
    private int getBalanceFactor(AVLNode node) {
        if (node == null)
            return 0;
        return getHeight(node.left) - getHeight(node.right);
    }

    // 更新节点的高度
    private void updateHeight(AVLNode node) {
        int leftHeight = getHeight(node.left);
        int rightHeight = getHeight(node.right);
        node.height = Math.max(leftHeight, rightHeight) + 1;
    }

    // 执行左旋操作
    private AVLNode leftRotate(AVLNode node) {
        AVLNode newRoot = node.right;
        AVLNode subtree = newRoot.left;

        newRoot.left = node;
        node.right = subtree;

        updateHeight(node);
        updateHeight(newRoot);

        return newRoot;
    }

    // 执行右旋操作
    private AVLNode rightRotate(AVLNode node) {
        AVLNode newRoot = node.left;
        AVLNode subtree = newRoot.right;

        newRoot.right = node;
        node.left = subtree;

        updateHeight(node);
        updateHeight(newRoot);

        return newRoot;
    }

    // 插入节点到AVL树中
    public void insert(int key) {
        root = insertNode(root, key);
    }

    private AVLNode insertNode(AVLNode node, int key) {
        if (node == null)
            return new AVLNode(key); 
        if (key < node.key) {
            node.left = insertNode(node.left, key);
        } else if (key > node.key) {
            node.right = insertNode(node.right, key);
        } else {
            return node;
        } // 忽略重复的关键字值

        updateHeight(node);

        int balanceFactor = getBalanceFactor(node);

        // 左左情况,执行右旋
        if (balanceFactor > 1 && key < node.left.key)
            return rightRotate(node);

        // 右右情况,执行左旋
        if (balanceFactor < -1 && key > node.right.key)
            return leftRotate(node);

        // 左右情况,先对左子树左旋,再对当前节点右旋
        if (balanceFactor > 1 && key > node.left.key) {
            node.left = leftRotate(node.left);
            return rightRotate(node);
        }

        // 右左情况,先对右子树右旋,再对当前节点左旋
        if (balanceFactor < -1 && key < node.right.key) {
            node.right = rightRotate(node.right);
            return leftRotate(node);
        }

        return node;
    }

    // 删除节点
    public void delete(int key) {
        root = deleteNode(root, key);
    }

    private AVLNode deleteNode(AVLNode node, int key) {
        if (node == null)
            return null;

        if (key < node.key)
            node.left = deleteNode(node.left, key);
        else if (key > node.key)
            node.right = deleteNode(node.right, key);
        else {
            // 找到要删除的节点

            if (node.left == null && node.right == null) {
                // 叶节点,直接删除
                node = null;
            } else if (node.left == null) {
                // 只有右子树,用右子树替换当前节点
                node = node.right;
            } else if (node.right == null) {
                // 只有左子树,用左子树替换当前节点
                node = node.left;
            } else {
                // 左右子树都存在,找到右子树中的最小节点
                AVLNode minNode = findMinNode(node.right);
                node.key = minNode.key;
                node.right = deleteNode(node.right, minNode.key);
            }
        }

        if (node == null)
            return null;

        updateHeight(node);

        int balanceFactor = getBalanceFactor(node);

        // 左左情况,执行右旋
        if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0)
            return rightRotate(node);

        // 左右情况,先对左子树左旋,再对当前节点右旋
        if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
            node.left = leftRotate(node.left);
            return rightRotate(node);
        }

        // 右右情况,执行左旋
        if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0)
            return leftRotate(node);

        // 右左情况,先对右子树右旋,再对当前节点左旋
        if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
            node.right = rightRotate(node.right);
            return leftRotate(node);
        }

        return node;
    }

    // 查找树中的最小节点
    private AVLNode findMinNode(AVLNode node) {
        AVLNode current = node;
        while (current.left != null) {
            current = current.left;
        }
        return current;
    }

    // 中序遍历AVL树
    public void inorderTraversal() {
        inorder(root);
    }

    private void inorder(AVLNode node) {
        if (node != null) {
            inorder(node.left);
            System.out.print(node.key + " ");
            inorder(node.right);
        }
    }
    // 示例用法 
    public static void main(String[] args) {
        AVLTree tree = new AVLTree();

        tree.insert(10);
        tree.insert(20);
        tree.insert(30);
        tree.insert(40);
        tree.insert(50);
        tree.insert(25);

        System.out.println("AVL树中序遍历结果:");
        tree.inorderTraversal();

        tree.delete(30);

        System.out.println("\n删除节点30后的AVL树中序遍历结果:");
        tree.inorderTraversal();
    }
}





  1. 二叉树的高度是指从根节点到最远叶子节点的路径上经过的节点数目。 ↩︎

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值