之前我们学习栈,队列,链表都是一对一的关系,而树属于一对多的关系。
一、基础概念
1.定义
树是递归式的定义,树是n(n≥0)个结点的有限集合:当n=0时,称为空树;当n>0时,满足有且仅有一个根结点(Root),其余结点可分为m (m≥0)个互不相交的每个集合本身也是一棵树,称为根的子树。

2.基本概念
(1)节点:树中的一个独立单元,包含一个数据元素和若干指向其他子树的分支。
(2)节点的度:节点拥有的子树的个数。
(3)树的度:树内各节点的最大值。
(4)叶子:度为0的节点,也称终端节点,即到这里就停止了。
(5)非叶子节点:度不为0的节点,也称非终端节点,分支节点,内部节点(根节点除外)。
(6)双亲和孩子:根是子树的双亲,子树是根的孩子,也就是上面一层的节点是相连的下一层节点的双亲,下一层是上一层的孩子。
(7)兄弟:同一个双亲的孩子。
(8)堂兄弟:双亲在同一层上的节点。
(9)祖先:从根节点开始到这个节点所经分支上的所有节点。
(10)子孙:以某节点为根的子树中的任意节点,也就是根节点下面的所有节点都是他的子孙。
(11)层次:根节点为第一层,依次往下。
(12)深度(高度):树中节点的最大层次。
(13)有序树:树中节点各子树从左向右看是有序的,如图1.
(14)森林:m颗互不相交的树的集合。
一定要理解这些词指的都是什么,不然在题中见到直接就大脑一片空白了。
3.存储
存储方式有两种,后面以二叉树为例进行具体介绍。
(1)顺序存储
(2)链式存储
二、二叉树
1.基本概念
二叉树其实就是所有节点只有两个分支的树,但是不是必须两个分支,是少于两个分支。并且这两个子树称作左子树和右子树,两者具有严格的顺序,不能调换。(只有一个根节点也算二叉树,还有空二叉树,以及左空二叉树,右空二叉树)。
(1)特殊二叉树
| 类型 | 定义 |
|---|---|
| 斜树 | 所有结点仅有左子树(左斜树)或仅有右子树(右斜树) |
| 满二叉树 | 深度为 k 且结点数为 2^k − 1 的二叉树;所有层均被填满,叶子结点全在最后一层 |
| 完全二叉树 | 对 n 个结点按层序从 1 到 n 编号,若每个结点的位置与相同深度的满二叉树中编号相同的结点位置一致,则为完全二叉树(特点:除最后一层外,其他层全满;最后一层结点靠左排列) |
重点是满二叉树和完全二叉树的区别,满二叉树就是所有层都满了,叶子节点都在最后一层(叶子节点那一层也是满的),完全二叉树是除了叶子节点那一层都满了,没满的叶子结点靠左。
对于完全二叉树可能会让计算他的层数,层数等于
这是是对其向下取整,如2.58向下取整为2,n为节点数。
(2)遍历
| 遍历方式 | 访问顺序 | 说明 |
|---|---|---|
| 前序遍历(Preorder) | 根 → 左 → 右 | 先访问根结点,再递归遍历左子树,最后右子树 |
| 中序遍历(Inorder) | 左 → 根 → 右 | 先递归遍历左子树,再访问根,最后右子树 |
| 后序遍历(Postorder) | 左 → 右 → 根 | 先递归遍历左右子树,最后访问根结点 |
| 层序遍历(Level-order) | 从上到下、从左到右 | 按层次逐层访问,通常借助队列实现 |
此处还有一点要注意的就是我们可以根据给出前序遍历和中序遍历,以及给出中序遍历和后序遍历画出整棵树,但是前序遍历和后序遍历不行。前序遍历可以确定根节点,然后通过根节点将中序遍历结果分为左子树和右子树,然后再依次找到子树的根节点,然后就可以画出全部。后序遍历的根节点在最后一个,与前一个方法类似。
2.性质
哈哈哈因为这个公式不好打,主播偷懒了截图的.......


这里就要注意性质4和性质5是针对完全二叉树的,因为完全二叉树的数据是按顺序排列的。
3.代码实现(链式存储)
(1)初始化

这里我们需要对树进行扩展,扩展序列=二叉树前序遍历+空节点标记(用#表示NULL)。这是为了方便描述他们之间的关系。


他们之间就靠这样形式来表达他们之间的关系。
(2)创建

树的创建也需要用到递归,因为我们开始给的序列是二叉树前序遍历的扩展序列,那么第一个值就是根节点,第二个值该是这个根节点的左孩子了,但是其他的左孩子又是这个子树的根节点,接下来又该找第二个值的左节点了,但是遇到了#,我们就知道他没有左孩子了,就return返回上一层递归,那么就该执行他的右孩子的创建了,他的右孩子也是下面这个小子树的根节点,穿件完后又该创建他的左孩子.......就这样就可以做到将整个数的关系全部保存在计算机中。
这里就是看你有没有理解递归是什么意思,我们在函数里面又调用这个函数,直到遇到#返回。
(3)前序遍历:以根左右方式进行遍历,即先读根节点,再读左节点,最后读右节点,但是读左节点时,如果这个节点下面还有数据,那么他就相当于这个小子树的根节点,读了他之后再读他的左节点,然后再去读根节点的右节点,具体读法如下。


跟创建一样,需要用到递归,因为除了根节点,其他的节点都会是别的节点的根节点,也会是别的节点的孩子。先打印根节点,再调用这个函数打印左子树(此时相当于给左子树看做一个完整的子树,那么根节点的左孩子就是这颗小子树的根节点),反正就是嵌套,循环,递归,要仔细体会一下。
(4)中序遍历:左根右


中序遍历就是左根右,所以先找到最左边的节点,因为前面给的是扩展序列嘛,就相当于找到第一个叶子节点部的左孩子,但是#不打印会直接返回上一层函数,那么此时就打印根节点其实也就是第一个叶子节点,然后再打印找叶子节点的右节点,也是#就返回,此时左节点打印出来了,该打印根节点了,然后再找根节点的右孩子(此处的根指的是小子树的根也就是倒数第二层的第一个数,不要弄混了)。就这样递归......
(5)后序遍历:左右根


后序遍历是左根右,就是第一个也是节点,左孩子返回右孩子返回,就打印他自己,然后打印右孩子。.....
其实这里就相当于让每个节点都当上一次根节点,然他是根节点的时候打印。
(6)广度优先遍历:一层一层从左往右读


这里用到了队列来遍历,首先让根节点入队,然后出队,再判断跟节点是否有左右孩子,如果有就依稀入队,然后左孩子出队,然后判断左孩子是否有孩子,右的话再入队,下一个数据为根节点的右孩子,右孩子出队,然后判断右孩子是否还有孩子,如果有就继续入队......依次2类推就可以遍历到整个树。
(7)销毁

依旧递归啊,但是因为先释放根节点的话就会找不到其他的节点,所以我们释放的方式为后序遍历的方式。