目录
前言
本人刚刚开始了解数据结构,所以本篇文章只讲了关于以下目录中数据结构的定义概念,这里不涉及在树中更改数据时,对树结构进行翻转操作及其他复杂情况的说明,以后系统学习数据结构的时候会重新总结,再记录一下关于数据结构的系列笔记。
数据结构练习网站 Data Structure Visualization。
一、二叉树
定义:即每个节点都最多只有两个子节点的树。
以下图片中数字仅代表节点位置,不代表节点内容。
根据二叉树的节点分布大概可以分为以下三种二叉树:满二叉树,完全二叉树,平衡二叉树。
1、特殊类型
⑴、满二叉树
从形象上来说满二叉树是一个绝对的三角形,也就是说它的最后一层全部是叶子节点,其余各层全部是非叶子节点,即都有左右子节点
,如果用数学公式表示,k表示深度,也就是层数,那么其第 k 层的节点数就是 n=2k-1 。也就是说满二叉树的节点数是一系列固定的数,比如说,1,3,7,15…如果节点数不是这个序列中的数,那么他肯定不是满二叉树,当然了,反之,是不成立的。
⑵、完全二叉树
完全二叉树用数学公式来讲就是对于 k 层的完全二叉树,其1~ k-1层为满节点,且其 k 层的节点数的范围是 2(k-2) < N < 2k-1;
完全二叉树和满二叉树的区别是,他的最后一行可能不是完整的,从形状上来说他是一个可能有缺失的三角形,但绝对是右方的连续部分缺失。
2、什么是二叉查找树?
⑴、二分查找算法
二分查找(binary search)
,又叫 折半查找
或 二分搜索
。二分查找
的使用,要有一个前提条件:要查找的数必须在一个有序数组里。在这个前提下,取中间位置数作为比较对象:
- 若要查找的值和中间数相等,则查找成功。
- 若小于中间数,则在中间位置的左半区继续查找。
- 若大于中间数,则在中间位置的右半区继续查找。
不断重复上述过程,直到查找成功或者查找区域变为 0,查找失败。
⑵、二叉查找树(BST)
二叉查找树(BST:Binary Search Tree)
也称为 二叉搜索树 、二分搜索树、有序二叉树或二叉排序树。就是实现二分查找算法的二叉树。
二叉查找树(BST)
可以是一颗空树,或者具有以下性质的二叉树。
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值。
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值。
- 它的左右子树也分别为二叉搜索树。
一般情况下 二分查找
比顺序查找在时间上要快很多,但是在频繁修改的表中采用 二分查找
,其效率是非常低下的,因为顺序表的修改操作效率低下,而二分查找的高效就是利用顺序表的索引来取值进行比较的。为了支持频繁的修改,我们需要采用链表这种数据结构。然而,单链表的查找效率又非常低,为了解决这一问题,二叉查找树(BST)
便诞生了。
如下最左边的图,最好的情况是一个完全二叉树,这样查找效率最高。
如下最右边的图,最坏的情况会退化成一个链表,这样查找效率最低。
下表时间复杂度是根据已知数值,对二叉搜索树进行操作的总结:
数据结构 | 增加 | 删除 | 修改 | 查找 |
---|---|---|---|---|
二叉树(最好情况) | O(logn) | O(logn) | O(logn) | O(logn) |
二叉树(一般情况) | O(logn)~O(n) | O(logn)~O(n) | O(logn)~O(n) | O(logn)~O(n) |
二叉树(最坏情况) | O(n) | O(n) | O(n) | O(n) |
注:树的高度和节点的关系就是以2为底,树的节点总数n的对数
3、自平衡二叉查找树
⑴、平衡二叉树(AVL)
平衡二叉树,又称 AVL树
,用于解决二叉排序树高度不确定的情况,如果二叉排序树的子树间的高度相差太大,就会让二叉排序树操作的时间复杂度升级为O(n),为了避免这一情况,就出现了平衡二叉树,使树的高度尽可能的小,其本质上是带了平衡功能的二叉查找树(二叉排序树,二叉搜索树)。
平衡二叉树的性质:
- 左子树和右子树的高度之差的绝对值小于等于1
- 左子树和右子树也是平衡二叉树
平衡因子(BF)= 节左子树的高度 - 节右子树的高度。
因此平衡二叉树所有节的平衡因子只能是-1、0、1,如下左图,是一个平衡二叉树,下图中红色数字为节的平衡因子(BF):
失衡
: 当我们在一个平衡二叉树上插入一个节点时,有可能会导致失衡,即出现平衡因子绝对值大于1 的节点,当插入节点后,其中其上6号节点失去了平衡,我们称该节点为 失衡节点
,我们必须重新调整树的结构,使之恢复平衡。
关于平衡二叉树的 失衡
和 恢复平衡
这里不做多说,可自行了解。
⑵、红黑树
通过上面的图片还有表格可以知道二叉搜索树的时间复杂度介于O(logN)和O(N)之间,而为了应对极端情况,红黑树就出现了,它是具备了某些特性的二叉搜索树,能解决非平衡树问题,红黑树是一种接近平衡的二叉树(说它是接近平衡因为它并没有像AVL树的平衡因子的概念,它只是靠着满足红黑节点的5条性质来维持一种接近平衡的结构,进而提升整体的性能,并没有严格的卡定某个平衡因子来维持绝对平衡)。
首先红黑树是一个二叉搜索树,其次红黑树的每个节点都带有颜色属性的二叉查找树,颜色是红色或黑色。对于任何有效的红黑树我们增加了如下的额外要求:
- 1、节点是红色或黑色。
- 2、根节点是黑色。
- 3、所有 NIL 叶子节点都是黑色,这里的叶子节点指的是最底层的空节点(外部节点)。
- 4、每个红色节点的两个子节点都是黑色。
- 5、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
由以上特性会出现这些情况:
- 特性4导致从每个叶子节点到根节点的路径上不能有两个连续的红色节点。
- 性质5导致没有路径能多于任何其他路径的两倍长。
- 最短路径:都是黑色节点;最长路径:一黑一红,交替出现;所以最长路径刚好是最短路径的2倍。
- 默认新插入的节点为红色:将节点设置为红色在插入时对红黑树造成的影响是小的,而黑色是最大的。
- 假如任意插入的节点是黑色节点,则连续插入两个黑色节点后,就不满足性质5(肯定有一边的黑色节点多于另外一边);
这些约束强制了红黑树的关键性质,结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。
数据结构\复杂度 | 增加 | 删除 | 修改 | 查找 |
---|---|---|---|---|
二叉搜索树(一般情况) | O(logn)~O(n) | O(logn)~O(n) | O(logn)~O(n) | O(logn)~O(n) |
平衡二叉树 | O(logn) | O(logn) | O(logn) | O(logn) |
红黑树 | O(logn) | O(logn) | O(logn) | O(logn) |
由于平衡二叉树和红黑树都是自平衡的二叉搜索树。平衡二叉树和红黑树分别由平衡因子和五个特性来控制平衡,所以他们的效率都是高效的。
⑶、AVL树 vs 红黑树
AVL树:
平衡标准比较严格:每个左右子树的高度差不超过1
最大高度是 1.44 ∗ log2 n + 2 − 1.328(100W个节点,AVL树最大树高28)
搜索、添加、删除都是 O(logn) 复杂度,其中添加仅需 O(1) 次旋转调整、删除最多需要 O(logn) 次旋转调整
红黑树:
平衡标准比较宽松:没有一条路径会大于其他路径的2倍
最大高度是 2 ∗ log2(n + 1)( 100W个节点,红黑树最大树高40)
搜索、添加、删除都是 O(logn) 复杂度,其中添加、删除都仅需 O(1) 次旋转调整
如何选择:
搜索的次数远远大于插入和删除,选择AVL树;搜索、插入、删除次数几乎差不多,选择红黑树
相对于AVL树来说,红黑树牺牲了部分平衡性以换取插入/删除操作时少量的旋转操作,整体来说性能要优于AVL树
红黑树的平均统计性能优于AVL树,实际应用中更多选择使用红黑树
参考:【数据结构】史上最好理解的红黑树讲解,让你彻底搞懂红黑树
二、B树
树家族是为了磁盘或其它存储设备而设计的一种平衡多路查找树。树上操作的时间通常由存取磁盘的时间和CPU计算时间这两部分构成,而CPU的速度非常快,所以B树的操作效率取决于访问磁盘的次数,关键字总数相同的情况下B树的高度越小,磁盘I/O所花的时间越少。
树的高度是命中查找的一个不可抗拒的时间下限。在一定的数据条件下,树的高度和宽度是互相制约的。(就像一定面积下,矩形的长和宽是互相制约的)而树家族中最简单的二叉树,尽管易于实现,却不能有实际的价值。其最最令人发指的是二叉树的高度太高。多叉树的提出和实现解决了二叉树的不足。
1、B-树
B-树就是B树,它是一种平衡的多叉树,不是B减树,而是B杠树,中文通常称为B树,英语称为B-tree。
由上面的平衡二叉树演变成平衡多叉查找树,依然保持基本规则:每个关键字的左侧子树与右侧子树的高度差的绝对值不超过1的查找树,用以解决树的高度问题。而B树限制更强,要求所有叶子节点都在同一层。
一棵 M
阶的B树满足下列性质:
- 1、根节点至少有两个子树,子树数量的范围是
2 ≤ 根节点子树数 ≤ M
; - 2、每个非根节点所包含的关键字个数满足:
ceil(M/2) ≤ 关键字个数 ≤ M-1
,其子树数为关键字个数加一,且这些关键字按照递增顺序排列; - 3、除根节点以外的所有节点(不包括叶子节点)的度数正好是关键字总数加1,故内部子树个数满足:
ceil(M/2) ≤ 子树个数 ≤ M
; - 4、所有的叶子节点都位于同一层。
B树每个节点的结构都包含关键字个数
,n+1个指针
,n个关键字
,指针指向子树位置,叶子节点中的指针全部用空指针表示,是查找失败到达的位置:
下面是一个B树结构图,数据是存在每个节点上的:
现在需要去查找关键字28,那么就需要3次磁盘IO,如果数据量越大,树的高度越高就会导致磁盘IO次数越多。
结构源码:
#define m 3 // B树的阶,此设为3
typedef int KeyType;
typedef struct {
KeyType key;
char data;
} Record;
typedef struct BTNode {
int keynum; // 节点中关键字个数,即节点的大小
struct BTNode *parent; // 指向双亲节点
KeyType key[m+1]; // 关键字向量,0号单元未用
struct BTNode *ptr[m+1]; // 子树指针向量
Record *recptr[m+1]; // 记录指针向量,0号单元未用
} BTNode, *BTree; // B树节点和B树的类型
typedef struct {
BTree pt; // 指向找到的节点
int i; // 1..m,在节点中的关键字序号
int tag; // 1:查找成功,0:查找失败
} Result; // 在B树的查找结果类型
2、B+树
B+树是B树的一种变形形式,B+树上的叶子节点存储关键字以及相应记录的地址,叶子节点以上各层作为索引使用。
一棵 M
阶的B+树定义如下:
- 1、每个节点至多有
M
个子树; - 2、除根节点外,每个节点有
ceil(M/2)
至M
个子树,根节点至少有两个子树; - 3、有
k
个子树的节点必有k
个关键字。 - 4、叶子节点包含了全部关键字和数据指针,叶子节点内的关键字有序排列,叶子节点间也是有序排列,指针相连。
- 5、所有非叶子节点可以看成是索引,仅包含其子树中最大(或最小)关键字的值。
- 6、所有的叶子节点都位于同一层。
B+树的查找与B树不同,当索引部分某个节点的关键字与所查的关键字相等时,并不停止查找,应继续沿着这个关键字左边的指针向下,一直查到该关键字所在的叶子节点为止。
由于B+树非叶子节点不存放数据地址,那这部分多出来的空间可以存放更多的关键字,这样一来B+树的高度也会比B树低,磁盘IO次数会更少。
以下是一个B+树结构图:
不过这里我有一个疑惑的地方,在 Data Structure Visualization 上测试所显示的结构并没有按照特性3显示,我不知道是不是 Data Structure Visualization 上显示问题。
3、B*树
这里直接转载百度百科。
B树是B+树的变体,在B+树的非根和非叶子节点再增加指向兄弟的指针;B树定义了非叶子节点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2)。
B+树的分裂:当一个节点满时,分配一个新的节点,并将原节点中1/2的数据复制到新节点,最后在父节点中增加新节点的指针;B+树的分裂只影响原节点和父节点,而不会影响兄弟节点,所以它不需要指向兄弟的指针;
B*树的分裂:当一个节点满时,如果它的下一个兄弟节点未满,那么将一部分数据移到兄弟节点中,再在原节点插入关键字,最后修改父节点中兄弟节点的关键字(因为兄弟节点的关键字范围改变了);如果兄弟也满了,则在原节点与兄弟节点之间增加新节点,并各复制1/3的数据到新节点,最后在父节点增加新节点的指针;
B*树分配新节点的概率比B+树要低,空间使用率更高;
4、B树和B+树简单对比
B+树和B树相比的主要区别:
1、就是B+树所有关键码都在叶子节点,树的高度矮,因为B+树存储量很大的层级一般在2–4层,经行磁盘io的次数和层数相同。
2、B+树的叶子节点是带有指针的,且叶节点本身按关键码从小到大顺序连接。
3、在搜索过程中,如果查询和内部节点的关键字一致,那么搜索过程不停止,而是继续向下搜索这个分支
为什么InnoDB选择B+树而不是B树:
1、B+树的磁盘读取代价低, B树每个节点都有data,B+树只有叶子节才有,假设每个节点大小16KB,那么B+树比B树能存储更多的关键字,一次性读入内存的关键字的内存也会更多,B+树的高度也会比B树低,磁盘IO次数会更少。
2、B+树对范围查询更友好,方便遍历,B树叶子节点没有链接,而B+树叶子节点通过双向指针链接,可以很方便的进行范围查询,比如where条件中 age >= 3 and age < 20,那么当找到3时就可以顺着指针找到20,而B树是不可以的。
3、B+树查询效率稳定性更好, 在B+树中,由于分支节点并不是最终指向文件内容的节点,分支节点只是叶子节点的索引,所以对于任意关键字的查找都必须从根节点走到分支节点,所有关键字查询路径长度相同,每个数据查询效率相当。而对于B树而言,其分支节点上也保存有数据,对于每一个数据的查询所走的路径长度是不一样的,效率也不一样,B树稳定性不如B+树好
三、AVL树,红黑树,B树,B+树都分别应用在哪些现实场景中
AVL树: 平衡二叉树,一般是用平衡因子差值决定并通过旋转来实现,左右子树树高差不超过1,那么和红黑树比较它是严格的平衡二叉树,平衡条件非常严格(树高差只有1),只要插入或删除不满足上面的条件就要通过旋转来保持平衡。由于旋转是非常耗费时间的。我们可以推出AVL树适合用于插入删除次数比较少,但查找多的情况。
- 应用相对其他数据结构比较少。windows对进程地址空间的管理用到了AVL树。
红黑树: 平衡二叉树,通过对任何一条从根到叶子的简单路径上各个节点的颜色进行约束,确保没有一条路径会比其他路径长2倍,因而是近似平衡的。所以相对于严格要求平衡的AVL树来说,它的旋转保持平衡次数较少。用于搜索时,插入删除次数多的情况下我们就用红黑树来取代AVL。红黑树应用比较广泛:
- 广泛用在C++的STL中。map和set都是用红黑树实现的。
- 著名的linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块。
- epoll在内核中的实现,用红黑树管理事件块
- nginx中,用红黑树管理timer等
- Java的TreeMap实现
B树,B+树: 它们特点是一样的,是多路查找树,一般用于数据库中做索引,因为它们分支多层数少,因为磁盘IO是非常耗时的,而像大量数据存储在磁盘中所以我们要有效的减少磁盘IO次数避免磁盘频繁的查找。
B+树是B树的变种树,有n棵子树的节点中含有n个关键字,每个关键字不保存数据,只用来索引,数据都保存在叶子节点。用在文件系统数据索引。
B+树相对B树磁盘读写代价更低:因为B+树非叶子结点只存储键值,单个节点占空间小,索引块能够存储更多的节点,从磁盘读索引时所需的索引块更少,所以索引查找时I/O次数较B-Tree索引少,效率更高。而且B+Tree在叶子节点存放的记录以链表的形式链接,范围查找或遍历效率更高。MySQL InnoDB用的就是B+Tree索引。