二叉树、自平衡二叉树和多叉树详解

一、简介

       记得当初在大学的时候,学过数据结构这门课程,由于当时不好好学习,只是为了应付考试,没有进行深入的理解,等到参加工作之后,才发现原来好多的底层都用到了数据结构,出来混迟早是要还的,今天简单的来聊一聊数据库中的树。

二、树的分类

2.1 简单分类

       我想系统的整理一下树,先百度一下树的分类,看看在计算机科学当中都有哪些树,如下图:

       我们发现树简直是太多了,不可能一个个的去了解它们,所以我今天只介绍下下面这几棵常见的树,如图所示:

2.2 基础知识

       树:包含 n(n>0) 个结点的有穷集,每个元素成为结点 (node),有一个特定的结点被称为根结点或树根 (root)

       结点的度:一个结点包含的子树的个数称为该结点的度

       叶子结点和终端结点:度为 0 的结点。非终端结点或分支结点:度不为 0 的结点

       双亲结点或父结点:若一个结点含有结结点,则这个结点称为其子结点的父结点

       孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点

       兄弟结点:具有相同父结点的结点互称为兄弟结点

       树的度:一颗树中,最大的结点的度称为树的度

       结点的层次:从树开始定义起,根为第一层,根的子结点称为第二层,以此类推

       树的高度或深度:树结点中最大的层次

       堂兄弟结点:双亲在同一层次的结点互称为堂兄弟

       结点的祖先:从根到该结点所经分支上的所有结点

       子孙:以某结点为根的子树中任一结点都称为该结点的子孙

       森林:由m棵互不相交的树的集合称为森林

        如图所示:这就是一棵普通的树,不属于二叉树,因为它有 3 个叉

三、二叉树

3.1 定义

        二叉树的每个结点最多只有两棵子树(不存在度大于 2 的结点),二叉树的子树有左右之分,次序不能颠倒,二叉树的第 i 层最多有 2^(i-1) 个结点,深度为 k 的二叉树至多有 (2^k-1) 个结点。

        二叉树分为:完美二叉树、完全二叉树、二叉查找树。

3.2 完美二叉树

        又称为满二叉树,即一颗深度为 且有 2^(k-1) 个结点的二叉树称为满二叉树,如下图所示:

3.3 完全二叉树

        在一棵二叉树中,除了最后一层,都是满的,并且最后一层或者是满的,或者是右边缺少连续若干点,称为完全二叉树,如下图所示,左边是完全二叉树,右边则不属于完全二叉树。

3.4 二叉查找树

       又称为叉搜索树、二叉排序树它或者是一颗空树,或者具有下列性质的二叉树:若它的左子树不空,则左子树上所有的结点的值均小于它的根结点的值;若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;它的左、右子树也分别为二叉排序树。(不存在相等值的结点)如下图所示:

二叉搜索树必须满足以下几个条件:

       1、所有子树上面的结点的值都比根结点要小,右结点的值都比根结点的值大。

       2、任意结点的左右子树也都是二叉查找树。

       3、通过中序遍历,将得到一个有序的数列。

       4、对其操作的最优的时间复杂度为 O(log2N) 相当于对数列进行二分查找法。最坏的时间复杂度为 O(N),相当于线性查找。为元素的个数。

四、自平衡二叉树

4.1 出现原因

       自平衡二叉树出现的原因是为了解决叉查找树退化成链表的问题(退化成链表,时间复杂度变成了 O(N) )。

4.2 平衡因子

        结点的平衡因子是结点的左子树减去右子树的高度。 平衡二叉树为每个结点的平衡因子都为 1、-1、0 的二叉排序树 平衡二叉树的目的是为了减少二叉查找树层次,提高查找速度。

4.3 定义

        自平衡二叉树是一种结构平衡的叉搜索树,即叶结点高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树。

        自平衡二叉树分为:平衡二叉树和红黑树

4.4 平衡二叉树

        平衡二叉树又称为 AVL 树,它是二叉查找树最优的情况,把插入、查找、删除的时间复杂度最好情况和最坏情况都维持在 O(logN),但是频繁旋转当发生插入、删除、修改时会发生旋转会使插入和删除牺牲掉 O(logN) 左右的时间,不过相对二叉查找树来说,时间上稳定了很多。

二叉搜索树必须满足以下几个条件:

        1、它是一个二叉查找树。

        2、它的左右两棵子树的高度差绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树。

        3、当删除、新  增、修改结点上的值时,它会通过左旋或右旋使二叉树保持平衡。

        4、最坏的时间复杂度为 O(log2N)

                  

4.5 红黑树

4.5.1 出现原因

        是为了解决平衡二叉树在删除等操作时需要频繁调整的情况。

4.5.2 定义

        红黑树是一种弱平衡二叉树(叶结点高度差的绝对值可能会超过 1,由于是弱平衡,可以看到,在相同的结点情况下,AVL 树的高度低于红黑树,即红黑树会很高。但是再高也不会出现 2 倍的长度)。

       可能关于不会出现 2 倍的长度,这个地方会有些问题,我也是想了好久才想明白,意思就是说 AVL 规定左右子树高度差不能超过 1,而红黑树左右子树高度可以超过 1,那最多可以超过多少呢?答案是最长的子树不能超过最短的子树的 2 倍,假设一棵子树长度是 8,那么另外一棵最多不能超过16,可以是15;最少不能低于 5,如果是 4,那么 就是 的二倍了,也不满足红黑树的要求了。

       它也是一种二叉查找树,但在每个结点增加一个存储位表示结点的颜色,可以是红或黑(非红即黑)。通过对任何一条从根到叶子的路径上各个结点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍(这里是和平衡二叉树的主要区别)。

       相对于要求严格的 AVL 树来说,红黑树的旋转次数少,所以对于搜索,插入,删除操作较多的情况下,我们就用红黑树。如果应用场景中对插入删除不频繁,只是对查找要求较高,那么 AVL 还是较优于红黑树。

4.5.3 特性

       1、对于黑平衡是指,从根结点开始搜索,一直搜索到叶子结点,所经历的黑色结点的个数是一样的。

       2、黑平衡二叉树,严格意义上,不是平衡二叉树。 左右子树的高度差可能大于 1

       3、时间复杂度是 O(logn),最大高度:2logn

       4、红黑树不会像二分搜索树一样退化为链表。 查找的时间上面会比 AVL 树慢一点,因为最大高度为 2logn 添加操作和删除操作比 AVL 树要快一些

       5、根结点是黑色的

       6、每个叶子结点都是黑色的(叶子是 NIL 结点)

       7、每个红色的结点必须有两个黑色结点

       8、NIL 结点就是空结点,二叉树中用 NIL 结点代替 NULL

       9、主要用来存储有序的数据时间复杂度为 O(logN)

      10、java 中的 TreeSetTreeMap 就是他的实现方式。

五、多叉树

5.1 背景

       前面已经简单的介绍了二叉树和自平衡二叉树,这两种树查找效率已经足够高了,那为什么还有多叉树的出现呢?难道它两的时间复杂度比二叉查找树还小吗?

       答案当然不是,多叉树的出现是因为另外一个问题,那就是磁盘 IO。众所周知,IO 操作的效率很低,如果应用场景内数据的存储量非常大,而我们在查询时不能一下子将所有数据加载到内存中,只能逐一加载磁盘页,每个磁盘页对应树的结点。造成大量磁盘 IO 操作(最坏情况下为树的高度)。(进行一次 IO,需要加载一个磁盘页、每个磁盘也存储一个结点的数据,当树很高时,结点就很多,此时需要进行很多次 IO)。

5.2 解决磁盘 IO

       平衡二叉树由于树深度过大而造成磁盘 IO 读写过于频繁,进而导致效率低下。所以,我们为了减少磁盘 IO 的次数,就你必须降低树的深度,将 “瘦高” 的树变得 “矮胖” 。即:每个结点存储多个元素、摒弃二叉树结构,采用多叉树。

        这样就引出来了一个新的查找树结构 ——多路查找树。 根据 AVL 给我们的启发,一颗平衡多路查找树(B 树)自然可以使得数据的查找效率保证在 O(logN) 这样的对数级别上。

5.3 概念纠正

       之前有看到有很多文章把 树和 B-tree 理解成了两种不同类别的树,其实这两个是同一种树。

5.4 定义

       树和平衡二叉树稍有不同的是 B 树属于多叉树,又名平衡多路查找树(查找路径不止两个),数据库索引技术里大量使用 B 树和 B+ 树的数据结构。

        虽然要降低树的高度,但是不能设计成无限多路,那就会变成有序数组,有悖于设计的初衷了。

5.5 B树

5.5.1 特征

        一个 阶的 树具有如下几个特征:树中所有结点的孩子结点最大值称为 树的阶,通常用 表示。一个结点有 个孩子时,必有 k-1 个关键字才能将子树中所有关键字划分为 个子集。

5.5.2 示例

        下面就是一棵 阶的 B 树:

以此图为例:若查询的数值为 9:    

       第一次磁盘 IO:在内存中定位(与 1735 比较),比 17 小,左子树;    

       第二次磁盘 IO:在内存中定位(与12 比较),比 大,比 12 小,中间子树;  

       第三次磁盘 IO:在内存中定位(与 910 比较),找到 9,终止。

       整个过程中,我们可以看出:比较的次数并不比二叉查找树少,尤其适当某一结点中的数据很多时,但是磁盘 IO 的次数却是大大减少。比较是在内存中进行的,相比于磁盘 IO 的速度,比较的耗时几乎可以忽略。所以当树的高度足够低的话,就可以极大的提高效率。相比之下,结点中的元素多点也没关系,仅仅是多了几次内存交互而已,只要不超过磁盘页的大小即可。

5.5.3 注意

       1、B 树主要用于文件系统以及部分数据库索引,例如: MongoDB。而大部分关系数据库则使用 B+ 树做索引,例如:mysql 数据库;

       2、从查找效率考虑一般要求 树的阶数 m >= 3;

       3、B 树上算法的执行时间主要由读、写磁盘的次数来决定,故一次 IO 操作应读写尽可能多的信息。因此 树的结点规模一般以一个磁盘页为单位。一个结点包含的关键字及其孩子个数取决于磁盘页的大小。

5.6 B+树

5.6.1 定义

        B+ 树是 树的一个升级版,相对于 树来说 B+ 树更充分的利用了结点的空间,让查询速度更加稳定,其速度完全接近于二分法查找。

       B+ 树所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。

5.6.2 示例

       下面是一棵 3 阶的 B+ 树:

       1、B+ B 树不同 B+ 树的非叶子结点不保存关键字记录的指针,只进行数据索引,这样使得 B+ 树每个非叶子结点所能保存的关键字大大增加;

       2、B+ 树叶子结点保存了父结点的所有关键字记录的指针,所有数据地址必须要到叶子结点才能获取到。所以每次数据查询的次数都一样;

       3、B+ 树叶子结点的关键字从小到大有序排列,左边结尾数据都会保存右边结点开始数据的指针。

       4、B+ 树通常有两个指针,一个指向根结点,另一个指向关键字最小的叶子结点。因些,对于 B+ 树进行查找两种运算:一种是从最小关键字起顺序查找,另一种是从根结点开始,进行随机查找。

5.6.3 优势

        B+ 树的优势在于查找效率上。B+ 树的查找和树一样,类似于二叉查找树。起始于根结点,自顶向下遍历树,选择其分离值在要查找值的任意一边的子指针。在结点内部典型的使用是二分查找来确定这个位置。

        不同的是,B+ 树中间结点没有卫星数据(索引元素所指向的数据记录),只有索引,而树每个结点中的每个关键字都有卫星数据;这就意味着同样的大小的磁盘页 B+ 树可以容纳更多结点元素,在相同的数据量下,B+ 树更加 “矮胖”,IO 操作更少 。

5.6.4 分析

       树所有的结点都包含卫星数据:

        B+ 树只有叶子结点包含卫星数据:

        在数据库的聚集索引(Clustered Index)中,叶子结点直接包含卫星数据。在非聚集索引(NonClustered Index)中,叶子结点带有指向卫星数据的指针。

        因为卫星数据的不同,导致查询过程也不同; 树的查找只需找到匹配元素即可,最好情况下查找到根结点,最坏情况下查找到叶子结点,所说性能很不稳定,而 B+ 树每次必须查找到叶子结点,性能稳定。

        在范围查询方面,B+ 树的优势更加明显 B树的范围查找需要不断依赖中序遍历。首先二分查找到范围下限,在不断通过中序遍历,知道查找到范围的上限即可。整个过程比较耗时。(sql 查询都是范围查找) 而 B+ 树的范围查找则简单了许多。首先通过二分查找,找到范围下限,然后同过叶子结点的链表顺序遍历,直至找到上限即可,整个过程简单许多,效率也比较高。 

5.7 对比

        1、B+ 树单一结点存储更多的元素,使得查询的 IO 次数更少;

        2、B+ 树所有查询都要查找到叶子结点,查询性能稳定;

        3、B+ 树所有叶子结点形成有序链表,便于范围查询。

5.8 索引为什么用 B+ 树

       如果用 hash 来存储索引,时间复杂度为 O(1),那为啥 mysql 要用 B+ 树来存储索引呢? 这个和业务场景有关,如果只选择一个数据,那确实是 hash 快。

        但是数据库经常选择多条,这时候由于 B+ 树索引有序,并且又有链表相连,它的查询效率比 hash 就快很多了。而且数据库中的索引一般是在磁盘上,数据量大的情况可能无法一次装入内存,B+ 树的设计可以允许数据分批加载,同时树的高度较低,提高查找效率。

六、遍历二叉树

6.1 先序遍历

6.1.1 访问方式

       1、访问根结点

       2、访问当前结点的左子树

       3、若当前结点无左子树,则访问当前结点的右子树

       4、采用先序遍历得到的序列为 1、2、4、5、3、6、7 

6.2 中序遍历

6.2.1 访问方式

        1、访问当前结点的左子树

        2、访问根结点

        3、访问当前结点的右子树

        4、采用中序遍历得到的序列为 4、2、5、1、6、3、7

3.3 后序遍历

6.3.1 访问方式

       1、从根结点出发,依次遍历各结点的左右子树,直到当前结点左右子树遍历完成后,才访问该结点元素。

       2、采用后序遍历得到的序列为 4、5、2、6、7、3、1

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐的小三菊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值