数据结构篇-平衡二叉树(AVL树)

image.png

平衡二叉树定义

任意节点的子树的高度差都小于等于1,并且左右两个子树都是一棵平衡二叉树

  • B树(多路平衡搜索树)
  • AVL树(二叉平衡搜索树)

时间复杂度:平均O(logN),最坏O(N)

添加元素

总是作为叶子节点插入,当破坏平衡性,首先从该新节点向根节点查找第一个失去平衡的节点,然后以该失衡节点和它相邻的刚查找过的两个节点构成调整子树,使之成为新的平衡子树。当失衡的最小子树被调整为平衡子树后,整棵树恢复平衡。

失衡的最小子树是指以离插入节点最近,且平衡因子绝对值大于1的结点作为根的子树。假设用A表示失衡的最小子树的根结点,则调整该子树的操作可归纳为下列四种情况

平衡因子:该节点左子树高度-该右子树高度(或者该右子树高度-该节点左子树高度),当为0,1,-1时为平衡二叉树

  • LL型调整

这是因在A节点的左子树(设为B结点)的右子树上插入结点,使得A结点的 平衡因子由1变为2而引起的不平衡。

调整方法:将B结点向上升替代A结点成为根节点,A结点作为B结点的右子树,B结点的原右子树b作为A结点左子树。

  • RR型调整

这是因在A节点的右子树(设为B结点)的右子树上插入结点,使得A结点的 平衡因子由-1变为-2而引起的不平衡。

调整方法:将B结点向上升替代A结点成为根节点,A结点作为B结点的左子树,B结点的原左子树b作为A结点右子树。

  • LR型调整

这是因在A节点的左子树(设为B结点)的右子树上插入结点,使得A结点的 平衡因子由1变为2而引起的不平衡。

调整方法:将C结点向上升替代A结点成为根节点,B结点作为C结点的左子树,A结点作为C结点的右子树,C结点的原左子树b作为B结点右子树,C结点的原右子树y作为A结点左子树。

  • RL型调整

这是因在A节点的右子树(设为B结点)的左子树上插入结点,使得A结点的 平衡因子由-1变为-2而引起的不平衡。

调整方法:将C结点向上升替代A结点成为根节点,A结点作为C结点的左子树,B结点作为C结点的右子树,C结点的原左子树b作为A结点右子树,C结点的原右子树y作为B结点左子树。

删除元素

平衡二叉树删除一个结点与二叉排序树类似,只是增加调整步骤。

删除过程

1.查找。先在平衡二叉树中查找关键字为k的结点p。

2.删除。删除分为以下几种情况。

2.1 叶子节点:直接删除该结点。

2.2 单分支节点:用p结点的左或右孩子结点替代p结点(结点替换)。

2.3 双分支节点:用p结点的中序前驱(或者中序后继)结点q的值替换p结点的值,再删除结点q。

3.调整。若被删除的是结点q,则从结点q向根结点方向查找第一个失去平衡的结点

3.1 若所有节点都是平衡的,则不需调整。

3.2 假设找到某个结点的平衡因子=-2:其左孩子的平衡因子=-1,则作RR型调整。其右孩子的平衡因子=1,则做RL型调整。其右孩子平衡因子=0,在作RR或者RL型调整均可。

3.3 假设找到某个结点的平衡因子=2:其左孩子的平衡因子=-1,则作LR型调整。其右孩子的平衡因子=1,则做LL型调整。其右孩子平衡因子=0,在作LR或者LL型调整均可。

参考例子

代码实现

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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
//AVL算法
#include <stdio.h>
#include <malloc.h>
typedef int KeyType; //关键字类型
typedef char InfoType;
typedef struct node //记录类型
{
KeyType key; //关键字项
int bf; //平衡因子
InfoType data; //其他数据域
struct node *lchild,*rchild; //左右孩子指针
} BSTNode;
void LeftProcess(BSTNode *&p,int &taller)
//对以指针p所指结点为根的二叉树作左平衡旋转处理,本算法结束时,指针p指向新的根结点
{
BSTNode *p1,*p2;
if (p->bf==0) //原本左、右子树等高,现因左子树增高而使树增高
{
p->bf=1;
taller=1;
}
else if (p->bf==-1) //原本右子树比左子树高,现左、右子树等高
{
p->bf=0;
taller=0;
}
else //原本左子树比右子树高,需作左子树的平衡处理
{
p1=p->lchild; //p1指向结点p的左孩子
if (p1->bf==1) //新结点插入在结点b的左孩子的左子树上,要作LL调整
{
p->lchild=p1->rchild;
p1->rchild=p;
p->bf=p1->bf=0;
p=p1;
}
else if (p1->bf==-1) //新结点插入在结点b的左孩子的右子树上,要作LR调整
{
p2=p1->rchild;
p1->rchild=p2->lchild;
p2->lchild=p1;
p->lchild=p2->rchild;
p2->rchild=p;
if (p2->bf==0) //新结点插在p2处作为叶子结点的情况
p->bf=p1->bf=0;
else if (p2->bf==1) //新结点插在p2的左子树上的情况
{
p1->bf=0;p->bf=-1;
}
else //新结点插在p2的右子树上的情况
{
p1->bf=1;p->bf=0;
}
p=p2;p->bf=0; //仍将p指向新的根结点,并置其bf值为0
}
taller=0;
}
}
void RightProcess(BSTNode *&p,int &taller)
//对以指针p所指结点为根的二叉树作右平衡旋转处理,本算法结束时,指针p指向新的根结点
{
BSTNode *p1,*p2;
if (p->bf==0) //原本左、右子树等高,现因右子树增高而使树增高
{
p->bf=-1;
taller=1;
}
else if (p->bf==1) //原本左子树比右子树高,现左、右子树等高
{
p->bf=0;
taller=0;
}
else //原本右子树比左子树高,需作右子树的平衡处理
{
p1=p->rchild; //p1指向结点p的右子树根结点
if (p1->bf==-1) //新结点插入在结点b的右孩子的右子树上,要作RR调整
{
p->rchild=p1->lchild;
p1->lchild=p;
p->bf=p1->bf=0;
p=p1;
}
else if (p1->bf==1) //新结点插入在结点p的右孩子的左子树上,要作RL调整
{
p2=p1->lchild;
p1->lchild=p2->rchild;
p2->rchild=p1;
p->rchild=p2->lchild;
p2->lchild=p;
if (p2->bf==0) //新结点插在p2处作为叶子结点的情况
p->bf=p1->bf=0;
else if (p2->bf==-1) //新结点插在p2的右子树上的情况
{
p1->bf=0;p->bf=1;
}
else //新结点插在p2的左子树上的情况
{
p1->bf=-1;p->bf=0;
}
p=p2;p->bf=0; //仍将p指向新的根结点,并置其bf值为0
}
taller=0;
}
}
int InsertAVL(BSTNode *&b,KeyType e,int &taller)
/*若在平衡的二叉排序树b中不存在和e有相同关键字的结点,则插入一个
数据元素为e的新结点,并返回1,否则返回0。若因插入而使二叉排序树
失去平衡,则作平衡旋转处理,布尔变量taller反映b长高与否*/
{
if(b==NULL) //原为空树,插入新结点,树“长高”,置taller为1
{
b=(BSTNode *)malloc(sizeof(BSTNode));
b->key=e;
b->lchild=b->rchild=NULL;
b->bf=0;
taller=1;
}
else
{
if (e==b->key) //树中已存在和e有相同关键字的结点则不再插入
{
taller=0;
return 0;
}
if (e<b->key) //应继续在结点b的左子树中进行搜索
{
if ((InsertAVL(b->lchild,e,taller))==0) //未插入
return 0;
if (taller==1) //已插入到结点b的左子树中且左子树“长高”
LeftProcess(b,taller);
}
else //应继续在结点b的右子树中进行搜索
{
if ((InsertAVL(b->rchild,e,taller))==0) //未插入
return 0;
if (taller==1) //已插入到b的右子树且右子树“长高”
RightProcess(b,taller);
}
}
return 1;
}
void DispBSTree(BSTNode *b) //以括号表示法输出AVL
{
if (b!=NULL)
{
printf("%d",b->key);
if (b->lchild!=NULL || b->rchild!=NULL)
{
printf("(");
DispBSTree(b->lchild);
if (b->rchild!=NULL) printf(",");
DispBSTree(b->rchild);
printf(")");
}
}
}
void DestroyAVL(BSTNode *&b) //销毁AVL
{
if (b!=NULL)
{
DestroyAVL(b->lchild);
DestroyAVL(b->rchild);
free(b);
}
}
void LeftProcess1(BSTNode *&p,int &taller) //在删除结点时进行左处理
{
BSTNode *p1,*p2;
if (p->bf==1)
{
p->bf=0;
taller=1;
}
else if (p->bf==0)
{
p->bf=-1;
taller=0;
}
else //p->bf=-1
{
p1=p->rchild;
if (p1->bf==0) //需作RR调整
{
p->rchild=p1->lchild;
p1->lchild=p;
p1->bf=1;p->bf=-1;
p=p1;
taller=0;
}
else if (p1->bf==-1) //需作RL调整
{
p->rchild=p1->lchild;
p1->lchild=p;
p->bf=p1->bf=0;
p=p1;
taller=1;
}
else //需作RL调整
{
p2=p1->lchild;
p1->lchild=p2->rchild;
p2->rchild=p1;
p->rchild=p2->lchild;
p2->lchild=p;
if (p2->bf==0)
{
p->bf=0;p1->bf=0;
}
else if (p2->bf==-1)
{
p->bf=1;p1->bf=0;
}
else
{
p->bf=0;p1->bf=-1;
}
p2->bf=0;
p=p2;
taller=1;
}
}
}
void RightProcess1(BSTNode *&p,int &taller) //在删除结点时进行右处理
{
BSTNode *p1,*p2;
if (p->bf==-1)
{
p->bf=0;
taller=-1;
}
else if (p->bf==0)
{
p->bf=1;
taller=0;
}
else //p->bf=1
{
p1=p->lchild;
if (p1->bf==0) //需作LL调整
{
p->lchild=p1->rchild;
p1->rchild=p;
p1->bf=-1;p->bf=1;
p=p1;
taller=0;
}
else if (p1->bf==1) //需作RL调整
{
p->lchild=p1->rchild;
p1->rchild=p;
p->bf=p1->bf=0;
p=p1;
taller=1;
}
else //需作LR调整
{
p2=p1->rchild;
p1->rchild=p2->lchild;
p2->lchild=p1;
p->lchild=p2->rchild;
p2->rchild=p;
if (p2->bf==0)
{
p->bf=0;p1->bf=0;
}
else if (p2->bf==1)
{
p->bf=-1;p1->bf=0;
}
else
{
p->bf=0;p1->bf=1;
}
p2->bf=0;
p=p2;
taller=1;
}
}
}
void Delete2(BSTNode *q,BSTNode *&r,int &taller)
//由DeleteAVL()调用,用于处理被删结点左右子树均不空的情况
{
if (r->rchild==NULL)
{
q->key=r->key;
q=r;
r=r->lchild;
free(q);
taller=1;
}
else
{
Delete2(q,r->rchild,taller);
if (taller==1)
RightProcess1(r,taller);
}
}
int DeleteAVL(BSTNode *&p,KeyType x,int &taller) //在AVL树p中删除关键字为x的结点
{
int k;
BSTNode *q;
if (p==NULL)
return 0;
else if (x<p->key)
{
k=DeleteAVL(p->lchild,x,taller);
if (taller==1)
LeftProcess1(p,taller);
return k;
}
else if (x>p->key)
{
k=DeleteAVL(p->rchild,x,taller);
if (taller==1)
RightProcess1(p,taller);
return k;
}
else //找到了关键字为x的结点,由p指向它
{
q=p;
if (p->rchild==NULL) //被删结点右子树为空
{
p=p->lchild;
free(q);
taller=1;
}
else if (p->lchild==NULL) //被删结点左子树为空
{
p=p->rchild;
free(q);
taller=1;
}
else //被删结点左右子树均不空
{
Delete2(q,q->lchild,taller);
if (taller==1)
LeftProcess1(q,taller);
p=q;
}
return 1;
}
}
int main()
{
BSTNode *b=NULL;
int i,j,k;
KeyType a[]={16,3,7,11,9,26,18,14,15},n=9; //例9.5
printf(" 创建一棵AVL树:\n");
for(i=0;i<n;i++)
{
printf(" 第%d步,插入%d元素:",i+1,a[i]);
InsertAVL(b,a[i],j);
DispBSTree(b);printf("\n");
}
printf(" AVL:");DispBSTree(b);printf("\n");
printf(" 删除操作:\n"); //例9.6
k=11;
printf(" 删除关键字%d:",k);
DeleteAVL(b,k,j);
printf(" AVL:");DispBSTree(b);printf("\n");
k=9;
printf(" 删除关键字%d:",k);
DeleteAVL(b,k,j);
printf(" AVL:");DispBSTree(b);printf("\n");
k=15;
printf(" 删除关键字%d:",k);
DeleteAVL(b,k,j);
printf(" AVL:");DispBSTree(b);printf("\n\n");
DestroyAVL(b);
return 1;
}

参考资料

平衡二叉树-PPT

平衡二叉树详解
平衡二叉树的实现原理
平衡二叉树实现的实例


数据结构篇-平衡二叉树(AVL树)
https://mikeygithub.github.io/2020/12/23/yuque/数据结构-平衡二叉树/
作者
Mikey
发布于
2020年12月23日
许可协议