算术表达式与二叉树课程设计.docx
《算术表达式与二叉树课程设计.docx》由会员分享,可在线阅读,更多相关《算术表达式与二叉树课程设计.docx(24页珍藏版)》请在冰豆网上搜索。
算术表达式与二叉树课程设计
山西大学
课程设计任务书
设计题目算术表达式与二叉树
所属课程:
数据结构
系别软件学院
专业软件工程
班级软工1408班
姓名霍志斌
指导教师李雪梅
设计任务下达日期2015年12月15日
设计时间2016年1月4日至2016年1月8日
目录:
一、需求分析
二、概要设计
1、数据类型的声明:
2、表达式的抽象数据类型定义
3、整体设计
三、详细设计
1、二叉树的存储类型
2、顺序栈的存储类型
3、表达式的基本操作
4、主程序和其他伪码算法
5、函数的调用关系
四、设计和调试分析
五、测试
六、课程设计的心得和心得以及问题
一、需求分析【课程设计要求】
【问题的描述】
一个表达式和一棵二叉树之间,存在着自然的对应关系。
写一个程序,实现基于二叉树表示的算术表达式Expression的操作。
【基本要求】
假设算术表达式Expression内可以含有变量(a-z),常量(0-9)和二元运算符(+,-,*,/,^(乘幂))。
实现以下操作:
(1)ReadExpr(E)――以字符序列的形式输入语法正确的前缀表达式并构造表达式E。
(2)WriteExpr(E)――用带括号的中缀表达式输出表达式E。
(3)Assign(V,c)――实现对变量V的赋值(V=c),变量的初值为0。
(4)Value(E)――对算术表达式E求值。
(5)CompoundExpr(p,E1,E2)――构造一个新的复合表达式(E1)p(E2)。
【测试数据】
1)分别输入0;a;-91;+a*bc;+*5x2*8x;+++*3^*2^x2x6并输出。
2)每当输入一个表达式后,对其中的变量赋值,然后对表达式求值。
二、概要设计
1、数据类型的声明:
在这个课程设计中,采用了链表二叉树的存储结构,以及两个顺序栈的辅助存储结构
/*头文件以及存储结构*/
#include
#include
#include
#include
#defineTRUE1
#defineFALSE0
#defineOK1
#defineERROR0
#defineOVERFLOW0
typedefintStatus;
2、表达式的抽象数据类型定义
ADTExpression{
数据对象D:
D是具有数值的常量C和没有数值的变量V;
数据关系:
R={<(V或者C)P(V或者C)>|V,C∈D,<(V或者C)P(V或者C)>表示由运算符P结合起来的表达式E}
基本操作:
StatusInput_Expr(&string,flag)
操作结果:
以字符序列的形式输入语法正确的前缀表达式,保存到字符串string;参数flag表示输出的提示信息是什么,输入成功返回OK,否则,返回ERROR。
voidjudge_value(&E,&string,i)
初始条件:
树E存在,表达式的前缀字符串string存在;
操作结果:
判断字符string[i],如果是'0'-'9'常量之间,二叉树结点E存为整型;否则,存为字符型。
StatusReadExpr(&E,&exprstring)
初始条件:
表达式的前缀形式字符串exprstring存在;
操作结果:
以正确的前缀表示式exprstring并构造表达式E,构造成功,返回OK,否则返回ERROR。
StatusPri_Compare(c1,c2)
初始条件:
c1和c2是字符;
操作结果:
如果两个字符是运算符,比较两个运算符的优先级,c1比c2优先,返回OK,否则返回ERROR。
voidWriteExpr(&E)
初始条件:
表达式E存在;
操作条件:
用带括弧的中缀表达式输入表达式E。
voidAssign(&E,V,c,&flag)
初始条件:
表达式E存在,flag为标志是否有赋值过;
操作结果:
实现对表达式E中的所有变量V的赋值(V=c)。
longOperate(opr1,opr,opr2)
初始条件:
操作数opr1和操作数opr2以及操作运算符opr;
操作结果:
运算符运算求值,参数opr1,opr2为常量,opr为运算符,根据不同的运算符,实现不同的运算,返回运算结果。
StatusCheck(E)
初始条件:
表达式E存在;
操作结果:
检查表达式E是否还存在没有赋值的变量,以便求算数表达式E的值。
longValue(E)
初始条件:
表达式E存在;
操作结果:
对算术表达式求值,返回求到的结果。
voidCompoundExpr(P,&E1,E2)
初始条件:
表达式E1和E2存在;
操作条件:
构造一个新的复合表达式(E1)P(E2)。
StatusRead_Inorder_Expr(&string,&pre_expr)
操作结果:
以表达式的原书写形式输入,表达式的原书写形式字符串string变为字符串pre_expr,后调用reversal_string()函数反转得到前缀表达式pre_expr。
得到正确的前缀表达式返回OK,否则,返回ERROR。
voidMergeConst(&E)
操作结果:
常数合并操作,合并表达式E中所有常数运算。
}ADTExpression
3、整体设计
在这个课程设计中,有两个源代码文件:
expression.h和expression.c。
在expression.h文件中,包含了各个存储结构的声明和辅助存储结构的两个栈的基本操作;在expression.c文件中,是实现课程设计要求的各个函数。
《一》expression.h文件的整体结构
1、各个存储结构的声明;
2、两个除了栈名和栈存储的元素不一样的顺序栈的基本操作。
其基本操作如下:
对于栈SqStack:
StatusInitStack(SqStack*S)/*构造一个空栈S*/
StatusStackEmpty(SqStackS)/*若栈S为空栈,则返回TRUE,否则返回FALSE*/
StatusPush(SqStack*S,SElemTypee)/*插入元素e为新的栈顶元素*/
StatusPop(SqStack*S,SElemType*e)/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
StatusGetTop(SqStackS,SElemType*e)/*若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR*/
对于栈SqStack1:
StatusInitStack1(SqStack1*S)/*构造一个空栈S*/
StatusStackEmpty1(SqStack1S)/*若栈S为空栈,则返回TRUE,否则返回FALSE*/
StatusPush1(SqStack1*S,SElemType1e)/*插入元素e为新的栈顶元素*/
StatusPop1(SqStack1*S,SElemType1*e)/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
StatusGetTop1(SqStack1S,SElemType1*e)/*若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR*/
顺序栈的基本操作的算法见程序清单。
《二》expression.c文件的整体结构
1、主程序模块的整体流程
可以从主菜单函数可以明了的了解的程序的整体流程,主菜单函数menu()如下:
charmenu()
{
charchoice;
printf("\n****************************************");
printf("\n1>>>输入正确的前缀表达式");
printf("\n2>>>带括弧的中缀表示式输出");
printf("\n3>>>对变量进行赋值");
printf("\n4>>>对算数表达式求值");
printf("\n5>>>构造一个新的复合表达式");
printf("\n6>>>以表达式的原书写形式输入");
printf("\n7>>>合并表达式中所有常数运算");
printf("\n0>>>退出");
printf("\n****************************************");
printf("\n请输入你的选择>>>>>");
choice=getche();
returnchoice;
}
在主函数中,采用多分支程序设计语句switch()使程序产生不同的流向,从而达到实现课程设计的各个要求。
voidmain()
{
while
(1)
{
清屏;
switch(主菜单)
{
根据不同的选择,调用不同的操作函数,完成各个操作;
}
}
}
2、本程序有四个模块,主程序模块,二叉树模块,两个顺序栈模块。
四者的调用关系如下:
主程序模块中的对于表达式的存储结构调用了二叉树模块,而在构造表达式的二叉树模块中又调用了顺序栈SqStack模块,主程序中在将原表达式形式输入表达式转换为前缀表达式操作中调用了顺序栈SqStack1模块。
三、详细设计
1、二叉树的存储类型
/*二叉树结点类型*/
typedefenum{INT,CHAR}ElemTag;/*INT为整型数据num,CHAR为字符型数据c*/
typedefstructTElemType
{
ElemTagtag;/*{INT,CHAR}指示是整型还是字符型*/
union
{
intnum;/*tag=INT时,为整型*/
charc;/*tag=CHAR时,为字符型*/
};
}TElemType;
/*二叉树的二叉链表存储表示*/
typedefstructBiTNode
{
TElemTypedata;
structBiTNode*lchild,*rchild;/*左右孩子指针*/
}BiTNode,*BiTree;
二叉树的基本操作已经在构造表达式和表达式中的基本操作中根据不同的功能和实际情况修改了,详细见各个函数操作的算法设计。
2、顺序栈的存储类型
/*栈的顺序存储表示*/
#defineSTACK_INIT_SIZE10/*存储空间初始分配量*/
#defineSTACKINCREMENT2/*存储空间分配增量*/
/*两个顺序栈*/
typedefstructSqStack
{
SElemType*base;/*在栈构造之前和销毁之后,base的值为NULL*/
SElemType*top;/*栈顶指针*/
intstacksize;/*当前已分配的存储空间,以元素为单位*/
}SqStack;/*顺序栈SqStack*/
typedefstructSqStack1
{
SElemType1*base;/*在栈构造之前和销毁之后,base的值为NULL*/
SElemType1*top;/*栈顶指针*/
intstacksize;/*当前已分配的存储空间,以元素为单位*/
}SqStack1;/*顺序栈SqStack1*/
相关的基本操作见上面的“expression.h文件的整体结构”的说明,详细的算法设计见附录的程序清单。
3、表达式的基本操作
StatusInput_Expr(char*string,intflag);
/*以字符序列的形式输入语法正确的前缀表达式,保存到字符串string*/
/*参数flag=0表示输出的提示信息是"请输入正确的前缀表示式:
"*/
/*flag=1表示输出的提示信息为"请以表达式的原书写形式输入正确表示式:
"*/
voidjudge_value(BiTree*E,char*string,inti);
/*判断字符string[i],如果是'0'-'9'常量之间,二叉树结点存为整型;否则,存为字符型*/
StatusReadExpr(BiTree*E,char*exprstring);
/*以正确的前缀表示式并构造表达式E*/
StatusPri_Compare(charc1,charc2);
/*如果两个字符是运算符,比较两个运算符的优先级,c1比c2优先,返回OK,否则返回ERROR*/
voidWriteExpr(BiTreeE);
/*用带括弧的中缀表达式输入表达式*/
voidAssign(BiTree*E,charV,intc,int*flag);
/*实现对表达式中的所有变量V的赋值(V=c),参数flag为表示是否赋值过的标志*/
longOperate(intopr1,charopr,intopr2);
/*运算符运算求值,参数opr1,opr2为常量,opr为运算符,根据不同的运算符,实现不同的运算,返回运算结果*/
StatusCheck(BiTreeE);
/*检查表达式是否还存在没有赋值的变量,以便求算数表达式的值*/
longValue(BiTreeE);
/*对算术表达式求值*/
voidCompoundExpr(charP,BiTree*E1,BiTreeE2);
/*构造一个新的复合表达式*/
StatusRead_Inorder_Expr(char*string,char*pre_expr);
/*以表达式的原书写形式输入,表达式的原书写形式字符串string变为字符串pre_expr,后调用reversal_string()函数反转得到前缀表达式pre_expr*/
voidMergeConst(BiTree*E);
/*常数合并操作函数,合并表达式E中所有常数运算*/
下面列出部分基本操作的伪码算法,未列出的请见程序清单。
其中部分基本操作的伪码算法如下:
StatusReadExpr(BiTree*E,char*exprstring)
{/*以正确的前缀表示式并构造表达式E*/
申请根结点空间(*E)=(BiTree)malloc(sizeof(BiTNode));并且左右孩子指针置空;
表达式只有一个字符,二叉树只有根结点;
否则
{
judge_value(E,exprstring,0);将exprstring[0]存入二叉树的结点中
InitStack(&S);/*初始化栈*/
Push(&S,q);Push(&S,q);
入栈,根结点入栈两次是为判断先序输入的表达式是不是正确的表达式
for(i=1;iStackEmpty(S);i++)
{
申请根结点空间p=(BiTree)malloc(sizeof(BiTNode));
并且左右孩子指针置空;
if(exprstring[i]为运算符)
运算符入栈,左孩子不空,向左孩子走,否则,如果右孩子不空,向右孩子走;
else
不是运算符,运算符出栈;
}
根据StackEmpty(S)&&i>=len判断输入的表达式是正确的;
正确返回OK,错误返回ERROR;
}
}voidWriteExpr(BiTreeE)
{/*用带括弧的中缀表达式输入表达式*/
if(E)/*树不为空*/
{
先递归左子树;WriteExpr(E->lchild);
其中要考虑何时带括弧输出:
if(Pri_Compare(E->data.c,E->lchild->data.c))
E->data.c比E->lchild->data.c优先,带括弧输出左子树;
访问输出根结点的值;
后递归右子树;WriteExpr(E->lchild);
其中要考虑何时带括弧输出:
if(Pri_Compare(E->data.c,E->lchild->data.c))
E->data.c比E->lchild->data.c优先,带括弧输出右子树;
}
}oidAssign(BiTree*E,charV,intc,int*flag)
{/*实现对表达式中的所有变量V的赋值(V=c),参数flag为表示是否赋值过的标志*/
if(*E)/*树不空*/
{
if((*E)->data.tag==CHAR&&(*E)->data.c==V)
{如果找到要赋值的变量,赋值;*flag=1;}
Assign(&((*E)->lchild),V,c,flag);/*递归左子树*/
Assign(&((*E)->rchild),V,c,flag);/*递归左子树*/
}
longOperate(intopr1,charopr,intopr2)
{/*运算符运算求值,参数opr1,opr2为常量,opr为运算符,根据不同的运算符,实现不同的运算,返回运算结果*/
switch(opr)
{
根据不同的运算符,进入不同分支求出result;
后返回result;
}
StatusCheck(BiTreeE)
{/*检查表达式是否还存在没有赋值的变量,以便求算数表达式的值*/
if(E&&E->data.tag==CHAR)/*树不为空*/
{
如果找到没有赋值的变量,返回ERROR;
if(Check(E->lchild))/*递归左子树*/
Check(E->rchild);/*递归右子树*/
}
}
longValue(BiTreeE);
{/*对算术表达式求值*/
if(E)/*树不为空*/
{
如果是叶子结点,返回叶子的结点的值;
returnOperate(Value(E->lchild),E->data.c,Value(E->rchild));后根遍历的次序对表达式求值;
}
}voidCompoundExpr(charP,BiTree*E1,BiTreeE2);
{/*构造一个新的复合表达式*/
E=(BiTree)malloc(sizeof(BiTNode));/*申请一个结点存放运算符P*/
E->lchild=(*E1);/*结点的左孩子为E1*/
E->rchild=E2;/*结点的右孩子为E2*/
(*E1)=E;/*(*E1)为根结点*/
}
StatusRead_Inorder_Expr(char*string,char*pre_expr);
{/*以表达式的原书写形式输入,表达式的原书写形式字符串string变为字符串pre_expr,后调用reversal_string()函数反转得到前缀表达式pre_expr*/
InitStack1(&S);/*初始栈*/
c=string[len-1];从字符串的最后一个字符开始向前扫描,len=strlen(string);
while(!
StackEmpty1(S)&&i>=0)/*栈不为空且i大于等于0*/
{
if(c=='(')字符为'(',Pop1(&S,&c);
while(c!
=')')假如c不为')',出栈;
elseif(c==')')字符为')',入栈,Push1(&S,c);
elseif(c>='0'&&c<='9')
字符为'0'-'9'之间,循环扫描string前一个字符,后确定常量的大小;
elseif((c>='a'&&c<='z')||(c>='A'&&c<='Z'))
字符为'a'-'z'或'A'-'Z'之间的变量;
elseif(c=='*'||c=='/')字符为运算符'*'或'/'
比较优先级,后确定是入栈还是出栈;
elseif(c=='+'||c=='-')字符为运算符'+'或'-'
比较优先级,后确定是入栈还是出栈;
elseif(c=='^')字符为运算符'^'
优先级最大,不用比较,直接入栈;
else{printf("\n输入的表达式有误!
");returnERROR;}
其他字符,错误,返回ERROR。
下一个字符,继续循环。
}
*pre_expr='\0';/*字符串结束符*/
判断构造是否成功,成功返回OK;否则返回ERROR;
}
voidMergeConst(BiTree*E);
{/*常数合并操作函数,合并表达式E中所有常数运算*/
if((*E)->lchild&&(*E)->rchild)左右孩子不为空
{
if((*E)->lchild->data.tag==INT&&(*E)->rchild->data.tag==INT)
假如左右孩子为常量,合并,并且删除常量的结点;
else
{
MergeConst(&((*E)->lchild));/*递归左孩子*/
MergeConst(&((*E)->rchild));/*递归右孩子*/
}
}4、主程序和其他伪码算法
voidmain()
{
while
(1)
{
switch(menu())
{
case'1':
/*输入正确的前缀表达式*/
if(Input_Expr(Expr_String,0))输入正确的前缀表达式
if(ReadExpr(&E,Expr_String))构造表达式
{flag=1;printf("\n表达式构造成功!
");}
case'2':
/*带括弧的中缀表示式输出*/
if(flag==1)WriteExpr(E);
elseprintf("\n表达式未构造成功!
请构造成功的表达式!
");
case'3':
/*对变量进行赋值*/
if(flag==1)
{
flushall();/*清理缓冲区*/
V=getchar();
scanf(&c);
Assign(&E,V,c,&Assign_flag);
}
elseprintf("\n表达式未构造成功!
请构造成功的表达式!
");
case'4':
/*对算数表达式求值*/
if(flag==1)
{
if(Check(E))
{result=Value(E);WriteExpr(E);printf(result);}
}
elseprintf("\n表达式未构造成功!
请构造成功的表达式!
");
case'5':
/*构造一个新的复合表达式*/
if(flag==1)
{
flushall();/*清理缓冲区*/
if(Input_Exp