数据结构之AVL自平衡二叉树

AVL的起源

AVL 并不是一个缩写词,而是取自它的两位前苏联发明者:G.M. Adelson-Velsky 和 E.M. Landis。他们在 1962 年的论文中首次提出了这种结构。这是历史上第一种自平衡二叉搜索树。在那个内存以 KB 计算的年代,如何高效利用存储空间和计算周期是生死攸关的大事。


要解决的核心问题:防止二叉树“降级”

我们在之前的二叉树中已经看到了:当插入数据趋于有序(如 1, 2, 3, 4)时,树会不可避免地退化成一个链表。这种普通 BST 的性能完全取决于输入数据的运气。AVL 的使命就是无论你按什么顺序输入,它都要通过 “强力整形”,即所谓的 旋转,确保查找的时间复杂度死死锁定在 $O(\log n)$


核心原理:极度均衡的平衡因子

AVL 树的灵魂在于它对 “平衡” 的严苛定义。

① 平衡因子(Balance Factor, BF)

每一个节点现在多了一个属性,叫做平衡因子:$BF = \text{Height(Left Subtree)} - \text{Height(Right Subtree)}$

AVL 的铁律:每一个节点的 $|BF|$ 必须 $\le 1$。也就是说,左子树和右子树的高度差,最多只能是 1。一旦变成 2,树就会立刻触发 “警报”,开始自我调整。

② 四种失衡场景与四大招式

为了维持这个 $BF \le 1$ 的铁律,AVL 总结出了四种 “长歪” 的情况,并对应了四种手术方案:


AVL的优缺点

AVL 树就像一个有强迫症的整理控。它的最大优点是其构建的二叉树极度平衡,查找速度是所有BST中最快的。这种为了维持这种 “绝对平衡”,每次插入或删除节点时,都可能引发从下往上的一连串旋转,导致 写入性能 略有下降。这就是为什么后来出现了 “红黑树” 的原因,红黑树放宽了平衡的要求,在树的均衡和写入效率之间寻找到了最佳的平衡。


旋转的过程

想象三个节点:10 → 20 → 30(连成了一条向右下的直线)。

  • 失衡感:10 觉得右边太沉了。
  • 动作:我们抓住 20,把它往上提。
  • 发生的位移:20 升官变成了新的最高领导;10 降级变成了 20 的左孩子;30 没动依然是 20 的右孩子,只是跟着 20 一起往上挪了一位。

结果原本 3 层的 “长棍子”,变成了以 20 为中心的 “人字形” 结构。

旋转里最容易让人糊涂的是:如果被提拔的那个孩子,原本就有自己的孩子怎么办?比如 20 原本有一个左孩子 15。现在 20 要晋升取代 10 的位置 15 该往哪放?解决方式如下:

  • 20 要变成 10 的新领导,它的左手必须腾出来拉住 10。
  • 20 就把 15 这个包袱甩给了 10。

为什么能甩呢?这是因为 15 本来就在 20 的左边(比 20 小),且在 10 的右边(比 10 大)。所以把它挂在 10 的右边,完美符合 “左小右大 ” 的规则。这就是代码里 x.right = y.left 的含义:原来父节点下台前,接手了新的父节点腾出手时丢下的子节点。你可以把旋转的过程总结成下面的四个字:

  • :把重的那个孩子往上提。
  • :把原来的父节点往下拉。
  • :把多出来的子树像包袱一样甩给降级的父节点。
  • :原本倾斜的天平,瞬间就拉平了。


AVL 左旋和右旋过程演示

观察节点如何通过“降级”与“提拔”重新分配高度

准备就绪。点击下方按钮开始构建。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 左旋转 (RR 型失衡使用)
*/
private TreeNode leftRotate(TreeNode x) {
// 拿到句柄
TreeNode y = x.right;
TreeNode temp = y.left;

// 执行旋转:y 提拔、x 下沉
y.left = x;
x.right = temp;

// 更新高度:先更新底层x、再更新高层y
updateHeight(x);
updateHeight(y);

// 返回高层y作为当前根节点
return y;
}

/**
* 右旋转 (LL 型失衡使用)
*/
private TreeNode rightRotate(TreeNode y) {
// 拿到句柄
TreeNode x = y.left;
TreeNode temp = x.right; // 暂存x的右子树(过继子)

// 执行旋转:x 提拔、y 下沉
x.right = y;
y.left = temp;

// 更新高度:先更新底层y、再更新高层x
updateHeight(y);
updateHeight(x);

// 返回高层x作为当前根节点
return x;
}


完整的代码实现

AVL 树的生成过程交互演示:

● 系统就绪 请输入数值开始构建 AVL 树

下面是一个完整的 ALV 插入、查找、遍历的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
* @author KJ
*/
public class MyAVL {

private TreeNode root;
public static class TreeNode {
int value;
int height;
TreeNode left;
TreeNode right;

public TreeNode(int value) {
this.value = value;
this.height = 1;
}
}

private void updateHeight(TreeNode node) {
if (node != null) {
node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
}
}

private int getHeight(TreeNode node) {
return node == null ? 0 : node.height;
}

private int getBalanceFactor(TreeNode node) {
return node == null ? 0 : getHeight(node.left) - getHeight(node.right);
}

/**
* 右旋转 (LL 型失衡使用)
*/
private TreeNode rightRotate(TreeNode y) {
// 拿到句柄
TreeNode x = y.left;
TreeNode temp = x.right; // 暂存x的右子树(过继子)

// 执行旋转:x 提拔、y 下沉
x.right = y;
y.left = temp;

// 更新高度:先更新底层y、再更新高层x
updateHeight(y);
updateHeight(x);

// 返回高层x作为当前根节点
return x;
}

/**
* 左旋转 (RR 型失衡使用)
*/
private TreeNode leftRotate(TreeNode x) {
// 拿到句柄
TreeNode y = x.right;
TreeNode temp = y.left;

// 执行旋转:y 提拔、x 下沉
y.left = x;
x.right = temp;

// 更新高度:先更新底层x、再更新高层y
updateHeight(x);
updateHeight(y);

// 返回高层y作为当前根节点
return y;
}


/**
* 插入
*/
public void insert(int value) {
this.root = insert(this.root, value);
}

private TreeNode insert(TreeNode curRoot, int value) {
// 1. 标准 BST 插入逻辑
if (curRoot == null) {
return new TreeNode(value);
}
if (value < curRoot.value) {
curRoot.left = insert(curRoot.left, value);
} else if (value > curRoot.value) {
curRoot.right = insert(curRoot.right, value);
} else {
// node.data = newData; // 如果节点存有数据
return curRoot; // 不允许重复值
}

// 2. 更新高度
updateHeight(curRoot);

// 3. 计算平衡因子,检查是否失衡
int balance = getBalanceFactor(curRoot);

// 4. 处理四种失衡的情况
// 4.1 LL => 执行右旋
if (balance > 1 && value < curRoot.left.value) {
return rightRotate(curRoot);
}
// 4.2 RR => 执行左旋
if (balance < -1 && value > curRoot.right.value) {
return leftRotate(curRoot);
}
// 4.3 LR => 先局部左旋,再整体右旋
// 新节点插入到了左子树的右侧,此时节点排列像一个“小于号”
if (balance > 1 && value > curRoot.left.value) {
curRoot.left = leftRotate(curRoot.left);
return rightRotate(curRoot);
}
// 4.4 RL => 先局部右旋,再整体左旋
// 新节点插入到了右子树的左侧,此时节点排列像一个“大于号”
if (balance < -1 && value < curRoot.right.value) {
curRoot.right = rightRotate(curRoot.right);
return leftRotate(curRoot);
}
return curRoot;
}


/**
* 查找
*/
public boolean search(int value) {
TreeNode curRoot = this.root;
while (curRoot != null) {
if (value == curRoot.value) return true;
curRoot = value < curRoot.value ? curRoot.left : curRoot.right;
}
return false;
}

/**
* 中序遍历打印树结构
*/
public void printTree() {
printTree(root, 0);
}

private void printTree(TreeNode node, int level) {
if (node == null) {
return;
}

printTree(node.right, level + 1);
if (level == 0) {
System.out.println(node.value + "[" + node.height + "]");
} else {
String subPrefix = "| ".repeat(level - 1);
System.out.println(subPrefix + "|---" + node.value + "[" + node.height + "]");
}
printTree(node.left, level + 1);
}

public static void main(String[] args) {
MyAVL avl = new MyAVL();

// 故意按顺序插入,看它是否能通过旋转保持平衡
int[] values = {10, 20, 30, 40, 50, 25};
for (int value : values) {
avl.insert(value);
}

avl.printTree();
System.out.println(avl.search(25));
System.out.println(avl.search(90));
}
}

测试结果:

1
2
3
4
5
6
7
8
|    |---50[1]
|---40[2]
30[3]
| |---25[1]
|---20[2]
| |---10[1]
true
false