实验日期:2022-12-05
目录
一、实验目的
二、实验内容
三、程序设计
(一)概要设计
(二)详细设计
1.相关结构体、全局变量和数据类型的定义
2.函数的功能设计及函数设计的思路与意图
(三)程序文件结构
四、程序实现及程序流程图
1.程序代码实现
2.程序流程图
五、系统测试与程序亮点
1.系统测试截图
2.程序亮点
六、实验思考与体会
八、实验总结
1、掌握二叉树线索化的建立
2、掌握二叉树线索化的遍历
3、掌握二叉树线索化的销毁
具体请完成如下功能:
首先定义一个线索二叉树,其采用链式存储,二叉树结构体内不仅定义左右子树指针,还定义左右标志变量,在无左/右孩子时,指针指向前驱/后驱。然后进行二叉树的创建,通过先序次序输入二叉树各节点的值,若为空则输入‘#’。创建成功后,程序通过一个while循环和一个switch语句实现了功能的多次选择执行,使用户可进行功能选择,具体功能有进行二叉树的中序线索化、遍历中序线索二叉树、销毁二叉树和退出功能选择。用户测试完所有功能后可退出程序。
(1)采用枚举类型,定义PointTag数据类型,由此定义左右标志变量ltag、rtag。
(2)定义线索二叉树结构体
采取链式存储,结构体的成员变量有左右孩子指针、左右标志变量、数据域。
(3)关键全局变量pre的定义
为了记下遍历过程中访问节点的先后关系,定义指针pre始终指向刚刚访问过的节点,而指针p(在代码中出现)指向当前访问的节点,由此记录下遍历过程中访问节点的先后关系。
I功能:创建一个二叉树并实现初始化
II设计思路和意图:设计此函数,主要是为了实现二叉树的创建。具体的过程:首先按先序次序输入二叉树每个节点的值(从根节点开始逐个输入),若节点为空,则输入‘#’;非空则申请内存空间存储数据,然后通过递归,继续创建当前节点的左子树和右子树。若输入的序列符合先序次序,则函数运行成功,程序进行功能选择阶段。此步骤是实现后续线索化中序二叉树和遍历中序二叉树的基础。
(2)void InThreading(BiTree T)
I 功能:对树中以任意节点T为根的二叉树进行中序线索化
II设计思路和意图:设计此函数,主要是在下一个函数“带头节点的二叉树中序线索化”中进行调用。将此功能进行提取,定义为一个函数,提高了代码的封装性。
思路:在根节点非空的情况下,首先进行左子树的递归线索化,若其左孩子为空,则给根节点T加上标志,并将左指针指向前驱(pre)。然后对根节点前驱pre的右孩子是否为空进行判断,若为空,则将其右指针指向根节点T;若非空,则加上标志Linked。此时保持pre指向T,然后对根节点T的右子树进行递归线索化。最后实现整个树结构的线索化。
(3)void InOrderThreading(BiTree* Thrt, BiTree T)
I 功能:将带头节点的二叉树进行中序线索化
II 设计思路和意图:
设计此函数主要通过对函数void InThreading(BiTree T)的调用,实现对整个二叉树的中序线索化。
思路:首先对树非空的情况进行判断,在树非空的情况下,创建并初始化头节点,将其左指针指向根节点T(左孩子),右指针指向自己,指针pre指向头节点。然后对以T为根节点的二叉树进行中序线索化。线索化结束后,pre为最右节点,将其进行线索化,即将pre的右线索指向头节点,而头节点的右线索也指向pre,由此实现整个二叉树的中序线索化。
(4)void InOrderTraverse(BiTree Thrt)
I 功能:遍历中序线索二叉树(非递归算法)
II 设计思路和意图:设计此函数用于对中序线索二叉树的遍历,具体实现是在控制台输出二叉树各个节点的数据。
思路:首先定义节点p为头节点的左孩子,即根节点。然后进行循环,至p等于头节点时,循环结束。在p不等于头节点时,沿左孩子向下,访问其左子树为空的节点,而后沿右线索访问后继节点;左子树访问结束后,转向p的右子树,重复以上操作。对每个数据元素直接输出。
(5)void Destroy(BiTree* Thrt)
I 功能:实现二叉树的销毁操作,释放内存空间
II 设计思路和意图: 为了达到实验要求“销毁二叉树”,同时此步骤也是本次实验不可少的一步,因为使用了malloc函数申请了空间后,为了避免内存泄漏,就要使用free函数释放空间,释放后并把指针置NULL。
思路:首先定义指针p指向根节点,进行二叉树的遍历。遍历中首先找到中序遍历的第一个节点,然后定义指针adjust指向p指向的节点,而p指向直接后继,即根结点,同时释放adjust节点的内存空间。执行完上述操作后,更新adjust指针,p指针指向其右孩子,并释放掉adjust节点的内存空间。最后释放头节点,并给野指针置NULL,完成二叉树的销毁。
(6)主函数int main(void)
I 功能:为用户提供多次功能选择并进行程序功能检测
II 设计思路和意图:为了验证所设计的程序是否具备实验要求的所有功能,设计此函数供用户检测。
思路:首先定义头节点和二叉树,并在功能选择前完成二叉树进行创建和初始化操作。然后进行功能选择,用户可选择对二叉树进行中序线索化、遍历中序二叉树、销毁二叉树和退出程序等操作。借助与while和switch语句,用户可在执行退出程序操作前选择多种进行测试,大大减少了测试时长。
内部存放系统头文件、结构体的定义、枚举类型的定义及所涉及函数的声明
调用BinTreeThread.h及系统头文件,内部存放所有功能函数的具体实现
调用BinTreeThread.h及系统头文件,实现主函数
// BinTreeTread.h
// 二叉树
//
// Created by peach on 2022/12/5.
//
#ifndef BinTreeTread_h
#define BinTreeTread_h
#include
#define _CRT_SECURE_NO_WARNINGS
#include
//定义一个线索二叉树
//此处采用枚举类型,定义数据类型PointTag链式存储
typedef enum { Linked, Thread } PointTag;
typedef struct Node {
char data;
/*左右孩子指针*/
struct Node* lchild;
struct Node* rchild;
/*左右标志,规定为Linked时存在左/右孩子*/
PointTag ltag;
PointTag rtag;
}BitNode, *BiTree;
//函数声明
void CreateBiTree(BiTree* T);
void InThreading(BiTree T) ;
void InOrderThreading(BiTree* Thrt, BiTree T) ;
void InOrderTraverse(BiTree Thrt);
void Destroy(BiTree* Thrt);
#endif /* BinTreeTread_h */
// BinTreeTread.cpp
// 二叉树
//
// Created by peach on 2022/12/5.
//
#include "BinTreeTread.h"
#include
//创建一个二叉树
void CreateBiTree(BiTree* T) {
char ch = 0;
/*按先序次序输入二叉树中结点的值(一个字符),创建二叉链表表示的二叉树T*/
scanf("%c", &ch);
if (ch == '#') {
*T = NULL;
} else {
*T = (BiTree)malloc(sizeof(BitNode));
if (!(*T)) {
//若申请内存失败,则退出程序
exit(-1);
}
//输入的数据作为二叉树根节点数据域
(*T)->data = ch;
CreateBiTree(&((*T)->lchild));
CreateBiTree(&((*T)->rchild));
}
}
//定义变量pre,始终指向已经线索化的结点
BiTree pre;
//中序线索二叉树
void InThreading(BiTree T) {
//以T为根节点
if (T) {
//左子树递归线索化
InThreading(T->lchild);
//p的左孩子为空
if (!T->lchild) {
//给T加上左线索
T->ltag = Thread;
//T的左孩子指针指向pre(前驱)
T->lchild = pre;
}
else {
T->ltag = Linked;
}
//pre右孩子为空
if (!pre->rchild) {
//给pre加上右线索
pre->rtag = Thread;
//pre的右孩子指针指向T(后继)
pre->rchild = T;
} else {
pre->rtag = Linked;
}
//保持pre指向T
pre = T;
//右子树递归线索化
InThreading(T->rchild);
}
}
//带头节点的二叉树中序线索化
void InOrderThreading(BiTree* Thrt, BiTree T) {
//排除树非空情况
if (!T) {
return;
}
//创建头结点
*Thrt = (BiTree)malloc(sizeof(BitNode));
if (!(*Thrt)) {
exit(-1);
}
//将头结点初始化
//头节点有左孩子
(*Thrt)->ltag = Linked;
//头节点的右孩子指针为右线索
(*Thrt)->rtag = Thread;
//头节点的左孩子指向根
(*Thrt)->lchild = T;
//初始化时右指针指向自己
(*Thrt)->rchild = *Thrt;
//pre始终指向已经线索化的结点
pre = *Thrt;
//以T为根的二叉树进行中序线索化
InThreading(T);
//线索化结束后,pre为最右节点,将最后一个结点pre线索化,即pre的右线索指向头节点
pre->rtag = Thread;
pre->rchild = *Thrt;
//头节点的右线索指向pre
(*Thrt)->rchild = pre;
}
//中序遍历线索化二叉树
void InOrderTraverse(BiTree Thrt) {
//指向根结点
BiTree p = Thrt->lchild;
//p == Thrt 为循环一遍
while (p != Thrt) {
//找到每个子树的第一个结点
while (p->ltag == Linked) {
p = p->lchild;
}
//访问左子树为空的节点
printf("%2c", p->data);
//依次遍历直接后继, 直到转移到右子树或者遍历结束
while (p->rtag == Thread && p->rchild != Thrt) {
p = p->rchild;
//沿右线索访问后继节点
printf("%2c", p->data);
}
//转移到右子树
p = p->rchild;
}
printf("\n");
}
//销毁线索化二叉树
void Destroy(BiTree* Thrt) {
//p 指向根结点
BiTree p = (*Thrt)->lchild;
BiTree adjust = p;
//p 指向头结点代表遍历结束
while (p != *Thrt) {
//找到中序遍历的第一个结点
while (p->ltag == Linked) {
p = p->lchild;
}
//调整指针adjust指向p指向的结点
//p指向直接后继(即子树的根结点)
//释放掉adjust结点(左孩子)的内存空间
while (p->rtag == Thread && p->rchild != *Thrt) {
adjust = p;
p = p->rchild;
free(adjust);
}
//更新adjust
adjust = p;
//p最后再指向右孩子
p = p->rchild;
//释放掉adjust(子树根结点)的内存空间
free(adjust);
}
//最后释放头结点
free(*Thrt);
//给野指针赋值
*Thrt = NULL;
adjust = p = NULL;
}
// main.cpp
// 二叉树
//
// Created by peach on 2022/12/5.
//
#include
#include "BinTreeTread.h"
#include
int main(void) {
//定义二叉树和头节点
BiTree T1, Thrt = nullptr;
//定义变量choice,决定是否进行二叉树的销毁
int choice;
//创建并初始化二叉树
printf("在执行功能选择前请按先序次序输入二叉树中结点的值(一个字符),创建二叉链表表示的二叉树:\n");
printf("提示:若输入的序列不符合先序次序,程序无法进行功能选择界面!\n");
CreateBiTree(&T1);
while(1){
printf("-------------------------------------------------------\n");
printf("---------------------功能选择界面------------------------\n");
printf("1--->进行二叉树中序线索化\n");
printf("--功能2提示:在执行此功能之前,请执行功能1完成二叉树的中序线索化!--\n");
printf("2--->遍历中序线索二叉树\n");
printf("--功能3提示:销毁二叉树操作请在二叉树非空情况下进行!-------------\n");
printf("3--->进行二叉树的销毁\n");
printf("4--->退出功能选择!\n");
printf("请输入您的功能选择:\n");
scanf("%d",&choice);
switch(choice){
case 1:
//进行二叉树中序线索化
InOrderThreading(&Thrt, T1);
printf("二叉树中序线索化成功!\n");
break;
case 2:
//遍历中序线索二叉树
printf("遍历中序线索二叉树:\n");
InOrderTraverse(Thrt);
break;
case 3:
//确保二叉树操作在二叉树非空情况下进行
if(Thrt){
Destroy(&Thrt);
}
else{
printf("二叉树为空!不可进行销毁!\n");
}
if (!Thrt) {
printf("二叉树销毁成功!\n");
}
break;
case 4:
printf("祝您愉快,欢迎下次使用!\n");
exit(0);
default:printf("请输入1~4实现功能选择!!!\n");
}
}
return 0;
}
3.线索二叉树简图
(1)二叉树创建及初始化、二叉树中序线索化
(2)功能二:遍历中序线索二叉树
(3)功能三:销毁二叉树
(4)结束程序运行
(1)枚举类型的使用
typedef enum {Linked,Thread},使用此方法,可代替一成不变的int类型标志,使程序更加规范化,更具有高级感。
(2)递归算法的多次使用
由于二叉树本身就属于递归的数据结构,递归算法在本程序中出现多次。在函数void CreateBiTree(BiTree* T)中,为了初始化二叉树,使用递归对每个节点进行赋值,从根节点开始,然后在左子树和右子树上进行递归。如下:
//输入的数据作为二叉树根节点数据域
(*T)->data = ch;
CreateBiTree(&((*T)->lchild));
CreateBiTree(&((*T)->rchild));
此外,在函数void InThreading(BiTree T)中,同样使用递归,完成了根和其左子树及右子树的线索化。总之,递归算法的使用,使程序更加简洁,可读性大大提高。
(3)main函数中的功能选择菜单设计
核心是采用一个while循环和一个switch语句,将功能选择的输入语句和switch语句全部放入循环,待程序执行完一种功能后,进行下一次循环即功能选择,直到用户选择其中choice==4,即退出程序,整个运行才算全部结束。此结构借鉴了曾经设计过的学生成绩管理系统的菜单结构,总体结构比较清晰,程序功能可进行多次选择,可一次性测试此多维数组的各种功能,省去了多次执行程序和输入数据的时间。在此次实验中,数据的输入放在了功能选择前执行,并在功能选择菜单中添加了功能提示,大大提高了程序可操作性。
(4)程序健壮性的体现
由于初始化二叉树中输入的数据为字符类型,因此程序本身健壮性很高。
本程序以两处为例,体现健壮性的提高
I switch语句中的default语句,在输入的数字不属于1~4时,若不采用default语句,程序不会作出任何提示,大大降低了程序健壮性和用户体验感。
II main函数中,功能三“二叉树销毁”中,添加对二叉树是否为空的判断语句后,可以很好的避免二叉树为空但被迫执行销毁操作导致报错的情况,大大提高了程序健壮性。
通过对二叉树线索化相关操作代码的编写,我较好的掌握了二叉树线索化的建立、遍历和销毁操作,除此之外,我还掌握了如何寻找节点的前驱和后继的方法,对二叉树有了更深的理解。
同时二叉树是一类具有层次关系的非线性数据结构,但其本身就是一个递归的数据结构,因此,递归思想在此次编写程序的过程中得到多方面运用。
分享结束,感谢观看!!!