AVL 树
最后更新
AVL 树是一种自平衡的二叉搜索树。它的用法和普通二叉搜索树(BST)一样,但每次插入后,它都会检查每个祖先节点的平衡因子,也就是左右子树的高度差。如果某个节点失去平衡(高度差大于 1),它就会通过旋转恢复平衡。点击上方的“播放”,观察数值依次插入,以及树如何通过旋转恢复原状。
因为 AVL 树从不允许自己明显倾斜,它能保证树高为 O(log n),所以查找、插入和删除始终是 O(log n),即使面对会让普通 BST 退化的有序输入也是如此。代价是每次插入时额外的旋转和高度维护。
时间和空间复杂度
| 操作 | 复杂度 | 说明 |
|---|---|---|
| 查找 | O(log n) | 树高始终约为 1.44 log n |
| 插入 | O(log n) | 外加 O(1) 次旋转 |
| 删除 | O(log n) | 外加 O(log n) 次旋转 |
| 空间 | O(n) | 每个节点一个高度字段 |
四种旋转情况
| 情况 | 失衡位置 | 修复方法 |
|---|---|---|
| LL 型(左左) | 左子节点的左侧过重 | 一次右旋 |
| RR 型(右右) | 右子节点的右侧过重 | 一次左旋 |
| LR 型(左右) | 左子节点的右侧过重 | 先左旋,再右旋 |
| RL 型(右左) | 右子节点的左侧过重 | 先右旋,再左旋 |
示例演算
逐个插入 [10, 20, 30, 40, 50, 25]:
| 步骤 | 结构 | 操作 |
|---|---|---|
插入 10 | 10 | 第一个节点成为根节点,树是平衡的 |
插入 20 | 10(_, 20) | 放在 10 的右边,仍然平衡 |
插入 30 | 20(10, 30) | 10 处出现 RR 型失衡,一次左旋把 20 提升为根节点 |
插入 40 | 20(10, 30(_, 40)) | 放在 30 的右边,所有平衡因子都在 ±1 以内 |
插入 50 | 20(10, 40(30, 50)) | 30 处出现 RR 型失衡,在 30 处左旋,把 40 提升上来 |
插入 25 | 30(20(10, 25), 40(_, 50)) | 20 处出现 RL 型失衡:先对 40 的子树右旋,再对 20 左旋 |
什么时候使用 AVL 树
| 适合使用的情况 | 不适合使用的情况 |
|---|---|
| 查找次数远多于插入,并且你希望树高尽可能低 | 插入和删除占多数,额外的旋转和再平衡开销比红黑树更大 |
即使面对恶意构造或有序的输入,也需要保证最坏情况 O(log n) | 普通哈希表就够用,你不需要有序遍历或范围查询 |
| 需要有序操作:中序遍历、前驱和后继、范围查询 | 数据量很小,普通 BST 或有序数组更简单,速度也够快 |
| 数据可能已经有序到达,这会让不平衡的 BST 退化成链表 | 内存非常紧张,承担不起每个节点额外的高度字段 |
AVL Tree 代码
简洁、可运行的 AVL Tree 实现,提供 Python, JavaScript, Java, C++, C 版本。选择一种语言,复制代码,或在 Coddy 在线编辑器中直接打开。
AVL Tree 的 Python 代码
1class Node:2 def __init__(self, key):3 self.key = key4 self.left = None5 self.right = None6 self.height = 17
8
9def height(node):10 return node.height if node else 011
12
13def update(node):14 node.height = 1 + max(height(node.left), height(node.right))15
16
17def rotate_right(y):18 x = y.left19 y.left = x.right20 x.right = y21 update(y)22 update(x)23 return x24
25
26def rotate_left(x):27 y = x.right28 x.right = y.left29 y.left = x30 update(x)31 update(y)32 return y33
34
35def insert(node, key):36 if node is None:37 return Node(key)38 if key < node.key:39 node.left = insert(node.left, key)40 else:41 node.right = insert(node.right, key)42 update(node)43 balance = height(node.left) - height(node.right)44 # Four imbalance cases: LL, RR, LR, RL45 if balance > 1 and key < node.left.key:46 return rotate_right(node)47 if balance < -1 and key > node.right.key:48 return rotate_left(node)49 if balance > 1:50 node.left = rotate_left(node.left)51 return rotate_right(node)52 if balance < -1:53 node.right = rotate_right(node.right)54 return rotate_left(node)55 return node56
57
58def inorder(node):59 if node is None:60 return []61 return inorder(node.left) + [node.key] + inorder(node.right)62
63
64root = None65for key in [10, 20, 30, 40, 50, 25]:66 root = insert(root, key)67
68print("Inorder:", inorder(root))69print("Root:", root.key, "| tree height:", root.height)AVL Tree 的 JavaScript 代码
1class Node {2 constructor(key) {3 this.key = key;4 this.left = null;5 this.right = null;6 this.height = 1;7 }8}9
10const height = (n) => (n ? n.height : 0);11const update = (n) => {12 n.height = 1 + Math.max(height(n.left), height(n.right));13};14const balance = (n) => height(n.left) - height(n.right);15
16function rotateRight(y) {17 const x = y.left;18 y.left = x.right;19 x.right = y;20 update(y);21 update(x);22 return x;23}24
25function rotateLeft(x) {26 const y = x.right;27 x.right = y.left;28 y.left = x;29 update(x);30 update(y);31 return y;32}33
34function insert(node, key) {35 if (!node) return new Node(key);36 if (key < node.key) node.left = insert(node.left, key);37 else node.right = insert(node.right, key);38 update(node);39 const b = balance(node);40 // Four rebalancing cases: LL, RR, LR, RL41 if (b > 1 && key < node.left.key) return rotateRight(node);42 if (b < -1 && key > node.right.key) return rotateLeft(node);43 if (b > 1) {44 node.left = rotateLeft(node.left);45 return rotateRight(node);46 }47 if (b < -1) {48 node.right = rotateRight(node.right);49 return rotateLeft(node);50 }51 return node;52}53
54function inorder(node, out = []) {55 if (!node) return out;56 inorder(node.left, out);57 out.push(node.key);58 inorder(node.right, out);59 return out;60}61
62let root = null;63for (const key of [10, 20, 30, 40, 50, 25]) root = insert(root, key);64console.log("Root after rebalancing:", root.key);65console.log("Inorder:", inorder(root).join(" "));AVL Tree 的 Java 代码
1public class Main {2 static class Node {3 int key, height = 1;4 Node left, right;5 Node(int key) { this.key = key; }6 }7
8 static int height(Node n) { return n == null ? 0 : n.height; }9 static int balance(Node n) { return n == null ? 0 : height(n.left) - height(n.right); }10 static void update(Node n) { n.height = 1 + Math.max(height(n.left), height(n.right)); }11
12 static Node rotateRight(Node y) {13 Node x = y.left;14 y.left = x.right;15 x.right = y;16 update(y); update(x);17 return x;18 }19
20 static Node rotateLeft(Node x) {21 Node y = x.right;22 x.right = y.left;23 y.left = x;24 update(x); update(y);25 return y;26 }27
28 static Node insert(Node node, int key) {29 if (node == null) return new Node(key);30 if (key < node.key) node.left = insert(node.left, key);31 else node.right = insert(node.right, key);32 update(node);33 int b = balance(node);34 // Four imbalance cases: LL, RR, LR, RL35 if (b > 1 && key < node.left.key) return rotateRight(node);36 if (b < -1 && key > node.right.key) return rotateLeft(node);37 if (b > 1) { node.left = rotateLeft(node.left); return rotateRight(node); }38 if (b < -1) { node.right = rotateRight(node.right); return rotateLeft(node); }39 return node;40 }41
42 static void inorder(Node n, StringBuilder sb) {43 if (n == null) return;44 inorder(n.left, sb);45 sb.append(n.key).append(" ");46 inorder(n.right, sb);47 }48
49 public static void main(String[] args) {50 Node root = null;51 int[] keys = {10, 20, 30, 40, 50, 25};52 for (int k : keys) root = insert(root, k);53 StringBuilder sb = new StringBuilder();54 inorder(root, sb);55 System.out.println("Inorder: " + sb.toString().trim());56 System.out.println("Root after rebalancing: " + root.key + " (height " + root.height + ")");57 }58}AVL Tree 的 C++ 代码
1#include <algorithm>2#include <iostream>3
4struct Node {5 int value;6 int height = 1;7 Node* left = nullptr;8 Node* right = nullptr;9 explicit Node(int v) : value(v) {}10};11
12int height(const Node* n) { return n ? n->height : 0; }13int balanceFactor(const Node* n) { return height(n->left) - height(n->right); }14void updateHeight(Node* n) {15 n->height = 1 + std::max(height(n->left), height(n->right));16}17
18Node* rotateRight(Node* y) {19 Node* x = y->left;20 y->left = x->right;21 x->right = y;22 updateHeight(y);23 updateHeight(x);24 return x;25}26
27Node* rotateLeft(Node* x) {28 Node* y = x->right;29 x->right = y->left;30 y->left = x;31 updateHeight(x);32 updateHeight(y);33 return y;34}35
36Node* insert(Node* node, int value) {37 if (node == nullptr) return new Node(value);38 if (value < node->value) node->left = insert(node->left, value);39 else node->right = insert(node->right, value);40 updateHeight(node);41 int balance = balanceFactor(node);42 // Four rebalancing cases: LL, RR, LR, RL43 if (balance > 1 && value < node->left->value) return rotateRight(node);44 if (balance < -1 && value > node->right->value) return rotateLeft(node);45 if (balance > 1) {46 node->left = rotateLeft(node->left);47 return rotateRight(node);48 }49 if (balance < -1) {50 node->right = rotateRight(node->right);51 return rotateLeft(node);52 }53 return node;54}55
56void inorder(const Node* n) {57 if (n == nullptr) return;58 inorder(n->left);59 std::cout << n->value << "(h" << n->height << ") ";60 inorder(n->right);61}62
63int main() {64 Node* root = nullptr;65 for (int value : {10, 20, 30, 40, 50, 25}) root = insert(root, value);66 std::cout << "Root after rebalancing: " << root->value << "\n";67 std::cout << "In-order: ";68 inorder(root);69 std::cout << "\n";70 return 0;71}AVL Tree 的 C 代码
1#include <stdio.h>2#include <stdlib.h>3
4typedef struct Node {5 int value, height;6 struct Node* left;7 struct Node* right;8} Node;9
10int height(const Node* n) { return n ? n->height : 0; }11int balanceFactor(const Node* n) { return height(n->left) - height(n->right); }12int maxInt(int a, int b) { return a > b ? a : b; }13
14Node* newNode(int value) {15 Node* n = calloc(1, sizeof(Node));16 n->value = value;17 n->height = 1;18 return n;19}20
21void updateHeight(Node* n) {22 n->height = 1 + maxInt(height(n->left), height(n->right));23}24
25Node* rotateRight(Node* y) {26 Node* x = y->left;27 y->left = x->right;28 x->right = y;29 updateHeight(y);30 updateHeight(x);31 return x;32}33
34Node* rotateLeft(Node* x) {35 Node* y = x->right;36 x->right = y->left;37 y->left = x;38 updateHeight(x);39 updateHeight(y);40 return y;41}42
43Node* insert(Node* node, int value) {44 if (node == NULL) return newNode(value);45 if (value < node->value) node->left = insert(node->left, value);46 else node->right = insert(node->right, value);47 updateHeight(node);48 int balance = balanceFactor(node);49 // Four rebalancing cases: LL, RR, LR, RL50 if (balance > 1 && value < node->left->value) return rotateRight(node);51 if (balance < -1 && value > node->right->value) return rotateLeft(node);52 if (balance > 1) {53 node->left = rotateLeft(node->left);54 return rotateRight(node);55 }56 if (balance < -1) {57 node->right = rotateRight(node->right);58 return rotateLeft(node);59 }60 return node;61}62
63void inorder(const Node* n) {64 if (n == NULL) return;65 inorder(n->left);66 printf("%d(h%d) ", n->value, n->height);67 inorder(n->right);68}69
70int main(void) {71 int values[] = {10, 20, 30, 40, 50, 25};72 Node* root = NULL;73 for (int i = 0; i < 6; i++) root = insert(root, values[i]);74 printf("Root after rebalancing: %d\n", root->value);75 printf("In-order: ");76 inorder(root);77 printf("\n");78 return 0;79}AVL 树常见问题
AVL 树中的平衡因子是什么?
节点的平衡因子等于它左子树的高度减去右子树的高度。AVL 树让每个节点的平衡因子保持在 -1、0 或 +1;如果一次插入使它超出这个范围,就通过旋转恢复平衡。
AVL 树和红黑树有什么区别?
O(log n)。AVL 树的平衡更严格,所以查找稍快,但插入和删除时可能需要更多旋转。红黑树的平衡更宽松,旋转次数更少,这就是很多标准库用它来实现 map 和 set 的原因。为什么用 AVL 树而不用普通二叉搜索树?
O(n)。AVL 树通过旋转保持平衡,无论插入顺序如何,都能保证树高和操作为 O(log n)。做数据库索引,AVL 树比红黑树更好吗?
这取决于工作负载。AVL 树的平衡更严格,所以在读多写少、希望查找路径尽可能短时更有优势。但大多数数据库和语言库选择红黑树(或 B 树),因为在写入频繁的场景下它们再平衡所需的旋转更少,而插入和删除频繁时这一点更重要。
AVL 树插入一个值需要几次旋转?
O(log n) 次旋转。每次旋转后都需要更新节点高度吗?
需要。一个常见的 bug 是指针旋转得没问题,却忘了重新计算相关两个节点的高度。每次旋转后,必须先更新被降级的节点的高度,再更新被提升的节点,因为被提升节点的高度取决于它的新子节点。跳过这一步会留下过时的平衡因子,破坏之后的再平衡判断。