AVL树原理及实现 +B树
1. AVL定义
AVL树是一种改进版的搜索二叉树。对于一般的搜索二叉树而言,如果数据恰好是按照从小到大的顺序或者从大到小的顺序插入的,那么搜索二叉树就对退化成链表,这个时候查找,插入和删除的时间都会上升到O(n),而这对于海量数据而言,是我们无法忍受的。即使是一颗由完全随机的数据构造成的搜索二叉树,从统计角度去分析,在进行若甘次的插入和删除操作,这个搜索二叉树的高度也不能令人满意。这个时候大家就希望能有一种二叉树解决上述问题。这个时候就出现平衡搜索二叉树,它的基本原理就是在插入和删除的时候,根据情况进行调整,以降低二叉树的高度。平衡搜索二叉树典型代表就是AVL树和红黑树。
AVL树:任何一个节点的左子支高度与右子支高度之差的绝对值不超过1。需要我们注意的是,AVL树定义不是说从根节点到叶子节点的最短距离比最长短距离大1。
上图就是一颗AVL树,从根节点到叶子节点的最短距离是5,最长距离是9。
2. 旋转的定义
因为每种书中对旋转的定义不一致,所以我们有必要在这里特此说明一下
以某一个节点为轴,它的左子枝顺时针旋转,作为新子树的根,我们称之为顺时针旋转(clockwise)或者右旋转。
同理,以某一个节点为轴,它的右子枝逆针旋转,作为新子树的根,我们称之为逆时针旋转(anticlockwise)或者左旋转。
3. AVL插入操作
AVL树的插入操作首先会按照普通搜索二叉树的插入操作进行,当插入一个数据后,我们会沿着插入数据时所经过的的节点回溯,回溯的过程中会判回溯路径中的每个节点的左子支高度与右子支高度之差的绝对值是否超过1,如果超过1我们就进行调整,调整的目的是使得该节点满足AVL树的定义。调整的情况可以分为以下四旋转操作,旋转操作可以降低树的高度,同时不改变搜索二叉树的性质(即任何一个节点左子支中的全部节点小于该节点,右子支的全部节点大于该节点)。
3.1 情况1
节点X左子支比右子支高度大2,且插入的节点位于X的左孩子节点XL的左子支上
3.2 情况2
节点X右子支比左子支高度大2,且插入的节点位于节点X右孩子节点XR的右子支上
3.3 情况3
节点X左子支比右子支高度大2,且插入的节点位于节点X左孩子节点XL的右子支上
3.4 情况4
节点X左子支比右子支高度大2,且插入的节点位于节点X左孩子节点XL的右子支上
4. AVL删除操作
AVL树的删除操作和插入操作一样,首先会按照普通搜索二叉树的删除操作进行,当删除一个数据后,和插入操作一样,我们通常采取的策略是沿着删除数据时所经过的的节点回溯,回溯的过程中会判断该节点的左子支高度与右子支高度之差的绝对值是否超过1(或者说大2),如果超过1,我们就进行调整,调整的目的是使得该节点满足AVL树的定义。调整的情况可以分为四种,和插入过程完全一样,这里不在赘述。
5. C语言实现
5.1节点定义
AVLtree.h文件中的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#ifndef __AVLTREE_H__ #define __AVLTREE_H__ typedef struct Node{ int height; //该节点作为子树时的高度 int data; //表示每个节点存贮的数据 Node* left; Node* right; }Node, *AVLtree; //AVLtree 表示Node* //AVLtree* 就表示Node** int Insert(AVLtree* T, int D); int Delete(AVLtree* T, int D); int Find(AVLtree T, int x); int Destroy(AVLtree* T); //下面两个遍历函数主要用于测试 void InOredrTraverse(AVLtree T); void PreOredrTraverse(AVLtree T); #endif |
5.2代码实现
AVLtree.cpp文件中的内容
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
|
#include "AVLtree.h" #include<stdlib.h> #include<stdio.h> #define MAX(x1,x2) ((x1) > (x2) ? (x1) : (x2)) //一下函数用于辅助实现插入删除操作,作用域于仅限于AVLtree.cpp static void PostOrderTraverse(AVLtree T); static int GetHeight(AVLtree T); static void LeftRotate(AVLtree* T); static void RightRotate(AVLtree* T); static int FindMin(AVLtree T); //返回值用于表示插入是否成功,-1表示失败(说明树中已包含该数据),0表示成功 int Insert(AVLtree* T, int D){ //参数检查 if (T == NULL){ return - 1 ; } //找到插入的位置 if (*T == NULL){ *T = (Node*)malloc(sizeof(Node)); (*T)->data = D; (*T)->height = 1 ; (*T)->left = NULL; (*T)->right = NULL; return 0 ; } else { //树中已存在该数据 if (D == (*T)->data){ return - 1 ; } else if (D > (*T)->data){ //在右子树中插入 if (Insert(&(*T)->right,D) == - 1 ){ return - 1 ; } //插入后,当回溯到该节点进行检查,如果不满足平衡条件,则调整 //因为是在右子支中插入,如果高度只差等于2,只可能是右子支比左子支高 if (GetHeight((*T)->right) - GetHeight((*T)->left) == 2 ){ if (D > (*T)->right->data){ LeftRotate(T); //对应情况2,左旋 } else { //对应情况4,先右旋再左旋 RightRotate(&(*T)->right); LeftRotate(T); } } } else if (D < (*T)->data){ //在左子树中插入 if (Insert(&(*T)->left,D)){ return - 1 ; } if (GetHeight((*T)->left) - GetHeight((*T)->right) == 2 ){ if (D < (*T)->left->data){ RightRotate(T); //对应情况1,左旋 } else { //对应情况3,先右旋再左旋 LeftRotate(&(*T)->left); RightRotate(T); } } } } //更新当前节点的高度 (*T)->height = MAX(GetHeight((*T)->left),GetHeight((*T)->right))+ 1 ; return 0 ; } //返回-1表示,树中没有该数据,删除失败, int Delete(AVLtree* T, int D){ static Node* tmp; if (T == NULL){ return - 1 ; } if (*T == NULL){ //树为空,或者树中没有该数据 return - 1 ; } else { //找到要删除的节点 if (D == (*T)->data){ //删除的节点左右子支都不为空,一定存在前驱节点 if ((*T)->left != NULL && (*T)->right != NULL){ D = FindMin((*T)->right); //找后继替换 (*T)->data = D; Delete(&(*T)->right,D); //然后删除后继节点,一定成功 //在右子支中删除,删除后有可能左子支比右子支高度大2 if (GetHeight((*T)->left)-GetHeight((*T)->right) == 2 ){ //判断哪一个左子支的的两个子支哪个比较高 if (GetHeight((*T)->left->left) >= GetHeight((*T)->left->right)){ RightRotate(T); } else { LeftRotate(&(*T)->left); RightRotate(T); } } } else if ((*T)->left == NULL){ //左子支为空 tmp = (*T); (*T) = tmp->right; free(tmp); return 0 ; } else if ((*T)->right == NULL){ //右子支为空 tmp = (*T); (*T) = tmp->left; free(tmp); return 0 ; } } else if (D > (*T)->data){ //在右子支中寻找待删除的节点 if (Delete(&(*T)->right,D) == - 1 ){ return - 1 ; //删除失败,不需要调整,直接返回 } if (GetHeight((*T)->left)-GetHeight((*T)->right) == 2 ){ if (GetHeight((*T)->left->left) >= GetHeight((*T)->left->right)){ RightRotate(T); } else { LeftRotate(&(*T)->left); RightRotate(T); } } } else if (D < (*T)->data){ //在左子支中寻找待删除的节点 if (Delete(&(*T)->left,D) == - 1 ){ return - 1 ; } if (GetHeight((*T)->right) - GetHeight((*T)->left) == 2 ){ if (GetHeight((*T)->right->right) >= GetHeight((*T)->right->left)){ LeftRotate(T); } else { RightRotate(&(*T)->right); LeftRotate(T); } } } } //更新当前节点的高度 (*T)->height = MAX(GetHeight((*T)->left),GetHeight((*T)->right))+ 1 ; return 0 ; } int Find(AVLtree T, int x){ while (T != NULL){ if (T->data == x){ return 0 ; } else if (x > T->data){ T = T->right; } else { T = T->left; } } return - 1 ; } int Destroy(AVLtree* T){ if (T == NULL){ return - 1 ; } PostOrderTraverse(*T); *T = NULL; return 0 ; } void InOredrTraverse(AVLtree T){ if (T != NULL){ InOredrTraverse(T->left); printf( "%3d " ,T->data); InOredrTraverse(T->right);; } } void PreOredrTraverse(AVLtree T){ if (T != NULL){ printf( "%3d:%2d(%3d,%3d)\n" ,T->data,T->height, T->left == NULL?- 1 :T->left->data, T->right == NULL?- 1 :T->right->data ); PreOredrTraverse(T->left); PreOredrTraverse(T->right); } } static void PostOrderTraverse(AVLtree T){ if (T != NULL){ PostOrderTraverse(T->left); PostOrderTraverse(T->right); free(T); } } //空数的高度为0 static int GetHeight(AVLtree T){ if (T == NULL){ return 0 ; } else { return T->height; } } static void LeftRotate(AVLtree* T){ Node *P,*R; P = *T; R = P->right; P->right = R->left; R->left = P; *T = R; //旋转以后要更新节点的高度 P->height = MAX(GetHeight(P->left),GetHeight(P->right))+ 1 ; R->height = MAX(GetHeight(R->left),GetHeight(R->right))+ 1 ; } static void RightRotate(AVLtree* T){ Node *P,*L; P = *T; L = P->left; P->left = L->right; L->right = P; *T = L; //旋转以后要更新节点的高度 P->height = MAX(GetHeight(P->left),GetHeight(P->right))+ 1 ; L->height = MAX(GetHeight(L->left),GetHeight(L->right))+ 1 ; } static int FindMin(AVLtree T){ if (T == NULL){ return - 1 ; } while (T->left != NULL){ T = T->left; } return T->data; } |
1. 2-3-4树的定义
2-3-4树是一种阶为4的B树。它是一种自平衡的数据结构,可以保证在O(lgn)的时间内完成查找、插入和删除操作。它主要满足以下性质:
(1)每个节点每个节点有1、2或3个key,分别称为2(孩子)节点,3(孩子)节点,4(孩子)节点。
(2)所有叶子节点到根节点的长度一致(也就是说叶子节点都在同一层)。
(3)每个节点的key从左到右保持了从小到大的顺序,两个key之间的子树中所有的
key一定大于它的父节点的左key,小于父节点的右key。
2. 插入操作
(1)如果2-3-4树中已存在当前插入的key,则插入失败,否则最终一定是在叶子节点中进行插入操作
(2)如果待插入的节点不是4节点,那么直接在该节点插入
(3)如果待插入的节点是个4节点,那么应该先分裂该节点然后再插入。一个4节点可以分裂成一个根节点和两个子节点(这三个节点各含一个key)然后在子节点中插入,我们把分裂形成的根节点看成向上层插入的节点,然后重复第2步和第3步。
如果是在4节点中进行插入,每次插入会多出一个分支,如果插入操作导致根节点分裂,则2-3-4树会生长一层。
3. 删除操作
(1)如果2-3-4树中不存在当前需要删除的key,则删除失败。
(2)如果当前需要删除的key不位于叶子节点上,则用后继key覆盖,然后在它后继
key所在的子支中删除该后继key。
(3)如果当前需要删除的key位于叶子节点上:
(3.1)该节点不是2节点,删除key,结束
(3.2)该节点是2节点,删除该节点:
(3.2.1)如果兄弟节点不是2节点,则父节点中的key下移到该节点,兄弟节点中的一个key上移
(3.2.2)如果兄弟节点是2节点,父节点中的key与兄弟节点中的key合并,形成一个3节点,此节点实际上是下一层的节点,重复步骤3.2
如果是在2节点(叶子节点)中进行删除,每次删除会减少一个分支,如果删除操作导致根节点参与合并,则2-3-4树会降低一层。
4. 带有预分裂的插入操作
上面的插入以及删除操作在某些情况需要不断回溯来调整树的结构已达到平衡。为了消除回溯过程,在插入操作过程中我们可以采取预分裂的操作,即我们在插入的搜索路径中,遇到4节点就分裂(分裂后形成的根节点的key要上移,与父节点中的key合并)这样可以保证找到需要插入节点时可以直接插入(即该节点一定不是4节点)
5. 带有预分裂的插入操作
在删除过程中,我们同样可以采取预合并的操作,即我们在删除的搜索路径中(除根节点,因为根节点没有兄弟节点和父节点),遇到当前节点是2节点,如果兄弟节点也是2节点就合并(该节点的父节点中的key下移,与自身和兄弟节点合并);如果兄弟节点不是2节点,则父节点的key下移,兄弟节点中的key上移。这样可以保证,找到需要删除的key所在的节点时可以直接删除(即要删除的key所在的节点一定不是2节点)。
这里包含key为60的节点也可以选择让父节点中的key 76下移和兄弟节点中的83合并,两种方式都能达到B树的平衡,这也是在2-3-4树对应的红黑树中使用的方式。