本文已参与「新人创作礼」活动,一起开启掘金创作之路
在讨论什么是数据结构: 数据结构是研究 数据的存储 和 数据的操作 的一门学问。
数据的存数分为两个部分:
- 个体的存储。(int、char、等等这些个体)
- 个体关系的存储。(链表,树,图)
总结:从某个角度而言,数据的存储==最核心==的就是个体关系的存储,个体的存储可以忽略不计。
再次讨论什么是泛型:
同一种逻辑结构,无论该逻辑结构物理存储是什么样子的,我们都可以对他执行相同的操作。
1、前景知识
-
专业的定义: 1、有且只有一个根节点 2、有若干互不相交的子树,这些子树本身也是一棵树。
-
通俗的定义 1、数是由节点和边(指针域)组成。 2、每个节点只有一个父节点、但可以有多个子节点。 3、但有一个节点例外,该节点没有父节点,我们称为根节点。
-
专业术语的解释
1、节点(一个小圈)、父节点(B是D的父节点、但A不是D的父节点)、子节点、子孙(所有节点都是根节点的子孙)、 堂兄弟(D和F)、兄弟(E和F)
2、深度:从根节点到最底层节点的层数称之为深度,根节点是第一层
3、叶子节点:没有子节点的节点(叶子下面已经不能分岔)(G、H、I、J)
4、非终端节点:实际就是非叶子节点(有子节点)
5、度:子节点的个数
- 树的分类
1、一般树:任意一个节点的子节点的个数,都不受限制。
2、二叉树:任意一个节点的子节点的个数,最多是两个,且子节点的位置不可更改。
3、森林:n个互不相交的树的集合
2、树的存储(重点)
1、规定规则:来将非线性暂时转化为线性的结构 (1)先序:ABCDEF (2)中序: (3)后序: 2、如果我们只看到,有效节点,那我们就无法推算出原来树的结构是什么 3、所以我们,先将他转化为完全二叉树、添加一些无效节点,来帮助我们推算原来树的结构。
(1)二叉树的存储
1、连续存储(数组):必须要求这个树,是完全二叉树。
(如果:不是完全二叉树,就添加节点,构造完全二叉树)
特点:
- 1、知道有几个节点个数、就可以推算出节点的层数。
- 2、如果给我们一个编号,我们就可以推算出它的父节点、子节点。
- 3、耗用的内存比较大。
2、链式存储
(2)一般树的存储
目标:解决用线性结构来存储,非线性结构的数据结构。
-
双亲表示法:求父节点方便
-
孩子表示法:求子节点方便
-
双亲孩子表示法:求父节点和子节点都很方便
- 二叉树表示法:把一个普通树转化成二叉树来存储
具体转换方法: 设法保证任意一个节点的 左指针域指向它的第一个孩子, 右指针域指向它的兄弟, 只要满足此条件,就可以把一个普通树转化为二叉树。 一个普通树转化成的二叉树一定没有右子树
(3)森林的存储
先把森林转化为二叉树,再存储二叉树:
将相邻的父节点依次作为节点的右子树再对各父节点进行转化
3、二叉树的操作
考试经常考: (1)树的遍历 (2)已知两种遍历,求另一种遍历
线性化:将一个非线性的树,转化为一个线性的结构。
以根节点所在的位置,来进行规定序列 (1)先(根)序遍历(根左右) (2)中(根)序遍历(左根右) (3)后(根)序遍历(左右根)
先(根)序遍历(根左右):A B D H ,E I, C F J ,K, G 中(根)序遍历(左根右):D H B E I A J F K C G 后(根)序遍历(左右根):H D I E B J K F G C A
(1)二叉树的先序遍历
-
先访问根节点
-
再先序访问左子树 (递归:子树当中,还是 根左右)
-
最后先序访问右子树(递归:子树当中,还是 根左右)
分析:A 、B、 D、 H、E、 I、 C、 F、 J、 K、 G 1、根节点:A 2、访问 A 的左子树:(也是先序遍历的步骤——> 体现递归) 当中:根节点是 B ,左子树是 D 、右子树是 E 。 依次类推,先访问B,后访问 D。(然后将 D 也进行先序遍历) 然后 D 的左子树为空,所以就访问 D 的左子树。
(2)二叉树的中序遍历
-
中序遍历左子树
-
再访问根节点
-
再中序遍历右子树
同样也是递归的思想:
所以顺序为:
D、H、B、E、I、 A、J、F、K、C、G
分析:想要遍历一个大的二叉树(规模为n),要借助更小一层的二叉树来解决(规模为 n-1)。最先访问最小的二叉树。
(3)二叉树的后序遍历
-
先中序遍历左子树
-
再中序遍历右子树
-
最后遍历根节点
同样也是递归的思想:
所以顺序为:
H、D、I、E、B、J、K、F、G、C、A
4、已知两种遍历序列求原始二叉树
-
我么已知一种遍历顺序的时候,永远不可能推算出原始二叉树。
-
我们已知先序和后序,也无法推断出原始二叉树
-
只有通过先序和中序或者中序和后序,我们才可以唯一的确定一个二叉树。
(1)已知先序、中序,求后序
先序:A B C D E F G H 中序:B D C E A F H G 求后序? 那么此二叉树的后序为:DECBHGFA
按照先序的定义,A为最外层 根节点,按照中序的定义和前面的结论可知 BDCE 为A节点的左子树节点,FHG为A节点的右子树,再依次按照两个遍历定义可以推出原始二叉树为:
步骤:
1、先序第一个:A 根节点 ————> 中序当中:A 左边为左子树(BDCE),右边为右子树(FHG)。
2、再看先序:BCDE 为左子树的先序排列: 所以 B 是 A 的左节点。FHG 为左子树的先序排列: 所以 F 是 A 的右节点。
此时:最上面已经推理出来了,继续推理左子树的结构:(BDCE)
3、再从中序排序的左子树当中找B节点——> 中序当中:B没有左子树,右子树为(DCE)
4、在先序当中(DCE) 的排序为 CDE :所以 C是根节点 此时不能确保,D 就是左节点,E就右节点。 有可能 D 是 C的右节点,然后 E 又是 D 的子节点。
5、在中序当中找到 C 的位置:中序当中为 (DCE) ,所以肯定 D 是C的左节点,E 是C的右节点
同理我们也可以推理出右子树的结构:在先序中是:FGH: 根节点是 F
6、在中序当中为 FHG :所以说明 GH 是F 的右子树
7、在先序当中顺序为:GH :所以 G 是根节点。 H 为G 的右节点。
(2)已知中序、后序,求先序
先序:根、左(左子树)、右(右子树)
在左子树当中:也是 根左右 在右子树当中:也是 根左右
5、树的应用
应用情况:
-
树是数据库中数据组织一种重要形式。
-
操作系统子父进程本身就是一颗树。
-
面向对象语言中类的继承关系
-
赫夫曼树
6、链式二叉树遍历程序
#include <stdio.h>
// binary tree
typedef struct BTNode
{
// 数据域
int data;
// 指针域
struct BTNode * pLchild; // 解析名字:p代表指针,L:代表左, child 代表子节点
struct BTNode * pLchild;
}BTNode,* PBTNode;
int main(void)
{
// 创建一个树
struct BTNode *pT = CreateBTree();
}
(1)CreateBTree
思考:
1、创建一个二叉树,需要几个参数,返回什么参数?
参数:根节点 返回值:返回根节点地址(4个字节),如果返回整个根节点(有可能根节点的数据域很大)
2、这个函数里面是静态构建,还是动态构建二叉树?(静态的话,当函数结束,栈会销毁)
PBTNode CreateBTree()