二叉树
树
比如下面这幅图,A 节点就是 B 节点的父节点,B 节点是 A 节点的子节点。B、C、D 这三
个节点的父节点是同一个节点,所以它们之间互称为兄弟节点。我们把没有父节点的节点叫
作根节点,也就是图中的节点 E。我们把没有子节点的节点叫作叶子节点或者叶节点,比如
图中的 G、H、I、J、K、L 都是叶子节点。

二叉树
二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子节点和右 子节点。
二叉树中,叶子节点全都在最底层,除了叶子节点之外,每个节点都有左 右两个子节点,这种二叉树就叫作满二叉树。
二叉树中,叶子节点都在最底下两层,最后一层的叶子节点都靠左排列,并且除 了最后一层,其他层的节点个数都要达到最大,这种二叉树叫作完全二叉树。
二叉树的遍历
遍历方式

|
|
二叉查找树(Binary Search Tree)
二叉查找树要求,在树中的任意一个节点,其左子树 中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。
查找操作
首先,我们看如何在二叉查找树中查找一个节点。我们先取根节点,如果它等于我们要查找 的数据,那就返回。如果要查找的数据比根节点的值小,那就在左子树中递归查找;如果要 查找的数据比根节点的值大,那就在右子树中递归查找。
|
|
插入操作
如果要插入的数据比节点的数据大,并且节点的右子树为空,就将新数据直接插到右子节点 的位置;如果不为空,就再递归遍历右子树,查找插入位置。同理,如果要插入的数据比节 点数值小,并且节点的左子树为空,就将新数据插入到左子节点的位置;如果不为空,就再 递归遍历左子树,查找插入位置。
|
|
删除操作
|
|
其他操作
支持快速地查找最大节点和最小节 点、前驱节点和后继节点。
支持重复数据的二叉查找树
针对的都是不存在键值相同的情况。那如果存储的两个对 象键值相同,这种情况该怎么处理呢?
第一种方法比较容易。二叉查找树中每一个节点不仅会存储一个数据,因此我们通过链表和 支持动态扩容的数组等数据结构,把值相同的数据都存储在同一个节点上。
第二种方法比较不好理解,不过更加优雅。
每个节点仍然只存储一个数据。在查找插入位置的过程中,如果碰到一个节点的值,与要插 入数据的值相同,我们就将这个要插入的数据放到这个节点的右子树,也就是说,把这个新 插入的数据当作大于这个节点的值来处理。
当要查找数据的时候,遇到值相同的节点,我们并不停止查找操作,而是继续在右子树中查 找,直到遇到叶子节点,才停止。这样就可以把键值等于要查找值的所有节点都找出来
对于删除操作,我们也需要先查找到每个要删除的节点,然后再按前面讲的删除操作的方 法,依次删除。
什么是“平衡二叉查找树”?
平衡二叉树的严格定义是这样的:二叉树中任意一个节点的左右子树的高度相差不能大于 1。
平衡二叉查找树中“平衡”的意思,其实就是让整棵树左右看起来比较“对称”、比 较“平衡”,不要出现左子树很高、右子树很矮的情况。这样就能让整棵树的高度相对来说 低一些,相应的插入、删除、查找等操作的效率高一些。
如何定义一棵“红黑树”?
红黑树的英文是“Red-Black Tree”,简称 R-B Tree。它是一种不严格的平衡二叉查找 树
一棵红黑 树还需要满足这样几个要求:
- 根节点是黑色的;
- 每个叶子节点都是黑色的空节点(NIL),也就是说,叶子节点不存储数据;
- 任何相邻的节点都不能同时为红色,也就是说,红色节点是被黑色节点隔开的;
- 每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点;
为什么说红黑树是“近似平衡”的?
平衡”的意思可以等价为性能不退化。“近似平衡”就等价为性能不会 退化的太严重。
二叉查找树很多操作的性能都跟树的高度成正比。一棵极其平衡的二叉 树(满二叉树或完全二叉树)的高度大约是 log2n,所以如果要证明红黑树是近似平衡的, 我们只需要分析,红黑树的高度是否比较稳定地趋近 log2n 就好了。
首先,我们来看,如果我们将红色节点从红黑树中去掉,那单纯包含黑色节点的红黑树的高 度是多少呢?
红色节点删除之后,有些节点就没有父节点了,它们会直接拿这些节点的祖父节点(父节点 的父节点)作为父节点。所以,之前的二叉树就变成了四叉树。
前面红黑树的定义里有这么一条:从任意节点到可达的叶子节点的每个路径包含相同数目的 黑色节点。我们从四叉树中取出某些节点,放到叶节点位置,四叉树就变成了完全二叉树。 所以,仅包含黑色节点的四叉树的高度,比包含相同节点个数的完全二叉树的高度还要小。
我们现在知道只包含黑色节点的“黑树”的高度,那我们现在把红色节点加回去,高度会变 成多少呢?
在红黑树中,红色节点不能相邻,也就是说,有一个 红色节点就要至少有一个黑色节点,将它跟其他红色节点隔开。红黑树中包含最多黑色节点 的路径不会超过 log2n,所以加入红色节点之后,最长路径不会超过 2log2n,也就是说, 红黑树的高度近似2log2n
实现红黑树的基本思想
插入操作的平衡调整
红黑树规定,插入的节点必须是红色的。而且,二叉查找树中新插入的节点都是放在叶子节 点上。所以,关于插入操作的平衡调整,有这样两种特殊情况,但是也都非常好处理。
- 如果插入节点的父节点是黑色的,那我们什么都不用做,它仍然满足红黑树的定义。
- 如果插入的节点是根节点,那我们直接改变它的颜色,把它变成黑色就可以了 除此之外,其他情况都会违背红黑树的定义,于是我们就需要进行调整,调整的过程包含两 种基础的操作:左右旋转和改变颜色
CASE 1:如果关注节点是 a,它的叔叔节点 d 是红色
- 将关注节点 a 的父节点 b、叔叔节点 d 的颜色都设置成黑色;
- 将关注节点 a 的祖父节点 c 的颜色设置成红色;
- 关注节点变成 a 的祖父节点 c;
- 跳到 CASE 2 或者 CASE 3。
CASE 2:如果关注节点是 a,它的叔叔节点 d 是黑色,关注节点 a 是其父节点 b 的右子节点
- 关注节点变成节点 a 的父节点 b;
- 围绕新的关注节点b 左旋;
- 跳到 CASE 3。
CASE 3:如果关注节点是 a,它的叔叔节点 d 是黑色,关注节点 a 是其父节点 b 的左子节点
- 围绕关注节点 a 的祖父节点 c 右旋;
- 将关注节点 a 的父节点 b、兄弟节点 c 的颜色互换。
- 调整结束。
删除操作的平衡调整
1. 针对删除节点初步调整
2. 针对关注节点进行二次调整
递归树与时间复杂度分析
递归的思想就是,将大问题分解为小问题来求解,然后再将小问题分解为小 小问题。这样一层一层地分解,直到问题的数据规模被分解得足够小,不用继续递归分解为 止。
如果我们把这个一层一层的分解过程画成图,它其实就是一棵树。我们给这棵树起一个名 字,叫作递归树。
实战一:分析快速排序的时间复杂度
快速排序的过程中,每次分区都要遍历待分区区间的所有数据,所以,每一层分区操作所遍
历的数据的个数之和就是n。我们现在只要求出递归树的高度 h,这个快排过程遍历的数据个数
就是 h * n,也就是说,时间复杂度就是 O(h * n)
我们知道,快速排序结束的条件就是待排序的小区间,大小为1,也就是说叶子节点里的数据规模是 1。
从根节点n到叶子节点 1,递归树中最短的一个路径每次都乘以1/10,最长的一个路径每次都乘以9/10.
通过计算,我们可以得到,从根节点到叶子节点的最短路径是 log10n,最长的路径是 log10/9 n

当分区比例是1 : 9时, 快速排序的时间复杂度仍然是 O(n log n)
实战二:分析斐波那契的时间复杂度
f(n)分解为 f(n-1)和f(n-2),每次数据规模都是 -1 或者-2.叶子节点的数据规模是 1 或者 2,
所以,从跟节点到叶子节点,每条的路径都是长短不一的
每次分解之后的合并操作只需要一次加法计算,我们把这次加法运算的时间消耗记作 1,从上往下,第一层的总时间消耗为 1, 第二层的总时间消耗为 2,第三层的总时间消耗就是 依次类推,第 k层的时间消耗就是 2^k-1,那整个算法的总的时间就是每层时间消耗之和
所以,该算法的时间复杂度就介于O(2^n)和O(2^(n/2))之间