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树的更多相关文章

  1. AVL树,红黑树,B-B+树,Trie树原理和应用

    前言:本文章来源于我在知乎上回答的一个问题 AVL树,红黑树,B树,B+树,Trie树都分别应用在哪些现实场景中? 看完后您可能会了解到这些数据结构大致的原理及为什么用在这些场景,文章并不涉及具体操作 ...

  2. 从Trie树到双数组Trie树

    Trie树 原理 又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种.它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,能在常数时间O(len)内实现插入和查 ...

  3. 浅谈AVL树,红黑树,B树,B+树原理及应用(转)

    出自:https://blog.csdn.net/whoamiyang/article/details/51926985 背景:这几天在看<高性能Mysql>,在看到创建高性能的索引,书上 ...

  4. 浅谈AVL树,红黑树,B树,B+树原理及应用

    背景:这几天在看<高性能Mysql>,在看到创建高性能的索引,书上说mysql的存储引擎InnoDB采用的索引类型是B+Tree,那么,大家有没有产生这样一个疑问,对于数据索引,为什么要使 ...

  5. 深入理解索引和AVL树、B-树、B+树的关系

    目录 什么是索引 索引的分类 索引和AVL树.B-树.B+树的关系 AVL树.红黑树 B-树 B+树 SQL和NoSQL索引 什么是索引 索引时数据库的一种数据结构,数据库与索引的关系可以看作书籍和目 ...

  6. 树:BST、AVL、红黑树、B树、B+树

    我们这个专题介绍的动态查找树主要有: 二叉查找树(BST),平衡二叉查找树(AVL),红黑树(RBT),B~/B+树(B-tree).这四种树都具备下面几个优势: (1) 都是动态结构.在删除,插入操 ...

  7. 老李推荐:第14章9节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-遍历控件树查找控件

    老李推荐:第14章9节<MonkeyRunner源码剖析> HierarchyViewer实现原理-遍历控件树查找控件   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员 ...

  8. 编译原理LL1文法分析树(绘图过程)算法实现

    import hjzgg.analysistable.AnalysisTable; import hjzgg.first.First; import hjzgg.follow.Follow; impo ...

  9. B树和B+树原理图文解析

    B树与B+树不同的地方在于插入是从底向上进行(当然查找与二叉树相同,都是从上往下) 二者都通常用于数据库和操作系统的文件系统中,非关系型数据库索引如mongoDB用的B树,大部分关系型数据库索引使用的 ...

随机推荐

  1. Web Service(一):初识

    1. 前言 cxf 在项目中应用好久了,一直没有写总结,现在补上. 由于cxf 属于Web Service的一个实现,所以先学习和总结一下Web Service,作为学习cxf的基础. 2. WebS ...

  2. 系统的Drawable(二)-Selector

    系统的Drawable(二)-Selector Selector漫谈 Selector是定义 StateListDrawable 的标签,该Drawable代表着一个Drawable的集合,每一个Dr ...

  3. 什么情况下,会用到fiddler或者charles?

    有的页面,比如设限制的html页面,比如原生页面,只能在手机APP里面查看,无法在电脑浏览器中打开查看,这时候,需要用fiddler抓包来查看返回数据,定位问题.

  4. 【优先队列+贪心】BZOJ1826-[JSOI2010]缓存交换

    ……啊开始颓了. [题目大意] 已知当前集合最大容量为m,n个询问.每次询问一个元素,如果集合中没有则需要加入该元素,如果集合已经满了则需要先删去集合中的某些元素再加入.问至少要加入几次元素? [思路 ...

  5. 有向图强连通分量的Tarjan算法和Kosaraju算法

    [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极 ...

  6. [Java]类的生命周期(下)类的初始化[转]

    上接深入java虚拟机——深入java虚拟机(二)——类加载器详解(上),在上一篇文章中,我们讲解了类的生命周期的加载和连接,这一篇我们接着上面往下看. 类的初始化:在类的生命周期执行完加载和连接之后 ...

  7. pop3_用Java发送图文并茂的HTML邮件

    package com.syj; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.i ...

  8. 使用 IntraWeb (15) - 基本控件之 TIWEdit、TIWMemo、TIWText

    TIWEdit //单行文本框, 通过 PasswordPrompt 属性可以作为密码框 TIWMemo //多行文本框 TIWText //相当于多行的 TIWLabel 或不能编辑的 TIWMem ...

  9. oracle 11g wm_concat 、 listagg 函数的使用(合并数据)

    方法一 wn_concat() 函数 1.把以下图中Name一样的数据合并为一条,而且NO的值要这样显示如 C.1,C.2 2.实现这种效果的操作如下,先把Name的值进行分组(group by),再 ...

  10. Day14 自己定义泛型类的使用

    泛型的引入和体现: 问题:集合中能够存储各种类型的元素,可是由于集合能够存储各种类型数据.在获取集合中元素时,就会造成数据不安全. public class GenericDemo { public ...