这是我参与「第五届青训营 」笔记创作活动的第11天
本系列从MySQL数据库中InnoDB存储引擎使用的数据结构 B+ 树为引子,从最简单的二叉树这一树形结构逐渐探索,直到我们的目标: B+树和红黑树.
有关二叉搜索树
本篇文章我们继续讲解有关树的知识.首先为之前的二叉搜索树结个尾. 从二叉搜索树的知识中,我们可以知道,一棵二叉搜索树中最小的值在最左端的叶子节点,最大值在最右端的叶子节点.
graph TD
8 --> 4
8 --> 10
4 --> 2
4 --> 6
2 --> 1
2 --> 3
6 --> 5
6 --> 7
10 --> 9
10 --> 11
二叉搜索树的遍历
对于二叉树中的任何一个节点来说,都有如下三个要素: 根 左子树 右子树 ,根据排列组合的知识,我们可以知道,这三个元素一共可以排列出 种排列方式. 其中, 我们讨论更具有特殊性的三种: DLR LDR LRD,它们分别为 前序遍历 中序遍历 后序遍历. 前序遍历指的是,对于每一个树,首先访问根节点,然后访问左子树,最后访问右子树. 中序遍历指的是,首先访问左节点,然后访问根节点,最后访问右节点. 后序遍历指的是,首先访问左节点,然后访问右节点,最后访问根节点. 这种遍历方法很适合使用递归的方法来实现.
此外,除了如上这种依靠树的节点之间的关系来遍历的方法,还有一种层序遍历.即按照节点的度从小到大从左到右依次遍历.
二叉平衡树
我们使用二叉搜索树的目的,顾名思义,是为了减少对数据进行插入和查找的时间. 在合理的情况下,我们每筛掉一棵树的左子树或者右子树,都能够减少搜索的范围,因此,我们自然希望这棵树的每个节点都具有左节点和右节点最好. 但是,如果这棵树退化成为了如下的树,每个节点仅含有左节点或右节点,那么从直观上来说,对这棵树进行搜索的耗时与对链表进行搜索的耗时没有区别,因为它已经退化成了链表.
graph TD
A --> B
B --> D
B --> E
A --> C
C --> F
C --> G
上图:理想的情况,除了最下层,每个节点都具有左右两个子节点.
下图:最坏的情况,每个节点都最多有一个子节点,这是它已经退化为链表.
graph TD
A --> B
B --> C
C --> D
D --> E
退化成链表的二叉树显然不能在每次访问时排除一半的节点,所以我们需要一种方法,避免在操作二叉树的时候不小心让它退化到链表形态.
思考: 链表不能保证每次查询时都排除一半的数据吗? 能! 只要我们使用二分查找法即可,而二分查找法的前提条件是数据需要有序,在二叉搜索树中我们也看到了数据是有序的.因此,我猜想,数据的有序性在数据结构和算法中非常重要.
那么,什么是二叉平衡树? 二叉平衡树即指的是,在这棵二叉树中,每一个节点的左子树和右子树的高度之差最多为1的树.
为了将一棵二叉搜索树保持为二叉平衡树,我们肯定需要将树的某些子树或节点移动一下位置.能随便移动吗?不能,因为我们至少要保证它是一棵二叉搜索树,即在移动的过程中必须要保证树中数据的有序性.
为了保证数据的有序性,同时还能调整树的平衡,有两种基本操作:左旋和右旋.右旋是指:对于一个节点A,将A的左子节点B向右上旋转,代替A称为根节点,将A节点向右下旋转称为B的右子树的根节点,B原来的右子树变为A的左子树.
graph TD
A --> B
A --> C
B --> D
B --> E
将A的左子节点B向右上旋转,并将A作为B的右子树,B原来的右子树变为A的左子树,A原来的右子树还是A的右子树,B的左子树还是B的左子树.
上图:右旋前
下图:右旋后
graph TD
B --> D
B --> A
A --> E
A --> C
通过这样的旋转,我们可以将二叉树转换为任何形状,既可以从二叉平衡树转换为链表,又可以将链表转换为二叉平衡树,同时不失数据的有序性.
利用这种方法,我们可以通过维护一棵二叉平衡树在搜索数据时获得更优的时间复杂度.