识别广义表头尾演示数据结构课程设计报告.docx
《识别广义表头尾演示数据结构课程设计报告.docx》由会员分享,可在线阅读,更多相关《识别广义表头尾演示数据结构课程设计报告.docx(28页珍藏版)》请在冰豆网上搜索。
识别广义表头尾演示数据结构课程设计报告
1题目介绍与功能描述
1.1题目介绍
本课程设计主要完成对广义表的建立以及遍历(输出),并且对已建立的广义表实施操作,操作序列为一串由“t”、“h”以及“ ”组成的字符串。
“t”表示对广义表求表尾,“h”表示对广义表求表头,“ ”表示遍历当前整个广义表。
1.2具体要求
1.写一个程序,建立广义表的存储结构,演示在此存储结构上实现的广义表求头/求尾操作序列的结果。
2.广义表允许多行输入,其中可以任意输入空格符;
3.广义表存储结构自定;
4.对广义表的操作为一个由t和h组成的字符串;
1.3题目分析
设计一个广义表允许分多行输入,其中可以任意地输入空格符,原子是不限长的仅由字母或数字组成的串。
广义表采用如教科书中图5.8所示结点的存储结构,按表头和表尾的分解方法编写建立广义表存储结构的算法。
对已建立存储结构的广义表施行操作,操作序列为一个仅由“t”(取表尾)或“h”(取表头)组成的串,它可以是空串(此时印出整个广义表),自左至右施行各种操作,再以符号形式显示结果。
程序先进行广义表的输入,由程序进行广义表的建立,在打印出来,可检验广义表是否正确的建立。
然后进行求头尾的操作序列的输入。
由程序进行对广义表进行求头尾的操作。
程序中应该多次运用递归的思想,可以使程序显得更加的简洁高效。
2系统功能模块结构图
2.1系统功能结构图
图2.1系统功能结构图
2.2主要模块功能说明
2.2.1建立广义表
建立广义表由函数voidcreatlist(GList&Ls)完成。
当用户完成对广义表的输入后,由此函数完成对广义表的建立。
此函数运用递归的思想可以对广义表进行任意地建立。
能够输入空格字符,建立为空的广义表。
广义表的节点可以为原子,也可以为广义表,能够进行循环的建立直到输入完成,或程序结束。
2.2.2对表进行求头尾操作
对广义表进行求头尾操作是由多个函数共同完成,分别是voidGetTail(GList&LvoidGetHead(GList&Ls),voidGet_HT(GListLs),voidGL_Elem(GListp)voidprintf_GL(GListLs,int&i)。
当输入一串由t和h组成的操作序列后,由函数voidGet_HT(GListLs),对求头尾进行函数的调用。
如果是求头则调用GetHead(GList&Ls),如果是求尾则调用voidGetTail(GList&Ls),而函数voidGL_Elem(GListp)是负责对广义表的原子节点进行输出,函数voidprintf_GL(GListLs,int&i)负责对广义表进行整个的输出。
3数据结构设计及用法说明
3.1存储结构
广义表的存储结构采用的是链式存储结构,每个数据元素可用一个节点表示。
由于列表中的数据元素可能为原子或列表,由此需要两种结点:
原子结点和表结点。
前者用于表示原子,后者用于表示列表,一个表结点可由3个域组成:
标志域、指示表头的指针域和指示表尾的指针域;而原子结点只需要两个域:
标志域和值域。
广义表的存储结构定义形式为:
typedefstructGLNode//广义表节点
{
inttag;//表结点类型(tag=0表示原子结点,tag=1表示表结点)
union{
chardata;
struct{structGLNode*hr,*tr;};
};
}*GList;
3.2用法说明
对广义表的结构采用链表的形式进行定义,由于广义表可以是原子也可以是表,所以在结构体中还采用了共用体的数据结构。
让原子和节点可以共享存储单元。
tag代表节点的类型0为原子,1为表。
hr代表表的头指针,tr代表表的尾指针。
data为表的内容。
4主要函数
程序主体由几个关键函数组成:
creatlist(GList &Ls )、GL_Elem(GList p)、printf_GL(GList Ls,int &i)、GetHead(GList &Ls)、GetTail(GList &Ls)、Get_HT(GList Ls)以及main()函数中的三个部分,下面将对这些函数一一介绍。
4.1voidcreatlist(GList&Ls)
单独的creatlist()函数并不能创建完整的广义表,需要与main( )函数中的第一部分相结合才能共同完成广义表的创建。
creatlist()在main中的调用如下:
charc;
c=getchar();
if(c!
='(')
return-1,//广义表第一个字符必须是'(',否则终止函数
GListLs;
creatlist(Ls);
在整个程序中采用getchar( )函数从键盘缓冲区读取输入的广义表数据。
creatlist( )函数的完全代码如下:
voidcreatlist(GList&Ls)//建广义表
{charc;
c=getchar();//拾取一个合法字符
if(c=='')//空表的情况
{
Ls=NULL;
c=getchar();
if(c!
=')')return;//空表的下一个合法字符应该是')'
}
else
{//当前输入的广义表非空
GListp;
Ls=(GList)malloc(sizeof(GLNode));
Ls->tag=1;//表结点
if(c!
='(')//表头为单原子
{
Ls->hr=(GList)malloc(sizeof(GLNode));
p=Ls->hr;
p->tag=0;
p->data=c;//建立原子结点
}
else
{//表头为广义表
creatlist(Ls->hr);//对此广义表递归建立存储结构
}
c=getchar();
if(c==',')
creatlist(Ls->tr);//当前广义表未结束,等待输入下一个子表
elseif(c==')')
Ls->tr=NULL;//当前广义表输入结束
}
}
广义表是采用递归的方式定义的,因此creatlist()中广义表也是采用递归的方式建立的。
由于广义表的第一个字符“(”在main()函数中已被读取,因此creatlist()中从键盘数据缓冲区读取的第一个字符是所输入的广义表的第二个字符。
若当层广义表非空,则应建立表结点。
在建立原子结点时所用的判断方法为if(c!
=’(’) 是因为“,”之后的字符要么是“(”,要么是原子。
在经过代码:
if(c==',')creatlist(Ls->tr);,递归建立的广义表的读取的第一个字符只能是原子或者“(”,而不可能是“,”,因此建立原子结点的判断方法为if(c!
=’(’)。
当getchar()读取的字符为“(”时,表示要进入下一层广义表,故使用语句creatlist(Ls->hr);递归建立下一层广义表。
当getchar()读取的字符为“,”时,表示当层广义表未结束,还有子表需要建立,故用creatlist(Ls->tr);递归建立剩余的子表。
当getchar()读取的字符为“)”时,表示当层广义表已结束,则表结点的表尾指针应为空,即Ls->tr=NULL;
缺点:
函数creatlist()的纠错能力较差,要求输入的广义表应为广义表的标准正确形式,如(a,b,(a,1,(d,3,4))),如果输入的形式错误,如:
(s,d(,o))就会造成整个程序无法运行下去。
4.2voidGL_Elem(GList p)
GL_Elem()十分简单,只是一个将存储的原子输出的函数,代码如下:
voidGL_Elem(GListp)//输出原子
{
printf("%c",p->data);
}
4.3voidprintf_GL(GListLs,int&i)
这是输出广义表的函数,同creatlist()函数一样,printf_GL()并不能单独输出完整的已存储的广义表,它需要与main()函数中的第二部分相结合才能共同完成广义表的输出。
printf_GL()在main()中调用方式如下:
if(!
Ls)printf("()");//Ls指向空表
else{cout<<"(";//广义表第一个字符为“(”
inti=1;//已有左括号,故i=1
printf_GL(Ls,i);
}
主要思想:
在对广义表的输出过程中,如何将括号正确的输出是一个棘手的问题,故采用i作为括号计数器,在调用printf_GL( )之前,已输出一个“(”,故参数i=1。
printf_GL( )完整的代码如下:
voidprintf_GL(GListLs,int&i)//输出(遍历)Ls指针所指向的广义表
{//i为括号计数器
GListp=Ls->hr;if(!
p)//空表
{printf("()")
}
else
{
if(p->tag==1)//p指向表结点
{
printf("(");i++;
printf_GL(p,i);
}
elseif(p->tag==0)
{
GL_Elem(p);//若p指向原子结点则输出原子
}
}
GListk=Ls->tr;//表结点的尾指针
if(k)
{printf(",");//尾指针存在表示此表中还有元素
printf_GL(k,i);//遍历下一结点
}
elseif(!
k&&i)
{printf(")");i--;}}
主要思想:
解决这两个问题需要分辨出当前指针指向的是第几层子表。
对于问题一,假设Ls指向的是当层子表的一个表结点,p=Ls->hr; 显然p->tag ==0表示p指针所指结点与Ls所指结点在同一层,则只需输出原子即可;p->tag ==1表示p指针所指结点为Ls所指结点所在层次的广义表的子层,则输出“(”,并递归遍历p所指层次的广义表,即printf_GL(p,i)。
对于问题二,则需要分辨出表结点的尾指针的指向。
k=Ls->tr,若k存在,则说明此层子表中仍有元素,故输出“,”,若k不存在,说明此层子表完结,输出“)”。
4.4void GetHead(GList &Ls)
此函数为求表头函数,此函数的结构并不复杂,代码如下:
voidGetHead(GList&Ls)//输出广义表的表头
{printf("\n上表的表头为:
");
GListp=Ls;//保存头指针
p->tr=NULL;//隔离出Ls所指广义表的第一个元素
inti=0;
printf_GL(p,i);
}
主要思想:
输出广义表的表头,可以利用printf_GL( )函数遍历广义表的第一个元素,具体做法只需将第一个结点的尾指针设为空,则将这一结点从原广义表中独立开来,单独作为一个“广义表”进行遍历,输出的结果即为原广义表的表头。
由于广义表的表头为广义表的第一个元素,可以不是一个子表。
如广义表 (a,(1,2))的表头为a,因此将传递的参数i初始值置为0,这样在最上层的广义表表结点尾指针为空(!
k)时,由于i=0,故不需要输出“)”。
4.5void GetTail(GList &Ls)
求表尾函数,代码如下:
voidGetTail(GList&Ls)//输出广义表的表尾
{printf("\n上表的表尾为:
");
Ls=Ls->tr;
if(!
Ls)
printf("()");//Ls指向空表
else{printf("(");//表尾第一个字符为“(”
inti=1;//已有左括号,故i=1
printf_GL(Ls,i);
}
}
主要思想:
输出广义表的表尾,可将指向广义表的指针Ls,指向第一个结点的下一个结点,Ls=Ls->tr; 这样就能得到原广义表中除第一个元素外其他元素所组成的新表,即原广义表的表尾,然后再利用遍历广义表的方法对该表尾进行遍历即可。
4.6void Get_HT(GList Ls)
这是直接求表头,表尾的函数,由它接收输入。
此函数实现对广义表求头尾操作的函数,解释输入的操作序列命令(由“t”、“h”、“ ”组成的字符串),并执行。
代码如下:
voidGet_HT(GListLs)//执行求广义表表头(尾)的操作函数
{
charch=getchar();
while(ch){GListp=Ls;//保存表头指针
if(!
p){
printf("\n当前表为空表,不能执行求头尾操作\n");
break;
}
else
{
switch(ch){
case't':
GetTail(Ls);break;
case'h':
GetHead(Ls);break;
case'':
//空串时输出整个广义表
{
printf("\n上表为:
");
if(!
Ls)printf("()");//Ls指向空表
else
{
printf("(");//广义表第一个字符为“(”
inti=1;//已有左括号,故i=1
printf_GL(Ls,i);
}
}break;
default:
return;
}
}
ch=getchar();
}
}
主要思想:
Get_HT( )函数中同样是利用getchar( )函数读取输入的操作命令。
根据读取到的不同字符,完成不同的操作。
5主要函数流程图
5.1main函数
否
是
是
否
图5.1main函数流程图
主函数较简洁,操作都放在具体函数之上。
5.2creatlist函数
是否
是
是
否
输入为’)’
图5.2createlist函数流程图
建表函数主要运用递归的思想对广义表进行建立,循环的建立表头,表尾,直到输入的为反括号为止结束。
在建表的时候需要对表进行判断,判断输入的是否为空或字符。
5.3printf_GL函数
是
否
是
否
否
是
否
是
图5.3printf_GL函数流程图
输出函数是整个程序很重要的函数,取表头,取表尾的操作都依靠输出函数,表头,表尾函数与输出函数结合构成整个程序的核心。
6调试报告
6.1测试用例设计
1.输入:
((a,b),(c,d))操作:
thth
2.输入:
((),(e),(a,(b,c,d)))操作:
tth
3输入:
(((b,c),(),d),(((e)))操作:
htht
4输入:
((3,4,5,(())),2,d)操作:
htth
注意:
“()”和“( )”是不同的,前者括号内无空格符,后者有,本程只能识别后一种。
“( )”代表空表。
测试用例(3)中,操作序列“h tth”是由5个字符h、空格符、t、t、h组成,其中空格符表示空串。
6.2调试过程
遇到问题:
(1)当求表头时,输出时,在结果末尾会多出现一个右括号“)”,例如:
对于广义表(1,2,3)进行求表头操作,得到的结果为:
1)。
正确的结果应为:
1。
(2)第一次输入广义表的数据后,无法再输入求头尾的操作序列,使得程序不能完成应有的求头尾的功能
原因及解决方法:
(1)问题原因:
问题出在遍历函数printf_GL()中,在求头尾的操作中,由于将第一个表结点的尾指针置为空,将其从原广义表中独立出来,使得在printf_GL()中,执行了一次printf(")");
解决方法:
设置一个括号计数器i,当输出一个“(”时,i++,输出一个“)”时,i--。
并利用i的值判断是否应当输出“)”,即判断条件为if(!
k&&i),(见printf_GL()函数)求表头时,将传递参数i的值置为0,则在出现最后一个尾指针为空的情况时,i=0,故不能执行printf(“)”);语句。
(2)问题原因:
程序中使用了两次不同需求的getchar()函数,但是当用户键入回车之后,getchar才开始从stdin流中每次读入一个字符。
在第一次输入过程中,由于输入了广义表之后,键入了回车,导致接下来的getchar()函数均是从键盘缓冲区中读入数据,使得用户无法输入新数据。
解决方法:
通过fflush(stdin);来清空输入缓冲区,以确保不影响后面的数据读取。
将此语句放在main()函数第一部分的末尾,即在建立好广义表后清空缓冲区。
6.3运行结果
1.输入:
((a,b),(c,d))操作:
thth
2.输入:
((),(e),(a,(b,c,d)))操作:
tth
3输入:
(((b,c),(),d),(((e)))操作:
htht
4输入:
((3,4,5,(())),2,d)操作:
htth
课程设计总结:
在此次的课程设计之前,我对广义表的内容了解的比较模糊。
经过此次课程设计,对于广义表的存储结构以及求头尾操作,我有了较深的理解。
广义表的用途十分广泛,因此要求了广义表的灵活性。
广义表的存储结构是链式的,且类似于二叉树,广义表与二叉树的表头结点均有两个指针,但是二叉树的指针是自上而下的联系,而广义表中的尾指针是指向同层结点,因此有横向的联系,这点上与二叉树不同。
对广义表的求头尾操作中要注意:
广义表的表头是广义表中的第一个元素,而广义表的表尾是除第一个元素外其他元素组成的表。
即广义表的表尾一定是广义表,而表头是否为广义表,取决于第一个元素的类型。
由于时间问题,很多地方我都没有精雕细琢,导致本程序的容错能力不是很好,即健壮性较差,需要改进的地方有很多。
本程序尤其对输入的广义表要求很高,若输入的广义表出错,而用户没有发现,将会照成程序无法运行。
软件设计的健壮与否直接反应了分析设计和编码人员的水平,即所谓高手写的程序不容易死,因此我还有很多值得学习的内容,有很多地方需要提高。
参考文献
[1]谭浩强.C程序设计.北京.清华大学出版社,2002
[2]严蔚敏,吴伟民.数据结构(C语言版).北京.清华大学出版社,2002
附录源程序清单
#include
#include
typedefstructGLNode//广义表结点定义
{
inttag;//表结点类型(tag=0表示原子结点,tag=1表示表结点)
union
{
chardata;
struct{structGLNode*hr,*tr;};//h为表头指针t为表尾指针
};
}*GList;
voidcreatlist(GList&Ls)//建广义表
{charc;
c=getchar();//拾取一个合法字符
if(c=='')//空表的情况
{
Ls=NULL;
c=getchar();
if(c!
=')')return;//空表的下一个合法字符应该是')'
}
else
{//当前输入的广义表非空
GListp;
Ls=(GList)malloc(sizeof(GLNode));
Ls->tag=1;//表结点
if(c!
='(')//表头为单原子
{
Ls->hr=(GList)malloc(sizeof(GLNode));
p=Ls->hr;
p->tag=0;
p->data=c;//建立原子结点
}
else
{//表头为广义表
creatlist(Ls->hr);//对此广义表递归建立存储结构
}
c=getchar();
if(c==',')
creatlist(Ls->tr);//当前广义表未结束,等待输入下一个子表
elseif(c==')')
Ls->tr=NULL;//当前广义表输入结束
}
}
voidGL_Elem(GListp)//输出原子
{
printf("%c",p->data);
}
voidprintf_GL(GListLs,int&i)//输出(遍历)Ls指针所指向的广义表
{//i为括号计数器
GListp=Ls->hr;
if(!
p)//空表
{printf("()");}
else{if(p->tag==1)//p指向表结点
{printf("(");
i++;
printf_GL(p,i);
}
elseif(p->tag==0)
{GL_Elem(p);//若p指向原子结点则输出原子
}
}
GListk=Ls->tr;//表结点的尾指针
if(k)
{printf(",");//尾指针存在表示此表中还有元素
printf_GL(k,i);//遍历下一结点
}elseif(!
k&&i)
{printf(")");i--;
}
}
voidGetHead(GList&Ls)//输出广义表的表头
{printf("\n上表的表头为:
");
GListp=Ls;//保存头指针
p->tr=NULL;//隔离出Ls所指广义表的第一个元素
inti=0;
printf_GL(Ls,i);
Ls=Ls->hr;
}
voidGetTail(GList&Ls)//输出广义表的表尾
{printf("\n上