• 树是一种分层数据的抽象模型

    • 现实生活中最常见的树的例子是家谱或是公司的组织架构图
  • 一个树结构包含一系列存在父子关系的节点

    • 每个节点都有一个父节点(除了顶部的第一个节点)以及零个或多个子节点
  • 下面为完全二叉树的示例:

二叉树 (Binary-Tree) - 图1

  • 用代码来表示上述 树节点 ```tsx // 链表节点 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. - 可以看到,链表就是一种特殊的树结构
  2. - 如图中:1->4->7->null
  3. - 树的有一个重要属性叫 `度`,指的就是每个节点的子节点个数
  4. - 图中,1 号节点的度为 32号节点的度为 13 号节点的度为 04 号节点的度为 2
  5. <a name="KJVVp"></a>
  6. # 二叉树
  7. - 二叉树的特性
  8. - 每个节点的度最多为 2
  9. - 度为 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


- 二叉树的三种重要遍历
   - 前序遍历
      - 根 -> 左子树 -> 右子树
   - 中序遍历
      - 左子树 -> 根 -> 右子树
   - 后序遍历
      - 左子树 -> 右子树 -> 根
   - 通过`中序遍历`的结果和`前序/后序遍历`的结果,可以还原二叉树

- 当然还有其他遍历方式,如层序遍历等

<a name="klBCu"></a>
## 完全二叉树

- complete binary tree
- 只有在树的最后一层的右侧缺少节点的这类二叉树,才叫做完全二叉树
- 特性:
   - 树的根节点以 1 为初始编号,那么编号为 i 的子节点:
      - 左孩子节点的编号为:2 * 1
      - 右孩子节点的编号为:2 * i + 1
      - 完全二叉树可以节省用于存储 `边` 的空间,因为通过节点编号可以计算出其子节点的编号
   - 可以用连续空间进行存储,如数组
- 下面为完全二叉树的示例:

![](https://cdn.nlark.com/yuque/0/2021/jpeg/344923/1638591481335-c0a1ae84-5058-483b-ac72-16b38bbb2d94.jpeg)

<a name="XfiWH"></a>
## 满二叉树

- full binary tree
- 没有 `度 为 1` 的节点的二叉树
- 下面为满二叉树的示例:

![](https://cdn.nlark.com/yuque/0/2021/jpeg/344923/1638591782547-f5db9ce2-23e8-4461-9728-009d118b6720.jpeg)


<a name="V0Tkf"></a>
## 完美二叉树

- perfect binary tree
- 每一层节点都是满了的二叉树
- 下面为完美二叉树的示例:

![](https://cdn.nlark.com/yuque/0/2021/jpeg/344923/1638591784499-b7b45adf-edfc-48b0-9dd9-b2f6a953c7da.jpeg)

<a name="QQxfy"></a>
#   关于树结构的深入理解

- 节点代表的是集合,边代表的是节点之间的关系
   - 父节点就是全集,各个子节点就是互不相交的子集
   - 所有子节点代表的子集,加起来就等于,父节点代表的全集
   - 集合涉及到的最基础的问题就是查找,而树结构一般情况下,是适用于各种场景下的查找操作

<a name="P3d1e"></a>
#   二叉树的作用
<a name="KaDBe"></a>
## 是理解高级数据结构的基础
![](https://cdn.nlark.com/yuque/0/2021/jpeg/344923/1638697888017-b71ed020-ba71-4276-be82-a3c1b4f924c9.jpeg)

<a name="DFgxd"></a>
## 是练习递归技巧的最佳选择

- 设计/理解递归程序:
   1. 赋予递归函数一个明确的意义
   1. 思考边界边界条件
   1. 实现递归过程
   1. 数学归纳法,也叫结构归纳法
      1. 确定 k0 是正确的,也就是边界条件正确
      1. 假设 k(i) 正确,能推导 k(i + 1) 也正确
- 举个例子 🌰,求斐波那契数列中的第 n 个数字
   1. 函数意义:第 n 项斐波那契数列的数字
   1. 边界条件:n 为 1 或者 n 为 2 时,直接返回 n
   1. 递归过程:函数返回 f(n - 1) + f(n - 2)
   1. 数学归纳法证明
      1. 确定 k(0) 是正确的,也就是输入 边界条件 得出的结果是正确的
      1. 假设 k(n-1) 与 k(n-2) 都是正确的,可以推导出 k(n) = k(n-1) + k(n-2) 是正确的
      1. 所以下面的递归程序是正确的
```jsx
const fb = (n) => {
  if(n <= 2) return n;

  return fb(n - 1) + fb(n - 2)
}

可以节省存储空间

  • 使用左孩子右兄弟表示法可以节省存储空间
  • 左孩子右兄弟表示法,指的是将一颗树,转换为二叉树的形式

    • 该树的根节点不变
    • 树中节点的左孩子为原树的当前节点的左孩子
    • 树中节点的右孩子为原树的当前节点的兄弟节点
  • 下图左边是一颗 多叉树,右边是该树的左孩子右兄弟表示法表示的二叉树

二叉树 (Binary-Tree) - 图2

  • 上述方法为什么可以节省树的存储空间?

    • 上图两颗树

      • 假设左边树是三叉树,每个节点拥有 3 个指针域,且该树有 6 个节点,5 条边
        • 由于不是每个节点的每个指针域都是被使用的
        • 计算可得左树总共浪费的指针域的数量为:6 * 3 - 5 = 13
      • 右边树为二叉树,每个节点拥有 2 个指针域,且该树有 6 个节点,5 条边
        • 计算可得右树总共浪费的指针域的数量为:6 * 2 - 5 = 7
      • 所以,上述右树相比于左树,是节约了存储空间的
    • 一个有 n 个节点的 k 叉树

      • 其指针域总数量为 n * k
      • 其边的数量为 n - 1,也就是有 n - 1 个指针域被使用
      • 浪费的指针域数量为:n k - (n - 1) = (k -1) n + 1