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树对应的红黑树中使用的方式。
参考:AVL树原理及实现 ,从2-3-4树到红黑树(上)
AVL树原理及实现 +B树的更多相关文章
- AVL树,红黑树,B-B+树,Trie树原理和应用
前言:本文章来源于我在知乎上回答的一个问题 AVL树,红黑树,B树,B+树,Trie树都分别应用在哪些现实场景中? 看完后您可能会了解到这些数据结构大致的原理及为什么用在这些场景,文章并不涉及具体操作 ...
- 从Trie树到双数组Trie树
Trie树 原理 又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种.它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,能在常数时间O(len)内实现插入和查 ...
- 浅谈AVL树,红黑树,B树,B+树原理及应用(转)
出自:https://blog.csdn.net/whoamiyang/article/details/51926985 背景:这几天在看<高性能Mysql>,在看到创建高性能的索引,书上 ...
- 浅谈AVL树,红黑树,B树,B+树原理及应用
背景:这几天在看<高性能Mysql>,在看到创建高性能的索引,书上说mysql的存储引擎InnoDB采用的索引类型是B+Tree,那么,大家有没有产生这样一个疑问,对于数据索引,为什么要使 ...
- 深入理解索引和AVL树、B-树、B+树的关系
目录 什么是索引 索引的分类 索引和AVL树.B-树.B+树的关系 AVL树.红黑树 B-树 B+树 SQL和NoSQL索引 什么是索引 索引时数据库的一种数据结构,数据库与索引的关系可以看作书籍和目 ...
- 树:BST、AVL、红黑树、B树、B+树
我们这个专题介绍的动态查找树主要有: 二叉查找树(BST),平衡二叉查找树(AVL),红黑树(RBT),B~/B+树(B-tree).这四种树都具备下面几个优势: (1) 都是动态结构.在删除,插入操 ...
- 老李推荐:第14章9节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-遍历控件树查找控件
老李推荐:第14章9节<MonkeyRunner源码剖析> HierarchyViewer实现原理-遍历控件树查找控件 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员 ...
- 编译原理LL1文法分析树(绘图过程)算法实现
import hjzgg.analysistable.AnalysisTable; import hjzgg.first.First; import hjzgg.follow.Follow; impo ...
- B树和B+树原理图文解析
B树与B+树不同的地方在于插入是从底向上进行(当然查找与二叉树相同,都是从上往下) 二者都通常用于数据库和操作系统的文件系统中,非关系型数据库索引如mongoDB用的B树,大部分关系型数据库索引使用的 ...
随机推荐
- 子类 父类强转 HttpServlet service实现
相当于 走父类 临时走了一趟 HttpServletRequest ->ServletRequets -> HttpServeltRequest /* */ public void ser ...
- Codeforces Round #505 (Div 1 + Div 2) (A~D)
目录 Codeforces 1025 A.Doggo Recoloring B.Weakened Common Divisor C.Plasticine zebra D.Recovering BST( ...
- curl dns缓存设置
CURLOPT_DNS_USE_GLOBAL_CACHE 启用时会启用一个全局的DNS缓存,此项为线程安全的,并且默认启用.CURLOPT_DNS_CACHE_TIMEOUT 设置在内存中保存DNS信 ...
- 【BZOJ-2329&2209】括号修复&括号序列 Splay
2329: [HNOI2011]括号修复 Time Limit: 40 Sec Memory Limit: 128 MBSubmit: 1007 Solved: 476[Submit][Statu ...
- Jmeter关于上传图片接口
最近接到的一个新的项目,老规矩,开发组开发完接口需要进行接口的测试,其他的很简单,根据限制条件逻辑等设计数据,用浏览器或者工具进行验证就OK. 其中有一个接口涉及到图片的上传,以前没有用过,通过查找资 ...
- [Deepin 15] 编译安装 MySQL-5.6.35
在 Ubuntu 下,先前一直是 二进制包解压安装,详情参考: http://www.cnblogs.com/52php/p/5680906.html 现改为 源码编译安装: #!/bin/bash ...
- delphi project of object
http://www.cnblogs.com/ywangzi/archive/2012/08/28/2659811.html 其实要了解这些东西,适当的学些反汇编,WINDOWS内存管理机制,PE结构 ...
- unlocked_ioctl和compat_ioctl
参考: https://www.cnblogs.com/super119/archive/2012/12/03/2799967.html https://lwn.net/Articles/119652 ...
- ASP.NET Web API实践系列07,获取数据, 使用Ninject实现依赖倒置,使用Knockout实现页面元素和视图模型的双向绑定
本篇接着上一篇"ASP.NET Web API实践系列06, 在ASP.NET MVC 4 基础上增加使用ASP.NET WEB API",尝试获取数据. 在Models文件夹下创 ...
- The Topo to Raster tool returns errors 010235 and 010067转
Problem: The Topo to Raster tool returns errors 010235 and 010067 Description The Topo to Raster geo ...