引入
上一节,我们试着解决了树在存储时空间浪费和没法找到双亲结点的问题,这一节,我们着重看一下如何用代码实现。这篇难度有一点大,请务必沉住气,认真仔细。
思路拆解
我们一步一步的拆解代码来看看。
创建树结构体
我们首先声明我们重新设计的线索二叉树结点
- char data 存放结点数据
- 左孩子、右孩子这个不用细说了吧,
- Ltag 和 Rtag 主要是判断左孩子和右孩子究竟是指向左右孩子还是前驱后继
- 我们定义结构体的时候,使用了 typedef
- 后面所有的 BiTNode 你可以理解为
struct BiTNode
,代表该结构体 - 后面所有的 BiTree 你可以理解为
struct BiTNode *
,代表结构体指针 - 注意上面有个星号哈!
1 |
|
创建二叉树
我们刚才重新设计了结点,现在开始生成树,首先接收数据
- 重点:我们这里的形参,是
BiTree *tempTree
BiTree *tempTree
你可以理解为struct BiTNode **tempTree
- 就是一个指向二叉树结点的结构体的
指针的指针。
- 然后 tempC 是用来存放输入的结点 data 的
- 我们使用 scanf 来接收数据
然后我们判断 scanf 中的数据
- 如果输入的是 # 号,表示结点为空,我们就把结点设置为 NULL
- 这里注意,我们是将
*tempTree
设置为 NULL,不是** tempTree
- 是1个星号的,1个星号表示的是结点结构体的指针
如果scanf 中接受的不是#号
- 先创建动态存储空间,1个星号表示的是结点结构体的指针
- 所以给1个星号表示的是结点结构体的指针创建动态存储空间
(*tempTree)
这里为什么加括号呢?学过指针都知道!- *tempTree 表示的是 tempTree 中地址指向的数据
- 既然 tempTree 存储的是
指针的指针
,所以把 tempTree 加一个星号 - 表示 tempTree 中结点的地址,也就是结点结构体的开始地址
- 我们先给 data 赋值为 tempC
- 然后默认左右 tag 是 Link,也就是链接状态
- 后面如果遇到子树为空,我们会修正 tag 的内容
- 然后利用递归,创建左子树和右子树
1 | void Create(BiTree *tempTree){ |
给二叉树添加线索
我们生成了一个二叉树,这棵二叉树的每个结点都有 Ltag 和 Rtag,该如何利用呢
- 我们这一步就是给哪些没有前驱、后继的结点,修改 Ltag 和 Rtag 状态。
- pre 是一个结点结构体指针的全局变量,保存根的前驱
- 然后我们传入二叉树,给二叉树添加线索
- 利用中序遍历,左中右的方式,递归遍历这棵树。
判断根节点左子树的情况
- 在根节点时,判断根节点左右子树的状态,记住有右必有左
- 先判断左子树,如果为空,那么这个结点的 Lchild 改为前序的地址
- Ltag 改为 thread,表示这个 Lchild 是线索
- 这里的 Lchild 是 pre,pre 等下会存储每个结点的前驱。
判断根节点右子树的情况
- 怎么判断右子树呢?如果只有左没有右呢?
- 所以判断 pre 的右子树,只要当前结点的前一结点的右子树为空
- 那么就改变 Rtag 状态为线索
- Rchild 右孩子为当前结点
- 也就是当前结点前驱没有右子树,那么前驱的右孩子指向当前结点。
- 记得最后把当前的地址个 pre,这样指针才能向后走,pre 才能保存前驱结点
1 | BiTree pre; |
根结点的前驱
我们知道,根结点是没有双亲的,那么怎么获得根结点的前驱呢?
- 创建一个 p ,模拟根的前驱,让根的前驱指向这个 p
- 先创建一个 p,星号的道理和创建树的时候相同,就不解释了。
- Rtag 因为指向自己,所以为 thread 线索
- Ltag 因为等下要指向树本身,所以为 link
- 然后让 Rchild 指向子集
- 判断树是不是空的,如果是空的,那没得玩,左右子树都指向 p 自己
- 如果不是空的,那就让左子树指向树根
- pre 就能获取根的前驱了,就是 p
- 然后从这里调用给二叉树加线索的函数
- 因为给二叉树加线索,需要用到根的前驱。
记得收尾
- 最后,我们让这棵树连成圈
- 让 pre,也就是最后一个结点的前驱的右孩子指向 P,也就是根的前驱
- 让后 p 的右孩子指向 pre
- 这样,就连起来了
1 | void Inorderthread(BiTree t , BiTree *p){ |
两种遍历方式
用递归的时候注意,要在加线索之前使用,否则树都连起来就会出错。
###递归中序遍历,不多说了,需要的话看前面
1 |
|
迭代中序遍历,就是利用 while 循环遍历
同样是左中右
- 如果 p=t->Lchild,那就证明转了一圈了。
- 然后判断最左边的结点,输出
- 判断右边结点是否是叶子,也输出
- p 向右孩子移动一位
- 直到转完所有的结点
1 | void Inorderthreading(BiTree t){ |
主调函数
这个其实没什么好讲的,就是星号和顺序的问题
- 星号请注意,因为传过去的是指针的地址,所以要加
&
符号 - 顺序请注意,先使用递归输出,再增加线索,最后用迭代循环输出
- 原因是增加线索后,树连起来了,递归没法结束。
1 | int main(){ |
主要问题
这个也绕了我好久,主要问题是
- 指向指针的指针,出了问题!
- 递归增加线索,出了问题!
总代码
1 |
|
输出结果:
请输入结点:
ABDH##I##E#J##CF#K##G##
递归-中序遍历是:HDIBEJAFKCG
迭代-中序遍历是:HDIBEJAFKCG
遍历的是上面这棵树。
带注释的总代码
我第二天一早又写了一遍,加了一些注释
1 |
|
尾巴
这是我的个人学习笔记,主要是应付考研复习使用,充斥着一些吐槽和个人观点,并不严谨,欢迎大家参考、指正。