题目设计一个程序实现基于二叉树表示的算术表达式的操作.docx
《题目设计一个程序实现基于二叉树表示的算术表达式的操作.docx》由会员分享,可在线阅读,更多相关《题目设计一个程序实现基于二叉树表示的算术表达式的操作.docx(34页珍藏版)》请在冰豆网上搜索。
题目设计一个程序实现基于二叉树表示的算术表达式的操作
题目:
设计一个程序实现基于二叉树表示的算术表达式的操作。
一、需求分析
1、以二叉树为基本模型,构建了表达式二叉树。
算术表达式的合法输入数据包括变量(,a~z)、常量(0-9)和二元运算符(+,-,*,/,^(乘幂)),一元运算符(sin,cos,tan)。
演示程序以人机对话的方式执行,即在计算机上显示提示信息后,由用户在键盘上输入对应的数据或命令,程序将执行相应的操作并显示下一步信息。
表达式的输出主要是用带括号的中缀表示式输出调用函数InorderExp(ExpTreeE,Status(*Visit)(ExpTreee));
2、程序的目的实现算术表达式在计算机里的树形存储,实现基本的运算(+,-,*,/,^(乘幂))sin,cos,tan),求偏导,常数合并。
3、测试数据(附后)。
提供两种方式的测试:
一种是自动测试,即程序调用test文件夹data.txt文件里的测试数据,另一种方式是手动测试,即按程序提示一步一步输入测试。
除了满足要求的0;a;-91;+a*bc;+*5^x2*8x;+++*3^x3*2^x2x6,还有几十组数据测试。
每当输入一个表达式后,程序提示用户赋值,再对表达式求值。
为了方便用户,我在程序中用数组保存着一些测试数据,以供测试用。
二、概要设计
1.以字符串保存输入的字符序列。
2.提示用户赋值的同时将数据取出建立二叉树。
3.用后根遍历的次序用递归函数对表达式求值,求值时进行相应的转化,将运算数的字符形式转换成整数形式。
4.用中缀表达式输出表达式时,适当添加括号,以正确反映运算的优先次序。
5.抽象数据类型的定义:
1)、存放表达式的结构类型,是以二叉树为基本原型。
typedefenum{OPER,VAR,ORD}ElemTag;//运算符,变量,常量
typedefstructExpNode
{
ElemTagtag;//标记
union{
charexpr[4];//存放运算符名
struct{
charvar;//存放变量名
intval;//存放变量的值,初始值为0
}vary;//存放变量
intordina;//存放常量值
};
structExpNode*lchild,*rchild;/*左右孩子指针*/
}*ExpTree;/*二叉树的二叉链表存储表示*/
基本操作:
intRandom(intnMin,intnMax);
//返回nMin到nMax之间的随机数
voidFindVary(char*c,char*e);
//找出表达式中的变量
StatusArrayCreateExp(ExpTree&E,char*ch,int&i);
//从ch数组中读取字符串,构造表达式
voidCreateExp(ExpTree&E,char*ch,int&i);
//StatusInputCreateExp(ExpTree&E);
//从键盘先序输入来构造表达式树T
StatusVisit(ExpTreee);
//输出e的内容
voidInorderExp(ExpTreeE,Status(*Visit)(ExpTreee));
//输出中序表达式用带括号的中缀表示式输出
StatusAssign(ExpTreeE,charv,floatc);
//对表达式内的所有v,赋值c
floatValue(ExpTreeE);
//计算表达式的值
ExpTreeCompound(charp,ExpTreee1,ExpTreee2);
//5.构造一个新的复合表达式(E1)P(E2)
StatusDiff(ExpTree&E,charV);
//求表达式E对变量V的导数
voidMergeConst(ExpTreeE);
//合并表达式种所有常数运算
StatusPreOrderTraverse(ExpTreeE,Status(*Visit)(ExpTreee));
//波兰式输出
StatusPostOrderTraverse(ExpTreeE,Status(*Visit)(ExpTreee));
//逆波兰式输出
2)、队列
typedefcharQElemType;
typedefstructQNode
{
QElemTypedata;
structQNode*next;
}QNode,*QuePtr;
typedefstruct
{
QuePtrfront;
QuePtrrear;
}Queue;
基本操作:
StatusInitQueue(Queue&Q);
//构造一个空队列
StatusDestroyQueue(Queue&Q);
//销毁队列
StatusQueueEmpty(QueueQ);
//判空
StatusEnQueue(Queue&Q,QElemTypee);
//插入元素e为Q的新的队尾元素
StatusDeQueue(Queue&Q,QElemType&e);
//删除队头元素,用e返回其值,并返回OK,否则返回ERROR;
3)、栈
typedefstruct
{
SElemType*base;
SElemType*top;
intstacksize;
}SqStack;
基本操作:
StatusInitStack(SqStack&S);
StatusStackEmpty(SqStackS);
StatusPush(SqStack&S,SElemTypee);
StatusPop(SqStack&S,SElemType&e);
SElemTypeTop(SqStackS);
6、主程序:
voidmain()
{
while
(1)
{接受命令
处理命令;
Switch()
{
case:
1.以数组形式输入前缀表示式函数构造表达式.
case:
2.以字符序列输入前缀表示式函数构造表达式.
case:
3.实现对变量V的赋值(V=c).
case:
4.对算术表达式E求值.\n");
case:
5.构造一个新的复合表示式(E1)P(E2).
case:
6.求偏导函数Diff(E,V)
case:
7.对三角函数的测试.
case:
8.常数合并.
case:
0.结束
}
}
三、详细设计
1、存放表达式的结构类型,是以二叉树为基本原型。
typedefenum{OPER,VAR,ORD}ElemTag;//运算符,变量,常量
typedefstructExpNode
{
ElemTagtag;//标记
union{
charexpr[4];//存放运算符名
struct{
charvar;//存放变量名
intval;//存放变量的值,初始值为0
}vary;//存放变量
intordina;//存放常量值
};
structExpNode*lchild,*rchild;/*左右孩子指针*/
}*ExpTree;/*二叉树的二叉链表存储表示*/
我原来是直接用二叉树的存储结构的,后来发现受到这个结构类型的很大限制,受到广义表存储结构的启发,就自己设计了这样一个存储类型。
下面分析这个存储结构:
(1)、用ElemTagtag;来标记是运算符,变量,常量。
用枚举类型定义typedefenum{OPER,VAR,ORD}ElemTag,可以区分是运算符,变量,常量。
(2)、用字符串charexpr[4];来存放运算符名,我先预定存放三个字符的运算符名(最后一个char用来存放’\0’),这样运算符不仅可以是'+','-','*','/','^',还可以是’sin’,’cos’,’tan’。
如果觉得三个字符不够,可以扩展。
(3)、struct{charvar;//存放变量名floatval;//存放变量的值,初始为0}vary;//存放变量。
这样在变量赋值后,还可以保存着变量名。
可以用作如公式一样,重复赋值使用。
(4)、使用intordina;来存放常量值。
这样赋值时,就扩大了赋值范围,可以是一个整形的范围,大大扩大了本程序的使用范围。
但需要在赋值时使用,在输入时,还是得用0-9,这也是本程序的缺陷,有待改进。
基本操作:
intRandom(intnMin,intnMax);
//返回nMin到nMax之间的随机数
voidFindVary(char*c,char*e);
//找出表达式中的变量
StatusArrayCreateExp(ExpTree&E,char*ch,int&i);
//从ch数组中读取字符串,构造表达式
voidCreateExp(ExpTree&E,char*ch,int&i);
//StatusInputCreateExp(ExpTree&E);
//从键盘先序输入来构造表达式树T
StatusVisit(ExpTreee);
//输出e的内容
voidInorderExp(ExpTreeE,Status(*Visit)(ExpTreee));
//输出中序表达式用带括号的中缀表示式输出
StatusAssign(ExpTreeE,charv,floatc);
//对表达式内的所有v,赋值c
floatValue(ExpTreeE);
//计算表达式的值
ExpTreeCompound(charp,ExpTreee1,ExpTreee2);
//5.构造一个新的复合表达式(E1)P(E2)
StatusDiff(ExpTree&E,charV);
//求表达式E对变量V的导数
voidMergeConst(ExpTreeE);
//合并表达式种所有常数运算
StatusPreOrderTraverse(ExpTreeE,Status(*Visit)(ExpTreee));
//波兰式输出
StatusPostOrderTraverse(ExpTreeE,Status(*Visit)(ExpTreee));
//逆波兰式输出
2、队列。
typedefcharQElemType;
typedefstructQNode
{
QElemTypedata;
structQNode*next;
}QNode,*QuePtr;
typedefstruct
{
QuePtrfront;
QuePtrrear;
}Queue;
基本操作:
StatusInitQueue(Queue&Q);
//构造一个空队列
StatusDestroyQueue(Queue&Q);
//销毁队列
StatusQueueEmpty(QueueQ);
//判空
StatusEnQueue(Queue&Q,QElemTypee);
//插入元素e为Q的新的队尾元素
StatusDeQueue(Queue&Q,QElemType&e);
//删除队头元素,用e返回其值,并返回OK,否则返回ERROR;
3、栈
typedefstruct
{
SElemType*base;
SElemType*top;
intstacksize;
}SqStack;
基本操作:
StatusInitStack(SqStack&S);
StatusStackEmpty(SqStackS);
StatusPush(SqStack&S,SElemTypee);
StatusPop(SqStack&S,SElemType&e);
SElemTypeTop(SqStackS);
4、主函数和其他主要函数
1)、访问函数(输出函数)
StatusVisit(ExpTreee)
//输出e的内容
{
intm,n[5];
if(e->tag==OPER)//运算符
printf("%s",e->expr);
elseif(e->tag==VAR)//变量
{
if(!
e->vary.val)//变量的值是0
printf("%c",e->vary.var);//输出变量名
else
printf("%0.4f",e->vary.val);//输出变量的值
}
else//常量
{
if(e->ordina>=0)//正数
printf("%d",e->ordina);
else
printf("(%d)",e->ordina);//负数,输出时加括号
}
returnOK;
}
2)、构造表达式
StatusArrayCreateExp(ExpTree&E,char*ch,int&i)
//从ch数组中读取字符串,构造表达式
{
if(ch[i])
{
if(!
(E=(ExpTree)malloc(sizeof(ExpNode))))
returnERROR;
if(ch[i]=='s'&&ch[i+1]=='i'&&ch[i+2]=='n'||//sin
ch[i]=='S'&&ch[i+1]=='I'&&ch[i+2]=='N'||
ch[i]=='c'&&ch[i+1]=='o'&&ch[i+2]=='s'||//cos
ch[i]=='C'&&ch[i+1]=='O'&&ch[i+2]=='S'||
ch[i]=='t'&&ch[i+1]=='a'&&ch[i+2]=='n'||//tan
ch[i]=='T'&&ch[i+1]=='A'&&ch[i+2]=='N')
{
E->tag=OPER;
E->expr[0]=ch[i];E->expr[1]=ch[++i];E->expr[2]=ch[++i];
E->expr[3]='\0';
ArrayCreateExp(E->rchild,ch,++i);//只建右子树
E->lchild=NULL;//左子树为空
returnOK;
}
elseif(ch[i]>='0'&&ch[i]<='9')//数字
{
E->tag=ORD;
E->ordina=ch[i]-'0';
}
elseif(ch[i]>='a'&&ch[i]<='z')//变量
{
E->tag=VAR;
E->vary.var=ch[i];
E->vary.val=0;
}
elseif(ch[i]=='+'||ch[i]=='-'||ch[i]=='*'||ch[i]=='/'||ch[i]=='^')//运算符
{
E->tag=OPER;
E->expr[0]=ch[i];
E->expr[1]='\0';
}
if(!
E->tag)//E是运算符
{
ArrayCreateExp(E->lchild,ch,++i);
ArrayCreateExp(E->rchild,ch,++i);
}
else
{
E->lchild=NULL;
E->rchild=NULL;
}
}
returnOK;
}
3)、带括号的中缀表示式输出
voidInorderExp(ExpTreeE,Status(*Visit)(ExpTreee))
//输出中序表达式用带括号的中缀表示式输出
{
intbracket;
if(E)
{
if(E->lchild)
{
bracket=precede(E,E->lchild);//比较双亲与左孩子运算符优先级
if(bracket>0)//左孩子优先级低
printf("(");
InorderExp(E->lchild,Visit);
if(bracket>0)
printf(")");
}
Visit(E);
if(E->rchild)
{
bracket=precede(E,E->rchild);//比较双亲与右孩子运算符优先级
if(bracket>=0)//右孩子优先级低
printf("(");
InorderExp(E->rchild,Visit);
if(bracket>=0)
printf(")");
}
}
}
4)、计算表达式的值
floatValue(ExpTreeE)
//计算表达式的值
{
floatlv,rv,value=0;
if(E)
{
if(E->tag==VAR)//是变量
return(E->vary.val);
if(E->tag==ORD)
return(E->ordina);
if(E->lchild)lv=Value(E->lchild);
rv=Value(E->rchild);
switch(E->expr[0])
{
case'+':
value=lv+rv;break;
case'-':
value=lv-rv;break;
case'*':
value=lv*rv;break;
case'/':
if(rv)value=lv/rv;
elseexit(0);
break;
case'^':
value=power(lv,rv);break;
case'S':
case's':
value=sin(rv);break;//sin
case'T':
case't':
value=tan(rv);break;//tan
case'C':
case'c':
if(E->expr[2]=='S'||E->expr[2]=='s')//cos
{value=cos(rv);
break;
}
}
}
return(value);
}
5)、合并常数
voidMergeConst(ExpTreeE)
//合并表达式中所有常数运算
{
if(!
E->lchild&&!
E->rchild)return;//叶子
if(E->tag==OPER&&E->lchild&&E->rchild&&E->lchild->tag==ORD&&E->rchild->tag==ORD)
{
E->tag=ORD;
switch(E->expr[0])
{
case'*':
E->ordina=E->lchild->ordina*E->rchild->ordina;break;
case'/':
E->ordina=E->lchild->ordina/E->rchild->ordina;break;
case'^':
E->ordina=power(E->lchild->ordina,E->rchild->ordina);break;
case'+':
E->ordina=E->lchild->ordina+E->rchild->ordina;break;
case'-':
E->ordina=E->lchild->ordina-E->rchild->ordina;break;
}
free(E->lchild);
free(E->rchild);
E->lchild=NULL;
E->rchild=NULL;
}
else
{
if(E->lchild)
MergeConst(E->lchild);
if(E->rchild)
MergeConst(E->rchild);
}
}
5、构造的表达式如图
算术表达式前缀表示:
*+ab-cd
中缀带括号表示:
(a+b)*(c-d)
四、调试分析
1、调试过程中遇到了许多问题,下面举几个例子。
(1)赋值出错处理
在调试赋值函数时发生了“赋值失败的情况”
修改前的函数:
voidFindVary(char*c,char*e)
//找出表达式中的变量
{
inti=-1,j=0;
while(e[++i])
if(e[i]>='A'&&e[i]<='Z'||e[i]>='a'&&e[i]<='z'){
i+=2;
continue;}
else
c[j++]=e[i];
c[j]=0;
}
调试出现下面的情况
出现了两次了对X的赋值,原因是我在编写voidFindVary(char*c,ch