【数据结构与算法】第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树的删除操作。

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

相关推荐
wenha几秒前
数据库隔离级别
数据库·mysql·sqlserver·隔离级别
2401_871492857 分钟前
Layui如何修改Layui默认的UI主题颜色(换肤功能实现)
jvm·数据库·python
南子北游23 分钟前
Python学习(基础语法1)
开发语言·python·学习
张健115640964828 分钟前
使用信号量限制并发数量
开发语言·c++
Edward1111111135 分钟前
4.27mysql ,数据库,数据源
数据库·mysql
小徐敲java35 分钟前
踩坑实录:MySQL8.0 导入SQL报错 2006 - MySQL server has gone away 完美解决
数据库·sql
别来无恙blwy44 分钟前
windows MongoDB升级-自动升级脚本-自动检测升级到任意版本
数据库·windows·mongodb
步辞44 分钟前
Redis如何利用LFU算法优化缓存命中率
jvm·数据库·python
糯米团子7491 小时前
Web Worker
开发语言·前端·javascript
~小先生~1 小时前
sqlserver 外键、级联使用
数据库·sqlserver