「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」
什么是树 🌲
-
树是一种分层数据的抽象模型
- 现实生活中最常见的树的例子是家谱或是公司的组织架构图
-
一个树结构包含一系列存在父子关系的节点
- 每个节点都有一个父节点(除了顶部的第一个节点)以及零个或多个子节点
-
下面为二叉树的示例:
-
用代码来表示上述 树节点
// 链表节点
class ListNode {
val: number
next: ListNode | null
constructor(val?: number, next?: ListNode | null) {
this.val = (val === undefined ? 0 : val)
this.next = (next === undefined ? null : next)
}
}
// 树节点
class TreeNode {
val: number
child: TreeNode[] | null
constructor(val?: number, child?: TreeNode[] | null) {
this.val = (val === undefined ? 0 : val)
this.child = (child === undefined ? null : left)
}
}
- 可以看到,链表就是一种特殊的树结构
- 如图中:1->4->7->null
- 树的有一个重要属性叫
度,指的就是每个节点的子节点个数- 图中,1 号节点的度为 3,2号节点的度为 1,3 号节点的度为 0,4 号节点的度为 2
二叉树
- 二叉树的特性
- 每个节点的度最多为 2
- 度为 0 的节点比度为 2 的节点数量上多 1 个
证明二叉树特点:
- 1、很明显,n 个节点的树,有 n-1 条边
- 2、度为 0 的节点记为 n0,度为 1 的节点记为 n1,度为 2 的节点记为 n2,所以 n0 + n1 + n2 = n
- 3、树的所有边的数量为 n0 * 0 + n1 * 1 + n2 * 2 = n - 1
- 4、n0 + n1 + n2 - 1 = n1 + 2 * n2
- 5、n0 = n2 + 1
二叉树的三种重要遍历
-
前序遍历
- 根 -> 左子树 -> 右子树
-
中序遍历
- 左子树 -> 根 -> 右子树
-
后序遍历
- 左子树 -> 右子树 -> 根
-
有个很有意思的特性
- 通过
中序遍历的结果和前序/后序遍历的结果,可以还原二叉树
- 通过
-
当然还有其他遍历方式,如层序遍历等
完全二叉树
- complete binary tree
- 只有在树的最后一层的右侧缺少节点的这类二叉树,才叫做完全二叉树
- 特性:
- 树的根节点以 1 为初始编号,那么编号为 i 的子节点:
- 左孩子节点的编号为:2 * 1
- 右孩子节点的编号为:2 * i + 1
- 完全二叉树可以节省用于存储
边的空间,因为通过节点编号可以计算出其子节点的编号
- 树的根节点以 1 为初始编号,那么编号为 i 的子节点:
- 可以用连续空间进行存储,如数组
- 下面为完全二叉树的示例:
满二叉树
- full binary tree
- 没有
度 为 1的节点的二叉树 - 下面为满二叉树的示例:
完美二叉树
- perfect binary tree
- 每一层节点都满了的二叉树
- 下面为完美二叉树的示例:
关于树结构的深入理解
- 节点代表的是集合,边代表的是节点之间的关系
- 父节点就是全集,各个子节点就是互不相交的子集
- 所有子节点代表的子集,加起来就等于,父节点代表的全集
- 集合涉及到的最基础的问题就是查找,而树结构一般情况下,是适用于各种场景下的查找操作
二叉树的作用
是理解高级数据结构的基础
是练习递归技巧的最佳选择
- 设计/理解递归程序:
- 赋予递归函数一个明确的意义
- 思考边界边界条件
- 实现递归过程
- 数学归纳法,也叫结构归纳法
- 确定 k0 是正确的,也就是边界条件正确
- 假设 k(i) 正确,能推导 k(i + 1) 也正确
- 举个例子 🌰,求斐波那契数列中的第 n 个数字
- 函数意义:第 n 项斐波那契数列的数字
- 边界条件:n 为 1 或者 n 为 2 时,直接返回 n
- 递归过程:函数返回 f(n - 1) + f(n - 2)
- 数学归纳法证明
- 确定 k(0) 是正确的,也就是输入 边界条件 得出的结果是正确的
- 假设 k(n-1) 与 k(n-2) 都是正确的,可以推导出 k(n) = k(n-1) + k(n-2) 是正确的
- 所以下面的递归程序是正确的
const fb = (n) => {
if(n <= 2) return n;
return fb(n - 1) + fb(n - 2)
}
可以节省存储空间
-
使用左孩子右兄弟表示法可以节省存储空间
-
左孩子右兄弟表示法,指的是将一颗树,转换为二叉树的形式
- 该树的根节点不变
- 树中节点的左孩子为原树的当前节点的左孩子
- 树中节点的右孩子为原树的当前节点的兄弟节点
-
下图左边是一颗 多叉树,右边是该树的左孩子右兄弟表示法表示的二叉树
-
上述方法为什么可以节省树的存储空间?
-
上图两颗树
- 假设左边树是三叉树,每个节点拥有 3 个指针域,且该树有 6 个节点,5 条边
- 由于不是每个节点的每个指针域都是被使用的
- 计算可得左树总共浪费的指针域的数量为:6 * 3 - 5 = 13
- 右边树为二叉树,每个节点拥有 2 个指针域,且该树有 6 个节点,5 条边
- 计算可得右树总共浪费的指针域的数量为:6 * 2 - 5 = 7
- 所以,上述右树相比于左树,是节约了存储空间的
- 假设左边树是三叉树,每个节点拥有 3 个指针域,且该树有 6 个节点,5 条边
-
一个有 n 个节点的 k 叉树
- 其指针域总数量为 n * k
- 其边的数量为 n - 1,也就是有 n - 1 个指针域被使用
- 浪费的指针域数量为:n * k - (n - 1) = (k -1) * n + 1
-
最后
- 二叉树的分享就到这里了,欢迎大家在评论区里面讨论自己的理解 👏。
- 如果觉得文章写的不错的话,希望大家不要吝惜点赞,大家的鼓励是我分享的最大动力🥰