南昌航空大学实验报告
课程名称: 数据结构A 实验名称: 实验六 二叉树及其应用 (一)
班 级: XXX 学生姓名: XXX 学号: XXXXX
指导教师评定: XXX 签 名: XXX
一、实验目的
树形结构是一类重要的非线性数据结构,树中结点之间具有明确的层次关系。本实验是有关二叉树的存储结构及其遍历的应用。
二、实验内容
在实验中,设计一程序求二叉树上结点的路径。
三、程序分析
在二叉树上无论采用哪种遍历方法,都能够访问遍树中的所有结点。由于访问结点的顺序不同,前序遍历和中序遍历都很难达到设计的要求;但采用后序遍历二叉树是可行的,因为后序遍历是最后访问根结点,按这个顺序将访问过的结点存储到一个顺序栈中,然后再输出即可。因此我们可非递归地后序遍历二叉树bt,当后序遍历访问到结点*p时,此时栈stack中存放的所有结点均为给定结点*p的祖先,而由这些祖先便构成了一条从根结点到结点*p之间的路径。
为了加深对遍历二叉树的理解,在这里把实现二叉树的非递归遍历概念也加入到这个设计要求中。
本程序共分四个部分:
(1)二叉树建立;
(2)求指定结点路径;
(3)二叉树的前、中、后序非递归遍历算法;
(4)查找函数。
四、程序源代码
过程见后续,不想看过程的直接拉到底即可。
编写准备
看一下二叉树的定义:
二叉树(Binary tree)是n(n≥0)个结点的有限集合。若n=0时称为空树,否则:
⑴ 有且只有一个特殊的称为树的根(Root)结点;
⑵ 若n>1时,其余的结点被分成为二个互不相交的子集T1,T2,分别称之为左、右子树,并且左、右子树又都是二叉树。左右子树次序不能任意颠倒。
以及二叉树的存储结构:顺序存储结构和链式存储结构。
二叉树的遍历:
定义:遍历二叉树(Traversing Binary Tree):是指按指定的规律对二叉树中的每个结点访问一次且仅访问一次。
常用的遍历方法有:
先序(DLR)----根、左子树、右子树;
中序(LDR)---左子树、根、右子树;
后序(LRD)---左子树、右子树、根;
层次遍历,从上到下、从左到右
并且对于二叉树的遍历有递归遍历算法和非递归遍历算法。
设计过程
定义部分
二叉树的存储结构分为顺序存储结构和链式存储结构,这块采用链式存储结构,以及使用常用的二叉链表结点定义。
二叉链表结点。有三个域:一个数据域,两个分别指向左右子结点的指针域。
typedef struct BTNode
{ ElemType data ;
struct BTNode *Lchild , *Rchild ;
}BTNode ,*BinTree;
创建二叉树部分
这里采用按先序遍历方式建立
BTNode *Create_BTree(BinTree &T)//建立链式二叉树,返回指向根结点的指针变量
{
ElemType ch;
ch=getchar();getchar();
if(ch=='?')
{
T=NULL;
return(T);
}
else
{
T=(BTNode *)malloc(sizeof(BTNode));
T->data=ch;
Create_BTree(T->Lchild);
Create_BTree(T->Rchild);
return(T);
}
}
遍历部分
对于二叉树的遍历有递归遍历算法和非递归遍历算法,同时又分为先、中、后序遍历。
1.先序非递归遍历算法:先输出根节点,然后是左子树、右子树
void PreOrder_Traverse(BinTree t)//先序非递归遍历算法
{
BinTree st[MAX_SIZE];//声明一个栈数组,用于实现LIFO结点调用
int top=-1;
while(t||top!=-1)
{
while(t)//一路向左
{
printf("%c",t->data);
st[++top]=t;//入栈
t=t->Lchild;
}
if(top!=-1)//取出一个右结点
{
t=st[top--];//top--是为了查询最左结点的右结点,而不是--top
t=t->Rchild;
}
}
}
2.中序非递归遍历算法:先输出左子树,然后是根节点、右子树
void InOrder_Traverse(BinTree t)//中序非递归遍历算法
{
BinTree st[MAX_SIZE];//声明一个栈数组,用于实现LIFO结点调用
int top=-1;
while(t||top!=-1)
{
while(t)//一路向左
{
st[++top]=t;//入栈
t=t->Lchild;
}
if(top!=-1)//取出一个右结点
{
t=st[top--];//top--是为了查询最左结点的右结点
printf("%c",t->data);
t=t->Rchild;
}
}
}
3.后序非递归遍历算法:先输出左子树,然后是右子树、根节点
这一部分实现不同于前面两个。
先分析一下先序为DLR,后序为LRD
将先序遍历的左右结点颠倒,则得到DRL
再将DRL进行一个颠倒(即一轮入栈和出栈)即为LRD,从而实现后序非递归遍历算法
//先序:DLR,后序:LRD
//左右结点颠倒的先序遍历:DRL
//DRL进行一个颠倒(即一轮入栈和出栈)即为LRD,从而实现后序非递归遍历算法
void PostOrder_Traverse(BinTree t)//先序非递归遍历算法
{
BinTree st[MAX_SIZE];//声明一个栈数组,用于实现LIFO结点调用
int top=-1;
ElemType s[MAX_SIZE];//建立顺序栈,进行压栈与出栈行为
int top1=0;
while(t||top!=-1)
{
while(t)//一路向左
{
s[top1++]=t->data;//先序时为(printf("%c",t->data);),这里将其进行压栈
st[++top]=t;//入栈
t=t->Rchild;//先序时为(t=t->Lchild;),这里进行左右结点颠倒
}
if(top!=-1)//取出一个右结点
{
t=st[top--];//top--是为了查询最左结点的右结点
t=t->Lchild;//先序时为(t=t->Rchild;),这里进行左右结点颠倒
}
}
while(top1--)printf("%c",s[top1]);//进行出栈,完成颠倒
}
求指定结点路径部分
鉴于上一部分遍历的设计,以及要求中的“一条从根结点到结点*p之间的路径”这块选择最为容易且最合适的先序遍历算法(先输出根节点,然后是左子树、右子树)。
void Find_path(BinTree t,ElemType c)//求指定结点路径
{
static ElemType path[MAX_SIZE];
static int i=0;
if(!t) return; //当前访问的结点为空,直接返回
if(t->data==c)//已找到目标
{
printf("路径为:");
for(int j=0;j<i;j++)printf("%c->",path[j]);//输出目标结点的祖先
printf("%c\n",t->data);//输出目标结点
return;
}
else
{
path[i++]=t->data;
Find_path(t->Lchild,c);
Find_path(t->Rchild,c);//利用先序遍历算法来查找路径,从根结点开始查,仅查祖先
i--;
}
}
源代码:
#include<stdio.h>
#include<stdlib.h>
#define ElemType char
#define MAX_SIZE 100
typedef struct BTNode
{
ElemType data;//结点数据
struct BTNode *Lchild;//左子结点指针
struct BTNode *Rchild;//右子结点指针
}BTNode,*BinTree;
BTNode *Create_BTree(BinTree &T)//建立链式二叉树,返回指向根结点的指针变量
{
ElemType ch;
ch=getchar();getchar();
if(ch=='?')
{
T=NULL;
return(T);
}
else
{
T=(BTNode *)malloc(sizeof(BTNode));
T->data=ch;
Create_BTree(T->Lchild);
Create_BTree(T->Rchild);
return(T);
}
}
void PreOrder_Traverse(BinTree t)//先序非递归遍历算法
{
BinTree st[MAX_SIZE];//声明一个栈数组,用于实现LIFO结点调用
int top=-1;
while(t||top!=-1)
{
while(t)//一路向左
{
printf("%c",t->data);
st[++top]=t;//入栈
t=t->Lchild;
}
if(top!=-1)//取出一个右结点
{
t=st[top--];//top--是为了查询最左结点的右结点,而不是--top
t=t->Rchild;
}
}
}
void InOrder_Traverse(BinTree t)//中序非递归遍历算法
{
BinTree st[MAX_SIZE];//声明一个栈数组,用于实现LIFO结点调用
int top=-1;
while(t||top!=-1)
{
while(t)//一路向左
{
st[++top]=t;//入栈
t=t->Lchild;
}
if(top!=-1)//取出一个右结点
{
t=st[top--];//top--是为了查询最左结点的右结点
printf("%c",t->data);
t=t->Rchild;
}
}
}
//先序:DLR,后序:LRD
//左右结点颠倒的先序遍历:DRL
//DRL进行一个颠倒(即一轮入栈和出栈)即为LRD,从而实现后序非递归遍历算法
void PostOrder_Traverse(BinTree t)//先序非递归遍历算法
{
BinTree st[MAX_SIZE];//声明一个栈数组,用于实现LIFO结点调用
int top=-1;
ElemType s[MAX_SIZE];//建立顺序栈,进行压栈与出栈行为
int top1=0;
while(t||top!=-1)
{
while(t)//一路向左
{
s[top1++]=t->data;//先序时为(printf("%c",t->data);),这里将其进行压栈
st[++top]=t;//入栈
t=t->Rchild;//先序时为(t=t->Lchild;),这里进行左右结点颠倒
}
if(top!=-1)//取出一个右结点
{
t=st[top--];//top--是为了查询最左结点的右结点
t=t->Lchild;//先序时为(t=t->Rchild;),这里进行左右结点颠倒
}
}
while(top1--)printf("%c",s[top1]);//进行出栈,完成颠倒
}
void Find_path(BinTree t,ElemType c)//求指定结点路径
{
static ElemType path[MAX_SIZE];
static int i=0;
if(!t) return; //当前访问的结点为空,直接返回
if(t->data==c)//已找到目标
{
printf("路径为:");
for(int j=0;j<i;j++)printf("%c->",path[j]);//输出目标结点的祖先
printf("%c\n",t->data);//输出目标结点
return;
}
else
{
path[i++]=t->data;
Find_path(t->Lchild,c);
Find_path(t->Rchild,c);//利用先序遍历算法来查找路径,从根结点开始查,仅查祖先
i--;
}
}
int main()
{
BinTree t=NULL;//根节点指针
ElemType ch;
printf("按照先序遍历输入二叉树:");
t=Create_BTree(t);
printf("先序遍历输出:");
PreOrder_Traverse(t);printf("\n");
printf("中序遍历输出:");
InOrder_Traverse(t);printf("\n");
printf("后序遍历输出:");
PostOrder_Traverse(t);printf("\n");
while(1)
{
printf("输入要查询路径的结点:");
scanf("%c",&ch);getchar();
Find_path(t,ch);printf("\n");
}
return 0;
}
运行结果
以下图为例,输入的字符序列应当是:ABD??E?G??CF???