【数据结构与算法】第28篇:平衡二叉树(AVL树)

一、AVL树的定义

1.1 平衡因子

平衡因子 = 左子树高度 - 右子树高度

AVL树要求所有节点的平衡因子只能是 -1、0、1。

text

复制代码
节点高度:从该节点到最远叶子节点的边数
空树高度:-1 或 0(不同定义,本文用-1)

1.2 为什么需要平衡

普通BST插入有序序列时会退化:

text

复制代码
插入:1,2,3,4,5
退化后:
1
 \
  2
   \
    3
     \
      4
       \
        5
查找效率 O(n)

AVL树通过旋转保持平衡,查找效率始终 O(log n)。


二、节点结构

c

复制代码
typedef struct AVLNode {
    int data;
    struct AVLNode *left;
    struct AVLNode *right;
    int height;  // 节点高度
} AVLNode, *AVLTree;

2.1 辅助函数

c

复制代码
// 获取节点高度
int getHeight(AVLNode *node) {
    return node == NULL ? -1 : node->height;
}

// 计算平衡因子
int getBalanceFactor(AVLNode *node) {
    if (node == NULL) return 0;
    return getHeight(node->left) - getHeight(node->right);
}

// 更新节点高度
void updateHeight(AVLNode *node) {
    if (node == NULL) return;
    int leftH = getHeight(node->left);
    int rightH = getHeight(node->right);
    node->height = (leftH > rightH ? leftH : rightH) + 1;
}

三、四种旋转调整

3.1 右旋(LL型)

触发条件:平衡因子 > 1 且 左子树的平衡因子 ≥ 0

text

复制代码
不平衡节点: A
左孩子: B

    A                  B
   / \                / \
  B   C    右旋→     D   A
 / \                    / \
D   E                  E   C

c

复制代码
AVLNode* rightRotate(AVLNode *y) {
    AVLNode *x = y->left;
    AVLNode *T2 = x->right;
    
    // 旋转
    x->right = y;
    y->left = T2;
    
    // 更新高度
    updateHeight(y);
    updateHeight(x);
    
    return x;
}

3.2 左旋(RR型)

触发条件:平衡因子 < -1 且 右子树的平衡因子 ≤ 0

text

复制代码
不平衡节点: A
右孩子: B

    A                      B
   / \                    / \
  C   B      左旋→       A   E
     / \                / \
    D   E              C   D

c

复制代码
AVLNode* leftRotate(AVLNode *y) {
    AVLNode *x = y->right;
    AVLNode *T2 = x->left;
    
    // 旋转
    x->left = y;
    y->right = T2;
    
    // 更新高度
    updateHeight(y);
    updateHeight(x);
    
    return x;
}

3.3 左右旋(LR型)

触发条件:平衡因子 > 1 且 左子树的平衡因子 < 0

text

复制代码
步骤1:对左孩子左旋
步骤2:对当前节点右旋

     A              A              C
    / \            / \            / \
   B   C   左旋→  C   C   右旋→   B   A
  / \            /                / \   \
 D   E          B                D   E   C
               / \
              D   E

c

复制代码
AVLNode* leftRightRotate(AVLNode *node) {
    node->left = leftRotate(node->left);
    return rightRotate(node);
}

3.4 右左旋(RL型)

触发条件:平衡因子 < -1 且 右子树的平衡因子 > 0

text

复制代码
步骤1:对右孩子右旋
步骤2:对当前节点左旋

c

复制代码
AVLNode* rightLeftRotate(AVLNode *node) {
    node->right = rightRotate(node->right);
    return leftRotate(node);
}

四、插入操作

c

复制代码
AVLNode* insert(AVLNode *root, int value) {
    // 1. 普通BST插入
    if (root == NULL) {
        AVLNode *newNode = (AVLNode*)malloc(sizeof(AVLNode));
        newNode->data = value;
        newNode->left = NULL;
        newNode->right = NULL;
        newNode->height = 0;
        return newNode;
    }
    
    if (value < root->data) {
        root->left = insert(root->left, value);
    } else if (value > root->data) {
        root->right = insert(root->right, value);
    } else {
        return root;  // 重复值不插入
    }
    
    // 2. 更新高度
    updateHeight(root);
    
    // 3. 计算平衡因子,判断是否需要旋转
    int balance = getBalanceFactor(root);
    
    // 左子树过高
    if (balance > 1) {
        if (value < root->left->data) {
            // LL型
            return rightRotate(root);
        } else {
            // LR型
            return leftRightRotate(root);
        }
    }
    
    // 右子树过高
    if (balance < -1) {
        if (value > root->right->data) {
            // RR型
            return leftRotate(root);
        } else {
            // RL型
            return rightLeftRotate(root);
        }
    }
    
    return root;
}

五、完整代码演示

c

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

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

int getHeight(AVLNode *node) {
    return node == NULL ? -1 : node->height;
}

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

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

AVLNode* rightRotate(AVLNode *y) {
    AVLNode *x = y->left;
    AVLNode *T2 = x->right;
    
    x->right = y;
    y->left = T2;
    
    updateHeight(y);
    updateHeight(x);
    
    return x;
}

AVLNode* leftRotate(AVLNode *y) {
    AVLNode *x = y->right;
    AVLNode *T2 = x->left;
    
    x->left = y;
    y->right = T2;
    
    updateHeight(y);
    updateHeight(x);
    
    return x;
}

AVLNode* leftRightRotate(AVLNode *node) {
    node->left = leftRotate(node->left);
    return rightRotate(node);
}

AVLNode* rightLeftRotate(AVLNode *node) {
    node->right = rightRotate(node->right);
    return leftRotate(node);
}

AVLNode* insert(AVLNode *root, int value) {
    if (root == NULL) {
        AVLNode *newNode = (AVLNode*)malloc(sizeof(AVLNode));
        newNode->data = value;
        newNode->left = NULL;
        newNode->right = NULL;
        newNode->height = 0;
        return newNode;
    }
    
    if (value < root->data) {
        root->left = insert(root->left, value);
    } else if (value > root->data) {
        root->right = insert(root->right, value);
    } else {
        return root;
    }
    
    updateHeight(root);
    
    int balance = getBalanceFactor(root);
    
    // LL
    if (balance > 1 && value < root->left->data) {
        return rightRotate(root);
    }
    // RR
    if (balance < -1 && value > root->right->data) {
        return leftRotate(root);
    }
    // LR
    if (balance > 1 && value > root->left->data) {
        return leftRightRotate(root);
    }
    // RL
    if (balance < -1 && value < root->right->data) {
        return rightLeftRotate(root);
    }
    
    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 printTree(AVLNode *root, int level) {
    if (root == NULL) return;
    printTree(root->right, level + 1);
    for (int i = 0; i < level; i++) printf("    ");
    printf("%d(h=%d,bf=%d)\n", root->data, root->height, getBalanceFactor(root));
    printTree(root->left, level + 1);
}

int main() {
    AVLNode *root = NULL;
    
    printf("=== 插入序列: 10, 20, 30, 40, 50, 25 ===\n");
    int values[] = {10, 20, 30, 40, 50, 25};
    
    for (int i = 0; i < 6; i++) {
        root = insert(root, values[i]);
        printf("\n插入 %d 后:\n", values[i]);
        printf("中序遍历: ");
        inorder(root);
        printf("\n");
        printf("树结构:\n");
        printTree(root, 0);
    }
    
    return 0;
}

运行结果:

text

复制代码
=== 插入序列: 10, 20, 30, 40, 50, 25 ===

插入 10 后:
中序遍历: 10 
树结构:
10(h=0,bf=0)

插入 20 后:
中序遍历: 10 20 
树结构:
    20(h=1,bf=-1)
10(h=0,bf=0)

插入 30 后:
中序遍历: 10 20 30 
树结构:
    30(h=1,bf=-1)
20(h=0,bf=0)
10(h=0,bf=0)

插入 40 后:
中序遍历: 10 20 30 40 
树结构:
        40(h=2,bf=-2)
    30(h=1,bf=-1)
        20(h=0,bf=0)
10(h=0,bf=0)

插入 50 后:
中序遍历: 10 20 30 40 50 
树结构:
        50(h=2,bf=-2)
    40(h=1,bf=-1)
        30(h=0,bf=0)
20(h=0,bf=0)
10(h=0,bf=0)

插入 25 后:
中序遍历: 10 20 25 30 40 50 
树结构:
        50(h=2,bf=-1)
    40(h=1,bf=1)
        30(h=0,bf=0)
            25(h=0,bf=0)
20(h=1,bf=0)
10(h=0,bf=0)

六、旋转判断总结

平衡因子 子树平衡因子 类型 操作
>1 ≥0 LL 右旋
>1 <0 LR 左右旋
<-1 ≤0 RR 左旋
<-1 >0 RL 右左旋

记忆口诀

  • LL:左边太重,右旋

  • RR:右边太重,左旋

  • LR:左子树的右边太重,先左旋左子,再右旋

  • RL:右子树的左边太重,先右旋右子,再左旋


七、复杂度分析

操作 时间复杂度 说明
查找 O(log n) 树高始终 log n
插入 O(log n) 查找位置 + 旋转(常数次)
删除 O(log n) 类似插入

AVL树的树高严格控制在 log n 级别,性能稳定。


八、AVL树 vs 普通BST

对比项 普通BST AVL树
最坏查找 O(n) O(log n)
插入复杂度 O(log n)平均 O(log n)
实现复杂度 简单 复杂(需要旋转)
额外开销 每个节点存储高度
适用场景 数据随机 数据有序或对性能要求高

九、小结

这一篇我们学习了AVL树:

要点 说明
平衡因子 左高-右高,取值-1,0,1
LL 右旋
RR 左旋
LR 左旋左子 + 右旋
RL 右旋右子 + 左旋
插入 BST插入 + 更新高度 + 旋转平衡

核心思想:在BST插入后,从插入点向上检查平衡因子,发现不平衡就旋转调整。

下一篇我们讲红黑树(原理与C语言模拟)。


十、思考题

  1. 为什么AVL树的高度平衡能保证查找效率O(log n)?

  2. 在LR旋转中,为什么需要先左旋再右旋?直接右旋行不行?

  3. 如果插入序列是 30,20,10,会触发哪种旋转?画图说明。

  4. 尝试实现AVL树的删除操作。

欢迎在评论区讨论你的答案。

相关推荐
测试_AI_一辰2 小时前
AI 如何参与 Playwright 自动化维护:一次自动修复闭环实践
人工智能·算法·ai·自动化·ai编程
未来之窗软件服务2 小时前
算法设计—计算机等级考试—软件设计师考前备忘录—东方仙盟
算法·软件设计师·计算机等级考试
未来之窗软件服务2 小时前
哈夫曼树构造—计算机等级考试—软件设计师考前备忘录—东方仙盟
算法·软件设计师·计算机等级考试·仙盟创梦ide·东方仙盟
csbysj20202 小时前
网站主机技术概述
开发语言
froginwe112 小时前
jQuery 事件方法详解
开发语言
SUNNY_SHUN3 小时前
VLM走进农田:AgriChat覆盖3000+作物品类,607K农业视觉问答基准开源
论文阅读·人工智能·算法·开源
黎阳之光3 小时前
视频孪生赋能车路云一体化,领跑智慧高速新征程
人工智能·算法·安全·数字孪生
echome8883 小时前
JavaScript Promise 与 async/await 实战:5 个高频异步编程场景的优雅解决方案
开发语言·javascript·ecmascript
道清茗3 小时前
【MySQL知识点问答题】高级复制技术
数据库·mysql