数据结构——树

1. 树的常见概念

树是一个由n个有限节点组成的一个具有层次关系的集合, 每个节点有 0 个或多个子节点, 没有父节点的节点称为根节点, 也就是说除了根节点以外每个节点都有父节点, 并且有且只有一个。树的种类比较多, 最常见的为二叉树, 基本结构如下:

在这里插入图片描述
参考上面的结构, 可以方便得理解树的如下概念:

  1. 节点的度: 一个节点含有的子节点的个数称为该节点的度;
  2. 树的度: 一棵树中, 最大的节点的度称为树的度;
  3. 叶节点或终端节点: 度为 0 的节点;
  4. 非终端节点或分支节点: 度不为 0 的节点;
  5. 双亲节点或父节点: 若一个节点含有子节点, 则这个节点称为其子节点的父节点;
  6. 孩子节点或子节点: 一个节点含有的子树的根节点称为该节点的子节点;
  7. 兄弟节点: 具有相同父节点的节点互称为兄弟节点;
  8. 节点的祖先: 从根到该节点所经分支上的所有节点;
  9. 子孙: 以某节点为根的子树中任一节点都称为该节点的子孙;
  10. 森林: 由 m (m >=0) 棵互不相交的树的集合;
  11. 无序树: 树中任意节点的子节点之间没有顺序关系, 这种树称为无序树, 也称为自由树;
  12. 有序树: 树中任意节点的子节点之间有顺序关系, 这种树称为有序树;
  13. 二叉树: 每个节点最多含有两个子树的树称为二叉树;

2. 树的性质

  • 在二叉树的第 i 层上至多有 2 ^ (i - 1) 个节点 ( i > 0 )
  • 深度为 k 的二叉树至多有 2 ^ ( k -1 ) 个节点 ( k > 0 )
  • 对于任意一颗二叉树, 如果其叶节点树为 N0, 而度数为 2 的节点总数为 N2, 则 N0 = N2 + 1
  • 具有 n 个节点的完全二叉树的深度必为 log2(n+1)
  • 对完全二叉树, 若从上至下、从左至右编号, 则编号为 i 的节点, 其左孩子的编号必为 2i, 其右孩子编号必为 2i + 1; 其双亲的编号必为 i / 2 ( i =1时为根, 除外)

满二叉树: 如果一棵二叉树只有度为 0 的节点和度为 2 的节点, 并且度为 0 的节点在同一层上, 则这棵二叉树为满二叉树。

在这里插入图片描述

完全二叉树: 除了最底层节点可能没有填满外, 其余每层节点数都达到最大值, 并且最下面一层的节点都集中在该层最左边的若干位置。

在这里插入图片描述

3. 树的定义与存储方式

定义树的原理与链表本质上是一样的, 只不过多了一个指针, 如果是二叉树, 只要在链表的定义上增加一个指针就可以了:

public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
}

这里本质上就是两个引用, 分别指向了两个位置, 为了方便理解, 分别命名为左孩子和有孩子。如果是 N 叉树的话, 使用 list 来表示指向其他节点的指针:

public class TreeNode {
    int val;
    List<TreeNode> nodes;
}

除了使用链表, 也可以使用数组来存储二叉树:

在这里插入图片描述

使用数组存储的最大不足是可能存在大量的空间浪费。

4. 树的遍历方式

二叉树的遍历方式有层次遍历和深度优先遍历两种:

  • 深度优先遍历: 先往深走, 遇到子节点再往回走
  • 广度优先遍历: 一层一层的去遍历, 一层访问完再访问下一层

深度优先又有前中后序三种, 其中前指的是中间的父节点在遍历中的顺序

在这里插入图片描述

5. 通过序列构造二叉树

通过给出的序列恢复原始二叉树, 看三个序列:

(1) 前序:1 2 3 4 5 6 8 7 9 10 11 12 13 15 14
(2) 中序:3 4 8 6 7 5 2 1 10 9 11 15 13 14 12
(3) 后序:8 7 6 5 4 3 2 10 15 14 13 12 11 9 1

5.1 前中序列复原二叉树

  • 第一轮:
    前序第一个访问的是根节点, 所以根节点是 1。
    中序遍历的特点是根节点的左子树的元素都在根节点的左侧, 右子树的元素都在根节点的右侧, 从中序遍历序列可以划分成如下结构:

中序序列划分:
[3 4 8 6 7 5 2] 1 [10 9 11 15 13 14 12]
前序序列划分:
1 [2 3 4 5 6 8 7] [9 10 11 12 13 15 14]

前序中 7 之前的元素都在中序的第一个数组中, 9 之后的元素都在第二个数组中, 所以从 7 和 9 之间划分。
上面前序序列第一个括号前都是左子树的元素, 第二个括号一定都是右子树的元素。
此时树的结构为:

在这里插入图片描述

  • 第二轮
    先看两个序列的第一个数组:
    前序: 2 3 4 5 6 8 7 中序: 3 4 8 6 7 5 2
    此时又可以利用上面的结论划分了: 根节点是2, 然后根据2在中序的位置可以划分为:

前序: 2 [ 3 4 5 6 8 7 ]
中序: [ 3 4 8 6 7 5 ] 2

所以此时树的结构为:

在这里插入图片描述

  • 第三轮
    对 3 4 5 6 8 7 继续划分:

前序: 3 [ 4 5 6 8 7 ]
中序: 3 [ 4 8 6 7 5 ]

此时树的结构为:

在这里插入图片描述

  • 第四轮
    对 4 5 6 8 7 继续划分:

前序 4 [ 5 6 8 7 ]
中序: 4 [ 8 6 7 5 ]

此时树的结构为:

在这里插入图片描述

  • 第五轮
    对 5 6 8 7 继续划分:

前序: 5 [ 6 8 7 ]
中序: [ 8 6 7 ] 5

树的结构为:

在这里插入图片描述

  • 第六轮
    对 6 8 7 进行划分:

前序: 6 [ 8 7 ]
中序: [ 8 ] 6 [ 7 ]

树的结构为:

在这里插入图片描述

  • 同理,对于序列[ 10 9 11 15 13 14 12],我们也可以逐步划分

前序: 9 [ 10 11 12 13 15 14 ]
中序: [ 10 ] 9 [ 11 15 13 14 12 ]

树的结构为:

在这里插入图片描述

前序: 11 [ 12 13 15 14 ]
中序: 11 [ 15 13 14 12 ]

树的结构:

在这里插入图片描述

前序: 12 [ 13 15 14 ]
中序: [ 15 13 14 ] 12

树的结构:

在这里插入图片描述

前序: 13 [ 15 14 ]
中序: [ 15 ] 13 [ 14 ]

树的结构:

在这里插入图片描述

5.2 通过中序和后序序列恢复二叉树

后序序列的最后一个是根节点,中序的处理也是上面一样的过程:

后序: [ 8 7 6 5 4 3 2 10 15 14 13 12 11 9 ] 1
中序: [ 3 4 8 6 7 5 2 ] 1 [ 10 9 11 15 13 14 12 ]
其中中序从根节点 1 划分为左子树 3 4 8 6 7 5 2 和 右子树 10 9 11 15 13 14 12

树的结构:

在这里插入图片描述

后序: [ 8 7 6 5 4 3 2 10 15 14 13 12 11 ] 9
中序: [ 10 ] 9 [ 11 15 13 14 12 ]

树的结构:

在这里插入图片描述

后序: [ 8 7 6 5 4 3 2 10 15 14 13 12 ] 11
中序: 11 [ 15 13 14 12 ]

树的结构:

在这里插入图片描述

后序: [ 8 7 6 5 4 3 2 10 15 14 13 ] 12
中序: [ 15 13 14 ] 12

树的结构:

在这里插入图片描述

后序: [ 8 7 6 5 4 3 2 10 15 14 ] 13
中序: [ 15 ] 13 [ 14 ]

树的结构:

在这里插入图片描述
至此, 右子树已经全部遍历, 后序开始从 2 开始遍历

后序: [ 8 7 6 5 4 3 ] 2
中序: [ 3 4 8 6 7 5 ] 2

树的结构:

在这里插入图片描述

后序: [ 8 7 6 5 4 ] 3
中序: 3 [ 4 8 6 7 5 ]

树的结构:

在这里插入图片描述

后序: [ 8 7 6 5 ] 4
中序: 4 [ 8 6 7 5 ]

树的结构:

在这里插入图片描述

后序: [ 8 7 6 ] 5
中序: [ 8 6 7 ] 5

树的结构:

在这里插入图片描述

后序: [ 8 7 ] 6
中序: [ 8 ] 6 [ 7 ]

树的结构:

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值