二叉树的定义
二叉树是一种树形数据结构,其中每个节点最多有两个子节点,分别称为左子节点和右子节点。二叉树具有以下特点:
- 每个节点最多有两个子节点。
- 每个节点都有一个父节点,除了根节点。
二叉树的性质
- 二叉树的深度为 ,则二叉树的最大节点数为
- 二叉树的节点数为 ,则二叉树的深度至少为
- 二叉树的节点数为 ,则二叉树的叶子节点数为
- 二叉树的节点数为 ,则二叉树的非叶子节点数为
二叉树的分类
满二叉树
如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树。
完全二叉树
如果二叉树中除了最后一层,其他各层都被完全填充,并且最后一层的节点都靠左对齐,则此二叉树称为完全二叉树。 因此,完全二叉树特别适合用数组来存储,因为它的节点可以按照层序遍历的顺序存储在数组中,且没有空洞。 完全二叉树的性质:
- 完全二叉树的深度为 ,则完全二叉树的节点数在 到 之间。
- 完全二叉树的节点数为 ,则完全二叉树的深度为
- 完全二叉树的节点数为 ,则完全二叉树的叶子节点数为
- 完全二叉树的节点数为 ,则完全二叉树的非叶子节点数为
二叉搜索树
二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树,其中每个节点的值都满足以下条件:
- 左子树中所有节点的值都小于该节点的值。
- 右子树中所有节点的值都大于该节点的值。
二叉搜索树的性质:
- 二叉搜索树的中序遍历结果是一个有序的序列。
- 二叉搜索树的平均时间复杂度为 ,最坏时间复杂度为 ,取决于树的平衡程度。
平衡二叉树
平衡二叉树是一种特殊的二叉搜索树,其中每个节点的左右子树的高度差不超过1。
平衡因子:对于二叉树中的每个节点,计算其左子树的高度与右子树的高度之差,称为该节点的平衡因子。平衡二叉树结点的平衡因子的值只能是-1、0或1。
最小不平衡二叉树:距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树,称为最小不平衡树。
对于不配平衡二叉树,可以通过旋转操作来恢复平衡。常见的旋转操作包括:旋转操作包括:
- 右旋(Right Rotation):当某个节点的左子树过高时,进行单右旋转。指将根节点的左侧往右拉,原先的左子节点变成新的父节点,并把多余的右子节点出让,给已经降级的根节点当左子节点
- 左旋(Left Rotation):当某个节点的右子树过高时,进行单左旋转。指将根节点的右侧往左拉,原先的右子节点变成新的父节点,并把多余的左子节点出让,给已经降级的根节点当右子节点
AVL树
AVL树是一种自平衡二叉搜索树,其中每个节点的平衡因子只能是-1、0或1。当插入或删除节点导致某个节点的平衡因子变为-2或2时,需要进行旋转操作来恢复平衡。
红黑树
红黑树是一种二叉搜索树,但在每个结点上增加一个存储位表示结点颜色(Red或Black)。 通过限制着色方式,红黑树确保没有一条路径会比其他路径长出两倍,从而接近平衡。 因而,红黑树是相对接近平衡的二叉树,并不是一个完美平衡二叉查找树。
红黑树的性质:
- 每个节点要么是红色,要么是黑色。
- 根节点是黑色。
- 每个叶子节点(NIL节点)是黑色。
- 如果一个节点是红色的,则它的两个子节点都是黑色的。
- 从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点。
红黑树的插入和删除操作需要通过旋转和重新着色来维护红黑树的性质,以确保树的平衡。
红黑树不要求严格高度平衡,而是要求从任一节点到叶子(NIL)的每条路径黑高相同,从而保证整体近似平衡。
红黑树插入时的旋转与着色过程
插入新节点时,先按二叉搜索树规则插入,并将新节点标记为红色。之后根据父节点、叔叔节点、祖父节点的颜色和位置关系进行修复。
步骤1:普通BST插入
- 找到插入位置,将新节点作为叶子插入。
- 新节点初始颜色设为红色(这样不会立即破坏黑高一致性)。
步骤2:检查是否违反性质 只有当“父节点也是红色”时才会违反性质4(红节点不能有红孩子),此时需要修复。
步骤3:按Case修复(以父节点是祖父左孩子为例)
- Case 1(叔叔为红): 父节点和叔叔节点都染黑,祖父节点染红,然后将“当前节点”上移到祖父,继续向上检查。
- Case 2(叔叔为黑,且当前节点是内侧): 例如父是左孩子、当前是父的右孩子。先对父节点做一次左旋,把它转成Case 3的外侧结构。
- Case 3(叔叔为黑,且当前节点是外侧): 例如父是左孩子、当前是父的左孩子。先将父染黑、祖父染红,再对祖父做一次右旋,修复完成。
当父节点是祖父的右孩子时,处理完全对称:
- Case 1仍是变色上移。
- Case 2先右旋父节点。
- Case 3再左旋祖父节点并配合变色。
步骤4:收尾 无论中间如何旋转或变色,最后都要把根节点染成黑色。
记忆口诀
- 叔红只变色,上移看祖父。
- 叔黑先折线变直线(Case 2转Case 3)。
- 叔黑直线就旋祖父并交换父祖颜色。
红黑树和AVL树的比较:
- 平衡性:AVL树比红黑树更严格地保持平衡,因此AVL树的查询性能通常优于红黑树。然而,红黑树在插入和删除操作时更快,因为它允许更大的不平衡。
- 插入和删除:AVL树在插入和删除节点时可能需要多次旋转来保持平衡,而红黑树通常只需要一次旋转。
- 内存使用:AVL树需要存储每个节点的平衡因子,而红黑树只需要存储每个节点的颜色,因此红黑树在内存使用上更为高效。
- 应用场景:AVL树适用于查询频繁且插入和删除较少的场景,而红黑树适用于插入和删除频繁的场景,如操作系统中的进程调度和数据库索引。
- 编写复杂度:AVL树的实现相对复杂,因为需要处理更多的旋转情况,而红黑树的实现相对简单。
二叉树的存储结构
顺序存储
顺序存储通常使用数组表示二叉树,常见于完全二叉树(如堆)。
若根节点下标从0开始,则有:
- 下标为
i的节点,其左孩子下标为2i + 1 - 下标为
i的节点,其右孩子下标为2i + 2 - 下标为
i的节点,其父节点下标为(i - 1) >> 1
如果根节点下标从1开始,则对应公式为:左孩子 2i,右孩子 2i + 1,父节点 i // 2。
优点:
- 按下标可 访问父子节点。
- 实现简单,缓存局部性好。
缺点:
- 对非完全二叉树会出现大量空位,浪费空间。
- 插入、删除中间节点不灵活。
链式存储
链式存储通过节点对象保存数据和左右指针,最常见定义是:val、left、right。
优点:
- 适合任意形态的二叉树,不会因为空位浪费大量空间。
- 插入、删除节点更灵活。
缺点:
- 每个节点有额外指针开销。
- 访问父节点不方便(若需要可增加
parent指针)。
TIP工程中若树结构经常变化,通常优先使用链式存储。
二叉树的遍历
前序遍历
访问顺序:根 -> 左 -> 右。
常见用途:
- 复制一棵树。
- 序列化树结构(可配合空节点标记)。
实现思路:
- 递归:先访问根,再递归左子树和右子树。
- 迭代:用栈,先压右子节点,再压左子节点。
中序遍历
访问顺序:左 -> 根 -> 右。
核心性质:
- 对二叉搜索树,中序结果是升序序列。
实现思路:
- 递归:左子树完成后访问根,再访问右子树。
- 迭代:一路向左入栈,弹出访问后转向右子树。
后序遍历
访问顺序:左 -> 右 -> 根。
常见用途:
- 删除树(先删子节点再删父节点)。
- 计算树高、子树汇总信息。
实现思路:
- 递归:左右子树都处理完成后再访问根。
- 迭代:可使用双栈,或“访问标记法”单栈实现。
层序遍历
访问顺序:按层从上到下、从左到右。
实现思路:
- 使用队列。
- 根节点入队。
- 每次出队一个节点并访问,再将其左右孩子入队。
常见用途:
- 求树的最大宽度。
- 按层打印、锯齿形遍历、最短层数问题。
二叉树的构建
常见构建方式:
- 已知前序 + 中序:可唯一确定一棵二叉树(节点值互异时)。
- 已知后序 + 中序:同样可唯一确定一棵二叉树(节点值互异时)。
- 已知层序 + 空节点标记:可按队列逐层恢复树。
构建的关键在于:
- 通过中序划分左右子树边界。
- 在前序或后序中定位当前子树根节点。
- 递归处理左右区间。
时间复杂度通常为 (配合哈希表记录中序下标)。
二叉树迭代器
二叉树迭代器是把“遍历过程状态化”的一种封装,使外部能像遍历数组一样逐个取节点。
以BST中序迭代器为例(升序输出):
- 初始化时把根到最左路径全部压栈。
next():弹出栈顶作为当前最小值;若其有右子树,则将右子树的最左路径继续压栈。hasNext():判断栈是否为空。
复杂度:
- 单次
next()均摊为 。 - 额外空间为 ,其中 是树高。