AVL树:自平衡二叉搜索树的奥秘

引言

在上一篇二叉搜索树(BST)的文章中,我们留下了一个关键问题:当数据以有序顺序插入时,BST 会退化成一个链表,查找效率从 O(log n) 跌落到 O(n)。

AVL 树 是历史上第一种自平衡二叉搜索树,由 Adelson-Velsky 和 Landis 于 1962 年提出(AVL 取自两人名字首字母)。它通过监控每个节点的平衡因子 ,在插入和删除后通过四种旋转操作 自动恢复平衡,确保树的高度始终保持在 O(log n),从而保证所有操作的时间复杂度稳定在 O(log n)。

第一部分:AVL 树的核心概念

一、平衡因子

平衡因子(Balance Factor,BF) 是 AVL 树最核心的概念:

复制代码
平衡因子 = 左子树高度 - 右子树高度

BF ∈ {-1, 0, 1}  →  平衡
BF < -1 或 BF > 1 →  失衡,需要旋转调整

二、高度计算

cpp 复制代码
// 节点的高度 = max(左子树高度, 右子树高度) + 1
// 空节点高度为 0

int height(AVLNode* node) {
    if (node == NULL) return 0;
    return node->height;
}

int balanceFactor(AVLNode* node) {
    if (node == NULL) return 0;
    return height(node->left) - height(node->right);
}

void updateHeight(AVLNode* node) {
    if (node == NULL) return;
    int hl = height(node->left);
    int hr = height(node->right);
    node->height = (hl > hr ? hl : hr) + 1;
}

三、节点结构

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef int ElemType;

typedef struct AVLNode {
    ElemType data;              // 数据域
    struct AVLNode* left;       // 左子节点
    struct AVLNode* right;      // 右子节点
    int height;                 // 节点高度(关键!)
} AVLNode;

第二部分:四种旋转操作

当插入或删除导致某节点的平衡因子绝对值 > 1 时,需要旋转恢复平衡。根据失衡的形态,分为四种情况:

一、四种失衡形态总览

二、LL 型 → 右旋

形态 :在节点 A 的左子树的左子树插入导致失衡。

右旋代码实现

cpp 复制代码
AVLNode* rotateRight(AVLNode* A) {
    AVLNode* B = A->left;         // ① B = A 的左子
    AVLNode* B_right = B->right;  // 保存 B 的右子树
    
    B->right = A;                  // ② B 的右子指向 A
    A->left = B_right;            // ③ A 的左子指向原来的 B_right
    
    // ④ 更新高度(必须先更新 A,再更新 B)
    updateHeight(A);
    updateHeight(B);
    
    return B;                      // ⑤ B 成为新的根
}

三、RR 型 → 左旋

形态 :在节点 A 的右子树的右子树插入导致失衡。与 LL 型镜像对称。

左旋代码实现

cpp 复制代码
AVLNode* rotateLeft(AVLNode* A) {
    AVLNode* C = A->right;
    AVLNode* C_left = C->left;
    
    C->left = A;
    A->right = C_left;
    
    updateHeight(A);
    updateHeight(C);
    
    return C;
}

四、LR 型 → 先左旋再右旋

形态 :在节点 A 的左子树的右子树插入导致失衡。需要先对左子左旋,再对根右旋。

LR 型旋转代码

cpp 复制代码
AVLNode* rotateLR(AVLNode* A) {
    A->left = rotateLeft(A->left);  // 先对左子节点左旋
    return rotateRight(A);           // 再对 A 右旋
}

五、RL 型 → 先右旋再左旋

形态 :在节点 A 的右子树的左子树插入导致失衡。与 LR 型镜像对称。

cpp 复制代码
AVLNode* rotateRL(AVLNode* A) {
    A->right = rotateRight(A->right);  // 先对右子节点右旋
    return rotateLeft(A);               // 再对 A 左旋
}

第三部分:插入操作

一、插入流程

二、完整插入代码

cpp 复制代码
AVLNode* createNode(ElemType val) {
    AVLNode* node = (AVLNode*)malloc(sizeof(AVLNode));
    if (node == NULL) return NULL;
    node->data = val;
    node->left = NULL;
    node->right = NULL;
    node->height = 1;  // 新节点高度为 1
    return node;
}

AVLNode* avlInsert(AVLNode* root, ElemType key) {
    // ===== 第一步:普通 BST 插入 =====
    if (root == NULL) {
        return createNode(key);
    }
    
    if (key < root->data) {
        root->left = avlInsert(root->left, key);
    } else if (key > root->data) {
        root->right = avlInsert(root->right, key);
    } else {
        return root;  // 不允许重复值,直接返回
    }
    
    // ===== 第二步:更新高度 =====
    updateHeight(root);
    
    // ===== 第三步:检查平衡并旋转 =====
    int bf = balanceFactor(root);
    
    // LL 型:左高 2,且新节点在左子树的左边
    if (bf > 1 && key < root->left->data) {
        return rotateRight(root);
    }
    
    // RR 型:右高 2,且新节点在右子树的右边
    if (bf < -1 && key > root->right->data) {
        return rotateLeft(root);
    }
    
    // LR 型:左高 2,且新节点在左子树的右边
    if (bf > 1 && key > root->left->data) {
        return rotateLR(root);
    }
    
    // RL 型:右高 2,且新节点在右子树的左边
    if (bf < -1 && key < root->right->data) {
        return rotateRL(root);
    }
    
    return root;  // 没有失衡,直接返回
}

三、插入过程图解

第四部分:删除操作

一、删除流程

二、完整删除代码

cpp 复制代码
// 找最小值节点
AVLNode* findMinNode(AVLNode* node) {
    while (node->left != NULL) node = node->left;
    return node;
}

AVLNode* avlDelete(AVLNode* root, ElemType key) {
    // ===== 第一步:普通 BST 删除 =====
    if (root == NULL) return NULL;
    
    if (key < root->data) {
        root->left = avlDelete(root->left, key);
    } else if (key > root->data) {
        root->right = avlDelete(root->right, key);
    } else {
        // 找到了要删除的节点
        if (root->left == NULL || root->right == NULL) {
            // 情况1&2:叶子或单子
            AVLNode* temp = (root->left != NULL) ? root->left : root->right;
            if (temp == NULL) {
                temp = root;
                root = NULL;
            } else {
                *root = *temp;  // 拷贝内容
            }
            free(temp);
        } else {
            // 情况3:双子 → 找后继替换
            AVLNode* successor = findMinNode(root->right);
            root->data = successor->data;
            root->right = avlDelete(root->right, successor->data);
        }
    }
    
    // 如果树被删空了
    if (root == NULL) return NULL;
    
    // ===== 第二步:更新高度 =====
    updateHeight(root);
    
    // ===== 第三步:检查平衡并旋转 =====
    int bf = balanceFactor(root);
    int bfLeft = balanceFactor(root->left);
    int bfRight = balanceFactor(root->right);
    
    // LL 型:左高2,且左子平衡或左倾
    if (bf > 1 && bfLeft >= 0) {
        return rotateRight(root);
    }
    
    // LR 型:左高2,且左子右倾
    if (bf > 1 && bfLeft < 0) {
        return rotateLR(root);
    }
    
    // RR 型:右高2,且右子平衡或右倾
    if (bf < -1 && bfRight <= 0) {
        return rotateLeft(root);
    }
    
    // RL 型:右高2,且右子左倾
    if (bf < -1 && bfRight > 0) {
        return rotateRL(root);
    }
    
    return root;
}

删除 vs 插入的失衡判断差异

操作 LL 判断 LR 判断
插入 key < root->left->data key > root->left->data
删除 bfLeft >= 0 bfLeft < 0

插入时新节点位置已知,可以直接用 key 判断。删除时,需要根据子节点的平衡因子来判断具体的失衡类型。


第五部分:完整代码与测试

一、完整 AVL 树实现

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>

typedef int ElemType;

typedef struct AVLNode {
    ElemType data;
    struct AVLNode* left;
    struct AVLNode* right;
    int height;
} AVLNode;

// ==================== 高度与平衡因子 ====================
int height(AVLNode* node) {
    return (node == NULL) ? 0 : node->height;
}

int balanceFactor(AVLNode* node) {
    if (node == NULL) return 0;
    return height(node->left) - height(node->right);
}

void updateHeight(AVLNode* node) {
    if (node == NULL) return;
    int hl = height(node->left);
    int hr = height(node->right);
    node->height = (hl > hr ? hl : hr) + 1;
}

// ==================== 创建节点 ====================
AVLNode* createNode(ElemType val) {
    AVLNode* node = (AVLNode*)malloc(sizeof(AVLNode));
    if (node == NULL) return NULL;
    node->data = val;
    node->left = node->right = NULL;
    node->height = 1;
    return node;
}

// ==================== 四种旋转 ====================
AVLNode* rotateRight(AVLNode* A) {
    AVLNode* B = A->left;
    AVLNode* B_right = B->right;
    
    B->right = A;
    A->left = B_right;
    
    updateHeight(A);
    updateHeight(B);
    return B;
}

AVLNode* rotateLeft(AVLNode* A) {
    AVLNode* C = A->right;
    AVLNode* C_left = C->left;
    
    C->left = A;
    A->right = C_left;
    
    updateHeight(A);
    updateHeight(C);
    return C;
}

AVLNode* rotateLR(AVLNode* A) {
    A->left = rotateLeft(A->left);
    return rotateRight(A);
}

AVLNode* rotateRL(AVLNode* A) {
    A->right = rotateRight(A->right);
    return rotateLeft(A);
}

// ==================== 插入 ====================
AVLNode* avlInsert(AVLNode* root, ElemType key) {
    if (root == NULL) return createNode(key);
    
    if (key < root->data)
        root->left = avlInsert(root->left, key);
    else if (key > root->data)
        root->right = avlInsert(root->right, key);
    else
        return root;
    
    updateHeight(root);
    
    int bf = balanceFactor(root);
    
    if (bf > 1 && key < root->left->data)        return rotateRight(root);   // LL
    if (bf < -1 && key > root->right->data)      return rotateLeft(root);    // RR
    if (bf > 1 && key > root->left->data)        return rotateLR(root);      // LR
    if (bf < -1 && key < root->right->data)      return rotateRL(root);      // RL
    
    return root;
}

// ==================== 删除 ====================
AVLNode* findMinNode(AVLNode* node) {
    while (node->left != NULL) node = node->left;
    return node;
}

AVLNode* avlDelete(AVLNode* root, ElemType key) {
    if (root == NULL) return NULL;
    
    if (key < root->data)
        root->left = avlDelete(root->left, key);
    else if (key > root->data)
        root->right = avlDelete(root->right, key);
    else {
        if (root->left == NULL || root->right == NULL) {
            AVLNode* temp = root->left ? root->left : root->right;
            if (temp == NULL) { temp = root; root = NULL; }
            else { *root = *temp; }
            free(temp);
        } else {
            AVLNode* successor = findMinNode(root->right);
            root->data = successor->data;
            root->right = avlDelete(root->right, successor->data);
        }
    }
    
    if (root == NULL) return NULL;
    
    updateHeight(root);
    int bf = balanceFactor(root);
    int bfLeft = balanceFactor(root->left);
    int bfRight = balanceFactor(root->right);
    
    if (bf > 1 && bfLeft >= 0)      return rotateRight(root);  // LL
    if (bf > 1 && bfLeft < 0)       return rotateLR(root);     // LR
    if (bf < -1 && bfRight <= 0)    return rotateLeft(root);   // RR
    if (bf < -1 && bfRight > 0)     return rotateRL(root);     // RL
    
    return root;
}

// ==================== 遍历 ====================
void inorder(AVLNode* root) {
    if (root == NULL) return;
    inorder(root->left);
    printf("%d ", root->data);
    inorder(root->right);
}

void preorder(AVLNode* root) {
    if (root == NULL) return;
    printf("%d ", root->data);
    preorder(root->left);
    preorder(root->right);
}

void freeTree(AVLNode* root) {
    if (root == NULL) return;
    freeTree(root->left);
    freeTree(root->right);
    free(root);
}

// ==================== 测试 ====================
int main() {
    AVLNode* root = NULL;
    
    // 有序插入(最考验平衡能力)
    printf("有序插入 1~7:\n");
    for (int i = 1; i <= 7; i++) {
        root = avlInsert(root, i);
        printf("插入 %d → 前序:", i);
        preorder(root);
        printf("  (高度=%d)\n", height(root));
    }
    
    printf("\n中序遍历:");
    inorder(root);
    printf("\n");
    
    // 删除测试
    printf("\n删除 4:\n");
    root = avlDelete(root, 4);
    printf("前序:"); preorder(root);
    printf("  (高度=%d)\n", height(root));
    
    printf("中序:"); inorder(root);
    printf("\n");
    
    // 重复插入测试
    printf("\n尝试插入重复值 3:\n");
    root = avlInsert(root, 3);
    printf("前序:"); preorder(root);
    printf("  (高度=%d,未变化)\n", height(root));
    
    freeTree(root);
    return 0;
}

运行结果

第六部分:性能分析

一、时间复杂度

操作 BST(最坏) AVL 树
查找 O(n) O(log n)
插入 O(n) O(log n)
删除 O(n) O(log n)

二、AVL vs 红黑树

对比项 AVL 树 红黑树
平衡条件 严格(|BF| ≤ 1) 宽松(颜色约束)
高度 更矮(查找更快) 稍高
插入旋转次数 最多 2 次 最多 2 次
删除旋转次数 可能 O(log n) 最多 3 次
适用场景 查询多的场景 增删多的场景
STL 容器 --- std::map, std::set

总结

一、AVL 树核心要点

要点 内容
平衡因子 BF = 左子树高 - 右子树高,BF ∈ {-1, 0, 1}
LL 型 左左失衡 → 右旋
RR 型 右右失衡 → 左旋
LR 型 左右失衡 → 先左旋再右旋
RL 型 右左失衡 → 先右旋再左旋
插入 BST 插入 + 回溯更新高度 + 最多 2 次旋转
删除 BST 删除 + 回溯更新高度 + 可能多次旋转

二、四种旋转记忆口诀

LL 单右旋,RR 单左旋

LR 左下后右上,RL 右下后左上

失衡看两边,哪边低往哪边转

三、一句话记忆

AVL 树通过平衡因子(左高-右高)监控平衡,一旦 BF 超出 [-1,1] 就通过四种旋转恢复:LL 右旋、RR 左旋、LR 先左后右、RL 先右后左,保证树高始终为 O(log n),从而查找、插入、删除均为 O(log n)。

相关推荐
玛卡巴卡ldf5 小时前
【LeetCode 手撕算法】(多维动态规划)不同路径、最小路径和、最长回文子串、最长公共子序列、编辑距离
java·数据结构·算法·leetcode·动态规划·力扣
被AI抢饭碗的人5 小时前
算法:数据结构
数据结构·算法
淞綰6 小时前
c语言的练习-字符串的练习-寻找最长连续字符以及出现次数
c语言·数据结构·学习·算法·c语言的练习
qq_296553276 小时前
[特殊字符] 搜索插入位置:从O(n)到O(log n)的优雅进化
数据结构·算法·面试·分类·柔性数组
凯瑟琳.奥古斯特6 小时前
力扣3654:二维矩阵连续空位统计
数据结构·数据库·算法·职场和发展
故事和你917 小时前
洛谷-【图论2-2】最短路3
开发语言·数据结构·c++·算法·动态规划·图论
想带你从多云到转晴8 小时前
07、数据结构与算法---优先级队列(堆)与排序
java·数据结构·算法
会编程的吕洞宾8 小时前
跳表_Skip_List_的_凌云九阶阵__从概率平衡到_Redis
数据结构·redis·list
少司府9 小时前
C++基础入门:深挖list的那些事
开发语言·数据结构·c++·容器·list·类型转换·类和对象