实验7-二叉树的应用
- 1)实验目的
通过该实验,使学生理解二叉树的链式存储,掌握二叉树的几种遍历算法,并通过该实验使学生理解递归的含义,掌握C语言编写递归函数的方法和注意事项.
- 2)实验内容
实现教材中算法6.4描述的二叉树创建算法,在此基础上实现二叉树的先序、后序递归遍历算法、两种非递归中序遍历、层序遍历、求二叉树的深度。注意:在非递归算法中用到栈和队列时,不要调用系统的栈和队列,需要自己实现栈和队列的操作。
- 3)验收/测试用例
- 创建 输入 :
ABC$$DE$G$$F$$$ ($表示空格)
- 先序 屏幕输出 A B C D E G F
- 后序 屏幕输出 C G E F D B A
- 中序 屏幕输出 C B E G DF A (两种中序非递归还需看源代码)
- 层序 屏幕输出 A B C D E F G
- 深度 屏幕显示 深度为5
另外自己画出一棵树,再测试一遍。
一、设计思想
- 程序按照数据结构大致分为3部分:
-
1.1. 链二叉树结构 + 功能函数声明、定义: (先序输入空格补位的二叉树)
-
1.2. 自定义定长顺序栈 + 功能函数声明、定义: 二叉树的 两种 中序非递归遍历算法,需要使用自定义的栈来完成
-
1.3. 自定义链队列 + 功能函数声明、定义: 二叉树的 层序 非递归遍历算法,需要使用自定义的队列完成
- 需要注意的问题:
- 2.1.对于创建二叉树,输入的是 普通树 补全版的 二叉树,对于 左右孩子为空的情况采用 空格 补齐,所以,获取输入的树 节点 不能使用cin\scanf();因为这两个读取函数会忽略空格,使用getchar()可以将空格当做字符读入。
- 2.2. 对于 未初始化树树为空的情况,要求执行功能函数需要提示 树为空,这里采用返回值为0或1来判断,对于遍历操作是递归的,这里是在 函数开始的时候判断 if(t),如果树为空,直接return 0,不在遍历;在函数尾部设置return 1,(内层递归并不此返回值的影响),另外对于栈、队列开辟空间失败的情况 return -1,终止操作。
- 2.3. 自己之前自定义的 栈数据结构 出栈元素采用return的形式返回,不兼容课本上的 中序非递归 算法,做了一些调整,使用&e来 及时 获取出栈、入栈下的 栈顶的元素
- 先序、后序、求树的深度 算法思想思想类似,只是对输出语句的位置做出调整,树的深度,就是返回左子树、右子树中最大的深度,然后+1
- 两种中序非递归算法 由 循环调整指针 + 入栈、出栈 来实现
- 4.1. 算法的关键在于:在遍历完后 某个节点 的 左子树 后,怎么 找到 右子树,需要将访问过得节点压栈,当访问指针为空(即当前访问节点的左子树已经没有左子树),就要弹栈,调整p指针指向弹栈的元素,输出,然后调整指针指向右孩子,再以右孩子开启逻辑中序遍历
- 4.2 对于非递归中序遍历1,可以把每个节点都当成逻辑根节点,开始先移动指针到逻辑第一个输出节点(此时一定是最左边的节点),只不过 待输出的 节点值 的 左孩子为NULL,因此弹出左孩子即可,接着弹出 待输出 节点,然后打印,并修改指针指向 右孩子,然后下层循环
- 4.3.对于非递归中序遍历2,和1思想类似,只不过更明确一些,指针不断顺着左孩子链移动,并且经过的节点全部入栈,然后知道移动到指针为NULL,即栈顶元素为待输出节点,出栈,打印,调整指针指向右孩子,进入下层循环,
打印过得元素都不在栈内,数的根节点在栈底,当访问完 左子树 后,也会打印根节点,然后根节点出栈,调整指针 指向 右子树,然后…
- 对于非递归层序遍历的实现,基于队列的,算法思想是,先根节点入队,然后从队列中取出一个元素,打印,如果该打印节点的左、右孩子不为空,就入队,即每次出父亲,入孩子。
二、源代码
# include<iostream>
# include<stdio.h>
# include<stdlib.h>
using namespace std;
//一、二叉树的链式存储
typedef char TElemType;
typedef int Status;
typedef struct BiTNode{
TElemType data;
struct BiTNode *lchild;
struct BiTNode *rchild;
}*BiTree;
//二、自定义的栈
# define STACK_INIT_SIZE 88
# define STACKINCRMENT 6
typedef BiTree SElemType;
typedef struct {
SElemType *base;
SElemType *top;
int stacksize;
}MyStack;
// 三、自定义队列
typedef BiTree QElemType;
typedef struct QNode {
QElemType data;
struct QNode *next;
}*QueuePtr;
//把两个指针封装在一个结构体内
//front、rear都是指向队列节点的结构体指针
typedef struct{
QueuePtr front;
QueuePtr rear;
}LinkQueue;
//函数声明
//一、树的功能函数声明
//1. 先序创建二叉树
Status CreateBiTree(BiTree &t);
//2. 先序遍历二叉树
Status Pre(BiTree t) ;
//3. 打印节点值函数
Status PrintElem(TElemType e);
//4. 中序遍历二叉树(递归)
Status Middle(BiTree t);
//5.1 中序遍历二叉树(非递归1)
Status Middle_otherOne(BiTree t,MyStack &ms);
//5.2 中序遍历二叉树(非递归2)
Status Middle_otherTwo(BiTree t,MyStack &ms);
//6. 后续遍历二叉树
Status Last(BiTree t);
//7. 层序遍历二叉树
Status cengXu(BiTree t,LinkQueue &Q);
//8. 求二叉树的深度
int getBiTreeLength(BiTree t);
//二、栈的功能函数声明
// 1. 初始化栈
Status InitStack(MyStack &s);
// 2. 栈判空
bool JudgeEmpty(MyStack s) ;
// 3. 插入一个元素(入栈)
Status Push(MyStack &s,SElemType e) ;
// 4. 删除一个元素(出栈)
SElemType Pop(MyStack &s,SElemType &e);
// 5. 获取栈顶元素
SElemType GetTop(MyStack s,SElemType &e);
//三、队列函数声明
// 1. 初始化队列函数
void InitQueue(LinkQueue &Q) ;
// 2. 销毁队列
void DestoryQueue(LinkQueue &Q);
// 3. 清空队列
void ClearQueue(LinkQueue &Q) ;
// 4. 队列判空
bool JudgeEmpty(LinkQueue Q);
// 5. 求队列长度
int GetLength(LinkQueue Q);
// 6. 获取队头元素
QElemType GetFront(LinkQueue Q);
// 7. 插入一个元素(入队)
void InsertElem(LinkQueue &Q,QElemType elem) ;
// 8. 删除一个元素(出队)
QElemType DeleteElem(LinkQueue &Q) ;
int main()
{
//栈节点
MyStack ms;
ms.base = NULL;
ms.top = NULL;
//树节点
BiTree t;
t = NULL;
//队列节点
LinkQueue Q;
Q.front = NULL;
Q.rear = NULL;
bool flag = true;
int n;
while(flag){
cout<<"☆欢迎使用二叉树的操作小程序☆"<<endl;
cout<<"Design by 1925050351-软6-司超龙"<<endl<<endl;
cout<<" 1. 创建二叉树"<<endl;
cout<<" 2. 先序遍历二叉树"<<endl;
cout<<" 3. 中序遍历二叉树1"<<endl;
cout<<" 4. 中序遍历二叉树2"<<endl;
cout<<" 5. 后序遍历二叉树"<<endl;
cout<<" 6. 层序遍历二叉树"<<endl;
cout<<" 7. 求二叉书的深度"<<endl;
cout<<" 8. 退出"<<endl<<endl;
cout<<"请输入相应指令完成相关操作:"<<endl;
cin>>n;
//功能函数返回值
Status s;
switch(n){
case 1:
system("cls");
cout<<"请按先序输入节点的值:"<<endl<<endl;
getchar();
s = CreateBiTree(t) ;
if(s == -1){
cout<<"二叉树创建失败!节点空间开辟失败,请重新创建二叉树~"<<endl<<endl;
free(t);
}
else if(s == 1){
cout<<"二叉树创建成功!"<<endl<<endl;
}
break;
case 2:
system("cls");
s = Pre(t);
if( s == 0){
cout<<"树为空!" ;
}
cout<<endl<<endl;
break;
case 3:
system("cls");
//Middle(t);中序递归遍历
s = Middle_otherOne(t,ms);
if( s == 0){
cout<<"树为空!"<<endl;
}
else if(s == -1){
cout<<"栈开辟失败!请重新操作~" <<endl;
}
cout<<endl<<endl;
break;
case 4:
system("cls");
s = Middle_otherTwo(t,ms);
if(s == 0){
cout<<"树为空!"<<endl;
}
else if(s == -1){
cout<<"栈开辟失败!请重新操作~" <<endl;
}
cout<<endl<<endl;
break;
case 5:
system("cls");
s = Last(t);
if( s == 0){
cout<<"树为空!";
}
cout<<endl<<endl;
break;
case 6:
system("cls");
s = cengXu(t,Q);
if( s == 0){
cout<<"树为空!";
}
cout<<endl<<endl;
break;
case 7:
system("cls");
cout<<"树的深度为:"<<getBiTreeLength(t)<<endl<<endl;
break;
case 8:
system("cls");
flag = false;
cout<<"退出程序成功!欢迎下次使用~~"<<endl<<endl;
break;
default:
system("cls");
cout<<"输入指令有误!请重新输入~~" <<endl<<endl;
break;
}
}
}
//三、自定义栈功能函数实现
//1. 初始化一个栈
Status InitStack(MyStack &s){
s.base = (SElemType *)malloc(STACK_INIT_SIZE *sizeof(SElemType));
if(s.base == NULL){
return -1;
}
s.top = s.base;
s.stacksize = STACK_INIT_SIZE;
return 1;
}
//2. 栈判空
bool JudgeEmpty(MyStack s){
if(s.base == s.top){
return true;
}
else
return false;
}
//3. 插入一个元素
Status Push(MyStack &s,SElemType e){
if(s.base == NULL || s.top == NULL){
return -1;
}
*(s.top++) = e;
return 1;
}
//4. 删除一个元素(出栈)
SElemType Pop(MyStack &s,SElemType &e){
if(s.base == NULL){
return NULL;
}
e = *(-- s.top);
}
//5. 获取栈顶元素
SElemType GetTop(MyStack s,SElemType &e) {
if(s.base == NULL){
return NULL;
}
e = *(--s.top);
//注意优先级,先使用还是先减减
//return *(--s.top);
//return ;
}
//四、二叉树功能实现
//1. 先序创建二叉树
Status CreateBiTree(BiTree &t){
//按照先序 输入二叉树 节点的值(全部输入后一次回车)
TElemType ch;
//cin>>ch;
ch = getchar();
//回车键
if(ch == 13)
return 1;
//if(ch == '\n')
//return 1;
//ch = getchar();
//空格键
if(ch == 32){
t = NULL;
}
else{
if(!(t = (BiTNode *)malloc (sizeof(BiTNode)))){
//节点空间开辟失败,返回值-1
return -1;
}
t->data = ch;
//递归节点的左子树
CreateBiTree(t->lchild);
//递归节点的右子树
CreateBiTree(t->rchild);
}
return 1;
}
//2. 先序遍历二叉树
Status Pre(BiTree t){
if(!t){
return 0;
}
if(t){
PrintElem(t->data);
Pre(t->lchild);
//PrintElem(t->data);
Pre(t->rchild);
//PrintElem(t->data);
}
//作为程序执行完成的标志,便于区分是不是空树
return 1;
}
//3. 打印节点值函数
Status PrintElem(TElemType e){
cout<<e<<" ";
return 1;
}
//4. 中序遍历二叉树(递归)
Status Middle(BiTree t){
if(!t){
return 0;
}
if(t){
Middle(t->lchild);
//PrintElem(t->data);
PrintElem(t->data);
Middle(t->rchild);
//PrintElem(t->data);
}
return 1;
}
//5.1 中序遍历二叉树(非递归1)
Status Middle_otherOne(BiTree t,MyStack &ms){
if(!t){
return 0;
}
BiTree p;
int stack_flag = InitStack(ms);
//先开栈,根据返回值判断是否开辟成功
if( stack_flag != -1) {
//根指针进栈
//根节点 在 栈底
//父节点永远在子节点下面
Push(ms,t);
while(!JudgeEmpty(ms)){
//有栈顶元素 ,并且节点不为NULL
while(GetTop(ms,p) && p){
Push(ms,p->lchild);
}
//直到左孩子为NULL,此时栈顶为整棵树最左边的节点,将NULL进栈,循环结束
//弹出栈顶空节点NULL ,因为子孩子为NULL,不需要打印值
Pop(ms,p) ;
if(!JudgeEmpty(ms)){
//弹出最上面的节点 ,也就是逻辑中序第一个节点
Pop(ms,p);
//打印值
PrintElem(p->data);
//就是在这 调整 指针
Push(ms,p->rchild);
//如果有右孩子,则右孩子进栈,,接着以右孩子为逻辑根节点来中序遍历,判断右孩子是否有左孩子和右孩子....
//如果没有右孩子,就将NULL压进栈,下次循环会被弹出并不打印
}
}
return 1;
}
//栈空间开辟失败
else{
return -1;
}
}
//5.2 中序遍历二叉树(非递归2)
Status Middle_otherTwo(BiTree t,MyStack &ms){
//树为空
if(!t){
return 0;
}
BiTree p = t;
int stack_flag = InitStack(ms);
//先开栈,根据返回值判断是否开辟成功
if( stack_flag != -1) {
while(p || !(JudgeEmpty(ms))) {
if(p){
Push(ms,p);
p = p->lchild;
}
else{
Pop(ms,p);
PrintElem(p->data);
p = p->rchild;
}
}
}
else{
return -1;
}
return 1;
}
//6. 后续遍历二叉树
Status Last(BiTree t){
if(!t){
return 0;
}
if(t){
Last(t->lchild);
//PrintElem(t->data);
Last(t->rchild);
PrintElem(t->data);
//PrintElem(t->data);
}
return 1;
}
//7. 层序遍历二叉树
Status cengXu(BiTree t,LinkQueue &Q){
//空树直接返回
if(!t){
return 0;
}
BiTree p = t;
InitQueue(Q);
InsertElem(Q,t) ;
while(!JudgeEmpty(Q)){
p = DeleteElem(Q);
PrintElem(p->data);
if(p->lchild){
InsertElem(Q,p->lchild);
}
if(p->rchild){
InsertElem(Q,p->rchild);
}
}
return 1;
}
//8. 求二叉树的深度
int getBiTreeLength(BiTree t){
if(t == NULL){
return 0;
}
else{
int n = getBiTreeLength(t->lchild);
//PrintElem(t->data);
int m = getBiTreeLength(t->rchild);
//PrintElem(t->data);
int max = m > n? (m + 1):(n + 1);
return max;
}
}
//五、自定义队列
// 1. 初始化队列函数
void InitQueue(LinkQueue &Q){
Q.front = (QueuePtr )malloc(sizeof(QNode));
Q.rear = Q.front;
}
// 2. 销毁队列
void DestoryQueue(LinkQueue &Q){
//队列元素为空
if(Q.front == Q.rear){
free(Q.front);
Q.front = NULL;
Q.rear = NULL;
}
//队列不为空,边移动,边释放空间,rear紧邻在front前开路
else{
while(Q.front){
Q.rear = Q.front->next;
free(Q.front);
Q.front = Q.rear;
}
Q.front = NULL;
}
}
// 3. 清空队列
void ClearQueue(LinkQueue &Q){
InitQueue(Q);
}
// 4. 队列判空
bool JudgeEmpty(LinkQueue Q){
if(Q.front == Q.rear){
return true;
}
else{
return false;
}
}
// 5. 求队列长度
int GetLength(LinkQueue Q){
int length = 0;
while(Q.front != Q.rear){
length ++;
Q.front = Q.front->next;
}
return length;
}
// 6. 获取队头元素
QElemType GetFront(LinkQueue Q){
return Q.front->next->data;
}
// 7. 插入一个元素(入队)
void InsertElem(LinkQueue &Q,QElemType elem){
QueuePtr p = (QueuePtr )malloc(sizeof(QNode));
p->data = elem;
Q.rear->next = p;
Q.rear = p;
}
// 8. 删除一个元素(出队)
QElemType DeleteElem(LinkQueue &Q){
//注意顺序,Q.front指向的当前节点不做数据节点
Q.front = Q.front->next;
QElemType elem = Q.front->data;
return elem;
}
// 9. 输出所有的元素
QElemType PrintElem(QueuePtr p){
return p->data;
}
三、部分运行截图
四、小结
- 熟悉了二叉树的建立,递归遍历,非递归遍历,层序遍历的相关操作,遍历融合栈、队列的综合操作、体会到程序健壮性的好处,自定义类型名实现快速修改定义数据类型,练习尽量在功能函数中使用返回值 提高功能函数独立性、健壮性
- 遇到的问题就是,对于非递归中序1的逻辑开始不太理解,后来跟着代码顺了几遍,清晰了许多,相对于非递归中序2,还是后者更容易理解