资源描述
。
青岛理工大学
数据结构课程设计报告
题目: 树的应用
院(系): 计算机工程学院
学生姓名: 管巨伟
班级: 软件132 学号: 201307227
起迄日期: 7.13-7.24
指导教师: 李传斌 杨鑫
-可编辑修改-
任务书
一、《数据结构课程设计》的目标
课程设计是《数据结构》课程的一个重要的实践环节,它可加深学生对该课程所学内容的进一步的理解与巩固,达到理论与实际应用相结合,提高学生组织数据及编写大型程序的能力,培养基本的对基本数据结构的理解和运用,良好的程序设计方法、提高编码及调试程序技能的能力,为整个专业的学习以及软件设计水平的提高打下良好的基础。
二、设计内容及要求
【问题描述】
(1) 要求实现树与二叉树的转换;
(2) 树的先序、后序的递归、非递归算法,层次序的非递归算法的实现;
(3) 在树中进行查找、插入(新插入的结点以叶子的形式插入树中)和删除的运算。
【设计要求】
遍历内容可以是数值可以是字符或者记录等等。
一、需求分析
1.问题描述:
该课题要求自己了解树的基本应用,能够使用先序遍历、后序遍历、层次遍历来遍历树的结点。掌握递归方法与非递归方法对树的操作。能够操作树,在树中进行插入、删除、查找等操作。熟练掌握树与二叉树的对应关系,在两者间进行熟练转化。这就使整个程序需要用递归及非递归方法进行树的先序、后序、层次遍历,能在树中插入、删除叶子结点,同时进行任意结点的查找,同时将树与二叉树进行转换。
2.基本功能
1、 通过用户的输入创建一棵树;
2、 先序递归遍历整棵树;
3、 先序非递归遍历整棵树;
4、 后序递归遍历整棵树;
5、 后序非递归遍历整棵树;
6、 层次非递归遍历整棵树;
7、 将树转换为二叉树;
8、 查找、删除、插入用户输入的对应元素。
二、 概要设计
1.设计思路:
树的建立过程采用子节点与父节点关联方法,寻找父节点,将子节点放入父节点的指针域,依次循环建立,完成建树过程。先序遍历的递归方法每次遍历到一个节点即输出节点的数据,依次调用遍历函数即可。先序非递归遍历过程用数组存储节点,每当存入一个节点即判断节点的孩子是否为空,为空停止采用标记位置返回父节点,遍历父节点的其他孩子,不为空时继续遍历该节点的孩子,直到孩子节点为空的节点。后序遍历递归类似先序遍历递归,此时输出节点数据域放在最后,而不是遍历递归之前。后序遍历非递归,建立一个栈,存入根节点,开始循环存入节点的孩子,存完孩子节点此时栈顶节点出栈,并给予标记,如有孩子节点遍历该节点的孩子节点存入栈中,如果没有,输出当前节点的数据域,依次循环即可。层次遍历过程定义数组,遍历节点的所有孩子节点存入,数组下标加一,遍历此时下标对应的节点的所有孩子节点存入数组,依次重复循环,最后输出数组中元素。树转化为二叉树过程,将树根结点存入树队列,出队,遍历到的第一个孩子节点放在二叉树根节点的左边,之后的孩子节点放在第一个孩子节点的右边,树队列节点再次出队,如此循环。插入时采用先序遍历方式查找到父节点,创建节点存储输入的元素,将该新节点放入找到的节点指针域即可。删除过程,遍历节点的孩子,当一个节点的其中一个孩子数据符合删除的节点,并且该节点的孩子为空,将节点的指针域置为空,并释放待删除节点的空间。查找采用先序遍历,找到后输出先序遍历的节点位置。
2.数据结构设计:
ADT Tree{
数据对象D:D是具有相同特性的数据元素的集合。
数据关系:若D为空集,则称树为空树;
若D仅含一个数据元素,则称R为空集,否则R={H},H是如下二元关系:
(1) 在D中存放唯一的称为根的数据元素root,它在关系H下无前驱;
(2) 若D-{root}!=Q,则存在D-{root}的一个划分D1,D2,D3,....Dm(m>0),对任意j!=k(1<=j,k<=m)有D1∩Dk=Q,且对任意的i(1<=i<=m),唯一存在数据元素xi<Di,有<root,xi>∈H;
(3) 对应于D-{root}的划分,H-{<root,x1>,...<root,xm>}有唯一的一个划分H1,H2...,Hm(m>0),对任意j!=k(1<=j,k<=m)有Hj∩Hk=Q,且对任意i(1<=i<=m),Hi是Di上的二元关系,(Di,{Hi})是一棵符合本定义的树,称为root的子树。
基本操作P:
CreateSTree();
操作结果:构造一棵树。
preorderTree( *T);
初始条件:树T存在。
操作结果:按先序遍历输出一棵树的所有结点。
prenorderTree( *T);
初始条件:树T存在。
操作结果:按先序遍历输出一棵树的所有结点。
lastnorderTree(*T);
初始条件:树T存在。
操作结果:按后序遍历输出一棵树的所有结点。
lastorderTree(*T);
初始条件:树T存在。
操作结果:按后序遍历输出一棵树的所有结点。
levelorderTree(*T);
初始条件:树T存在。
操作结果:按层次遍历输出一棵树的所有结点。
TreeToBTree(*T,*&BT);
初始条件:树T存在。
操作结果:将一棵树转化为二叉树,按二叉树层次遍历输出转化结果。
Search(*T,e);
初始条件:树T存在。
操作结果:返回指定结点在树中按先序遍历的位置。
Delete(*T,e);
初始条件:树T存在。
操作结果:删除指定叶子结点。
*Insert(*T,e);
初始条件:树T存在。
操作结果:将指定结点插入指定的父节点。
} ADT Tree
3.软件结构设计:
主函数
在树中插入结点
在树中查找结点
在树中删除叶子
树转化为二叉树
层次遍历
后序遍历递归
后序遍历非递归
后序遍历递归
创建树函数
先序遍历递归
先序遍历非递归
图1 程序结构
三、 详细设计
数据类型:
typedef struct st1//树节点的类型
{
char data;//数据域,采用char型
struct st1 *children[CHILD];//指向孩子节点的指针域
}CTreeNode;
typedef struct st2//二叉树节点
{
char data;//数据域
struct st2 *lchild,*rchild;//左右孩子节点的指针
}BTreeNode;
typedef struct nodeCTree
{
CTreeNode *CTreeArray[NODENUM];//结构体指针数组,存放节点
int CTreeFront,CTreeRear;
}QueueCTree;
//二叉树队列结构类型
typedef struct nodeBTree
{
BTreeNode *BTreeArray[NODENUM];//结构体指针数组,存放节点
int BTreeFront,BTreeRear;
}QueueBTree;
算法:
创建树:
CTreeNode *CreateSTree()//创建一棵树
{
int i,k,j;
char data, parent;
CTreeNode *root,*parentNode,*node;
printf("请输入树的节点的数量:");
if(nodenum==0)
return NULL;//空树,结束函数
printf("请输入根节点的数据:");
root=(CTreeNode *)malloc(sizeof(CTreeNode));//根节点
root->data=data;
for(i=0;i<CHILD;i++)
root->children[i]=NULL;
for(i=2;i<=nodenum;i++)//依次输入每个节点的信息
{
printf("请输入第%d个节点的数据及其父节点的数据:",i);
node=(CTreeNode *)malloc(sizeof(CTreeNode));
node->data=data;
for(k=0;k<CHILD;k++)
node->children[k]=NULL;
parentNode=SearchCTree(root,parent);//查找指定数据的节点
for(k=0;k<CHILD;k++)
{
if(parentNode->children[k]==NULL)
{
parentNode->children[k]=node;
break;
}
}
}
return root;
}
先序递归遍历:
void preorderTree(CTreeNode *ctroot)//先序遍历递归算法
{
CTreeNode *ctchild;
int i;
if(ctroot==NULL)
return;
printf("%c",ctroot->data);//先遍历根节点
for(i=0;i<CHILD;i++)//依次先序遍历孩子节点
{
ctchild=ctroot->children[i];
if(ctchild==NULL)
continue;//孩子节点遍历结束,退出
else
preorderTree(ctchild);//递归先序遍历
}
}
先序非递归遍历:
void preorderTree(CTreeNode *ctroot)//先序遍历非递归算法
{
CTreeNode *queue[NODENUM],*p;
int rear=0,front=0,mark;//mark作为指针往前一个节点跳的标志
queue[rear]=ctroot;
if(ctroot==NULL)
return;
if(queue[rear]->children[0]==NULL)//只有根节点的树
return;
p=queue[rear];
FOR: for(int i=0;i<CHILD;i++)//依次先序遍历孩子节点
{
p=p->children[i];
if(p==NULL)
break;//孩子节点遍历结束,退出
else
{
queue[++rear]=p->children[i];
p=queue[rear];
goto FOR;
}
}
while(front<=rear)
{
Visit(data);
}
后序递归遍历:
void lastorderTree(CTreeNode *ctroot)//后序遍历递归算法
{
if(ctroot==NULL)
return;
CTreeNode *ctchild;
int i;
for(i=0;i<CHILD;i++)//依次后序遍历孩子节点
{
ctchild=ctroot->children[i];
if(ctchild==NULL)
continue;//孩子节点遍历结束,退出
else
{
lastorderTree(ctchild);//递归后序遍历
Visit(data);
}
}
}
后序非递归遍历:
void lastnorderTree(CTreeNode *ctroot)//后序遍历非递归算法
{
if(ctroot==NULL)
return;
int k;
CTreeNode *stack[NODENUM],*p;
int mark[NODENUM],i;
int top = -1;
stack[++top] =ctroot;//根节点入栈
mark[top] = 0;
while(top>=0)//栈中有节点
{
p = stack[top];
for(i=0;i<CHILD;i++)
{
if(mark[top]==0&&p->children[i]!=NULL)//当子树不为空,并且未执行过入栈操作
{
mark[top] = 1;
for(k=CHILD-1;k>=0;k--)//从最右边的子树开始入栈
{
if(p->children[k]!=NULL)
{
stack[++top] = p->children[k];
mark[top] = 0;
}
}
break;//避免重复存入子节点
}
}
if(stack[top]->children[0]==NULL||mark[top]==1)//当节点子树为空或者标记为1时出栈
Visit(data);
}
}
层次遍历:
void levelorderTree(CTreeNode *ctroot)//树层次遍历非递归
{
if(ctroot==NULL)
return;
CTreeNode *queue[NODENUM],*p;
int front=0,rear=0,j;
queue[rear]=ctroot;
for(int i=0;i<NODENUM;i++)
{
p=queue[i];
j=0;
for(j=0;j<CHILD;j++)
{
if(p->children[j]!=NULL&&p!=NULL)
queue[++rear]=p->children[j];//入队
}
if(i==rear)//节点以全部入队停止循环
break;
}
while(front<=rear)
{
Visit(data);
}
}
树转化为二叉树:
void TreeToBTree(CTreeNode *ctroot,BTreeNode *&btroot)//树转化为二叉树ctroot指向树的根节点,btroot,指向二叉树的根
{
if(ctroot==NULL)
return;
QueueCTree *VisitedCTreeNodes;
QueueBTree *VisitedBTreeNodes;//辅助队列
initQueueCTree(VisitedCTreeNodes);
initQueueBTree(VisitedBTreeNodes);//初始化队列
CTreeNode *ctnode;
BTreeNode *btnode,*p,*LastSibling;
int i;
btroot=(BTreeNode *)malloc(sizeof(BTreeNode));//添加开辟内存空间语句
btroot->data=ctroot->data;//树的根节点作为二叉树的根节点
btroot->lchild=btroot->rchild=NULL;
addQueueCTree(VisitedCTreeNodes,ctroot);//树的跟节点入队
addQueueBTree(VisitedBTreeNodes,btroot);//二叉树的跟节点入队
while(!QueueCTreeEmpty(VisitedCTreeNodes))
{
ctnode=delQueueCTree(VisitedCTreeNodes);//树节点出队
btnode=delQueueBTree(VisitedBTreeNodes);//二叉树节点出队
for(i=0;i<CHILD;i++)//访问节点所有的孩子节点
{
if(ctnode->children[i]==NULL)//孩子节点访问完毕
break;
p=(BTreeNode *)malloc(sizeof(BTreeNode));//分配二叉树节点 p->data=ctnode->children[i]->data;
p->lchild=p->rchild=NULL;
if(i==0)
btnode->lchild=p;//长子,作为父节点的左孩子
else
LastSibling->rchild=p;//作为上一个兄弟节点的右孩子
LastSibling=p;
addQueueCTree(VisitedCTreeNodes,ctnode->children[i]);//树节点进队列
addQueueBTree(VisitedBTreeNodes,p);//二叉树节点进队列
}
}
}
插入结点:
CTreeNode *Insert(CTreeNode *Ctroot,char elem,char parent)//插入元素
{
CTreeNode *ctchild,*p;
int k;
fflush(stdin);
p=(CTreeNode *)malloc(sizeof(CTreeNode));
p->data=elem;
for(k=0;k<CHILD;k++)
p->children[k]=NULL;
ctchild=SearchCTree(Ctroot,parent);//查找指定数据的节点
if(ctchild==NULL)
return Ctroot;
else
{
for(k=0;k<CHILD;k++)//遍历到孩子为空,将新建节点放入
{
if(ctchild->children[k]==NULL)
{
ctchild->children[k]=p;
return Ctroot;
}
}
}
}
删除节点:
void Delete(CTreeNode *Ctroot,char elem)
{
CTreeNode *ctchild;
int i,j;
static int location=0,k=0;
if(Ctroot==NULL)
return;
if(Ctroot->data==elem&&location==0)
return;
location++;
for(i=0;i<CHILD;i++)
{
if(Ctroot->children[i]==NULL)
continue;
if((Ctroot->children[i]->data)==elem)
{
for(j=0;j<CHILD;j++)
if(((Ctroot->children[i])->children[j])!=NULL)
break;
if(j>=CHILD)
Ctroot->children[i]=NULL;
break;
}
else
{
ctchild=Ctroot->children[i];
if(ctchild==NULL)
break;//孩子节点遍历结束,退出
else
Delete(ctchild,elem);//递归查找
}
}
}
查找结点:
void Search(CTreeNode *Ctroot,char elem)//查找元素
{
CTreeNode *ctchild;
int i;
static int flag=0,k=0;
static int location=0;
if(Ctroot==NULL)
return;
location++;
if(Ctroot->data==elem)
return true;
if(location>=nodenum&&flag==0&&k==0)
return false;
for(i=0;i<CHILD;i++)//依次查找孩子节点
{
ctchild=Ctroot->children[i];
if(ctchild==NULL)
break;//孩子节点遍历结束,退出
else
Search(ctchild,elem);//递归查找
}
}
流程图:
开始
输入节点数及结点数据
查找父节点
N
结点找到
子结点放到父节点指针域
Y
结束
图2 创建树
开始
N
树存在
Y
存在孩子结点
N
Y
结点后移
输出结点数据
结束
图3 先序递归遍历
开始
存入根节点
N
存在孩子结点
Y
存入第一个孩子
存入节点,指针前移
N
已遍历完
Y
输出数组元素
结束
图4 先序非递归遍历
开始
N
树存在
N
结点有孩子
Y
Y
指向孩子
输出结点数据
返回上一个节点
N
树遍历完
Y
结束
图5 后序递归遍历
开始
根节点入栈
N
存在孩子
Y
输出栈顶结点
从指针域由大到小存入栈
N
栈为空
Y
栈顶结点出栈
结束
图6 后序非递归遍历
开始
Y
树为空
N
存入根节点
N
有孩子结点
Y
按顺序存入孩子
移向数组中下一个结点
输出数组中存入的所有结点
结束
图7 树的层次遍历
开始
输入待查找结点
先序遍历结点
N
找到数据域相等结点
Y
输出找到的位置
结束
图8 查找指定结点
开始
树为空
根结点入树队列,根二叉树队列
Y
队为空
N
结点出树队列,二叉树队列
N
有孩子结点
Y
遍历树结点孩子
创建对应二叉树结点,数据域存储对应孩子结点数据
N
Y
第一个孩子结点
放入前一个出队结点的左孩子
放入动态指针指向的当前结点的右孩子
动态指针指向当前结点
结束
图9 树转化为二叉树
开始
Y
树为空
N
输入插入结点及父结点
遍历树,查找父结点
N
遍历所有结点找到父结点
Y
将结点插入
结束
图10 插入指定结点
开始
输入待删除结点
遍历树,查找一个结点的孩子结点数据域符合删除条件
N
存在该结点
Y
N
该结点孩子结点不存在孩子
Y
该结点当前指针域置为空,释放空间
结束
图11 删除指定叶子结点
四、调试分析
1. 实际完成的情况
本程序完成了树的基本应用内容,包括树的创建,按先序、后序、层次遍历的递归非 递归遍历整棵树,可将树转换成二叉树,并用层次遍历输出转化后的二叉树,可在树中插入、 删除指定结点,可根据需要查找到节点在先序遍历中的位置。建树过程采用字符类型数据建立整棵树。
2、程序的性能分析
程序多次采用栈、队列的操作,使数据的读取存储较为快捷。多出进行判断,防止用户的误操作,达到使用的安全性。在树的各遍历过程中时间复杂度均为O(n2),递归过程中空间复杂度为O(1),非递归过程空间复杂度为O(n)(n为节点数)。插入、删除、查找的时间复杂度为O(n),空间复杂度为O(1)。综合考虑了代码的简洁性清楚,模块功能分明,空间的利用率高、运行效率高效、安全性能等多方面的优化,性能较为可观。
3.上机过程中出现的问题及其解决方案
上机时主要在树的创建过程中较为麻烦,查阅资料,树的存储形式有多种方法,最后决定采用孩子表示法,以此方便对孩子节点的遍历等操作。之后树与二叉树的转换较为麻烦,参考网上资料,采用队列形式,定义树形队列,二叉树形队列,将节点逐个入队、出队,加之判断操作,达到结点存入二叉树的顺序正确性。删除结点时,由于采用的孩子表示法,无法找到待删除元素时再返回父节点进行删除,此时采用找到父节点,如孩子结点符合删除条件,将父结点对应指针域置为空,释放结点。
4. 程序中可以改进的地方说明
程序在建树过程中只能采用子结点父结点同时输入,通过查询父结点建树,且输入时子结点与父结点之间不能有空格,使树的建立有了一定的限制。删除结点时不能删除非叶子结点,删除局限性大。
5. 程序中可以扩充的功能及设计实现假想
删除带孩子结点时,可以生成多棵树,成为森林,可采用先序遍历输出森林的生成结果。采用数组存储每棵子树,该数组中即为整个森林,然后每棵子树按先序遍历输出即可。还可添加对二叉树转化为树的过程,同树转化为二叉树,采用队列的形式,完成对数转化为二叉树的逆过程,此时即为二叉树转化为树。
五、 测试结果
1、树的创建:
图12 树的创建
2、树的递归先序遍历结果:
图13 树的递归先序遍历结果
3、树的非递归先序遍历结果:
图14 树的非递归先序遍历结果
4、树的递归后序遍历结果:
图15 树的递归后序遍历结果
5、树的非递归后序遍历结果:
图16 树的非递归后序遍历结果
6、树的层次遍历结果:
图17 树的层次遍历结果
7、树转化为二叉树的层次遍历结果:
图18 树转化为二叉树的层次遍历结果
8、插入叶子节点按先序遍历结果显示:
图19 插入叶子节点按先序遍历结果显示
9、插入不存在的父结点:
图20 插入不存在的父结点
10、删除叶子节点:
图21 删除叶子节点
11、删除非叶子节点:
图22 删除非叶子节点
12、删除节点不存在:
图23 删除节点不存在
13、查找指定结点(在树中存在该节点):
图24 查找指定结点(在树中存在该节点)
14、查找指定节点(在树中不存在该节点):
图25 查找指定节点(在树中不存在该节点)
六、 用户手册
1、主界面如下,按每个操作的前缀数字进行操作:
图26 主界面
2、首先输入1创建一棵树,输入树的结点与父节点之间不要含有空格:
图27 创建一棵树
3、输入2进行树的递归先序遍历:
图28 树的递归先序遍历
4、输入3进行树的非递归先序遍历:
图29 树的非递归先序遍历
5、输入4树的递归后序遍历:
图30 树的递归后序遍历
6、输入5树的非递归后序遍历:
图31 树的非递归后序遍历
7、输入6树的层次遍历:
图32 树的层次遍历
8、输入7树转化为二叉树的层次遍历结果:
图33 树转化为二叉树的层次遍历结果
9、输入8插入叶子节点,按2先序遍历结果显示(也可以选择其他遍历显示):
图34 插入叶子节点
10、输入9删除叶子节点(可输入遍历数字进行删除后的显示):
图35 删除叶子节点
11、输入10查找指定结点:
图36 查找指定结点
七、体会与自我评价
本次课题是对树的一些常用基本操作,开始对相应的模块功能的思路基本成形,但在相应代码实现过程中才发现有很多细节还需要处理,这也就是影响代码编写进度的关键地方。处理一个编程题关想到思路只是完成一半的任务,动手编写,调试程序,进行细节的处理才是完成整个程序的关键。这就要求在之后的问题处理上要能够着手去做想到的事,这才能完
展开阅读全文