18.11.23
这是一道最近刚上的实验课的题目。。。。
基于C语言,欢迎指正
掌握栈在解决实际问题中的应用,设计一个程序,演算用算符优先法对算术表达式求值的过程,利用算符优先关系,实现对算术四则混合运算表达式的求值
。。挺难的。。
思路在这!
这个程序是这样,当你点击运行它的时候,你需要输一个表达式,然后按个回车,答案就出来了,跟个计算器一样
那么,在一开始,我们需要一个数组,来存入你所输入的表达式,这样就得到了一个字符数组,然后呢,我们需要创建两个栈,一个用来存运算符,我们叫他运算符栈opter,一个用来存操作数,我们叫它操作数栈opnd,然后我们开始读入我们输入的表达式,读到一个操作符就加到一个新的字符数组,比如说,23+1这个式子,先读入一个’2’存入字符数组,在读入一个’3’存入字符数组,当读到’+’ ,这个时候字符数组有两个元素,给字符数组的第三号位加上一个’\0’,就变成了一个字符串,这样用atof函数就是可以把这个字符串变成浮点型从而可以参与运算(不然如果还是字符状态是不能参与运算的),然后把得到的这个23压入操作数栈中进行相应操作,相应操作如下:
这是来自学校发的实验报告册上的:(认真看还是看的懂的)
(1)首先将操作数栈opnd设为空栈,而将’#'作为运算符栈opter的栈底元素,这样的目的是判断表达式是否求值完毕。
(2)依次读入表达式的每个字符,表达式须以’#‘结尾,若是操作数则入栈opnd,若是运算符,则将此运算符c与opter的栈顶元素top比较优先级后执行相应的操作,具体操作如下:
(i)若top的优先级小于c,即top
(iii)若top优先级高于c,即top>c,则表明可以计算,此时弹出opnd的栈顶两个元素,并且弹出opter栈顶的的运算符,计算后将结果放入栈opnd中。直至opter的栈顶元素和当前读入的字符均为’#’,此时求值结束。
表中需要注意的是θ1为opter的栈顶元素,θ2为从表达式中读取的操作符,此优先级表可以用二维数组实现。
有.思路了,咱们把整个程序拆开来看看
1. 一堆头文件
2. 一个用于优先级比较的数组,
3. 两个栈(我们这里用的是顺序栈)
其中,这个用于运算符比较的数组是这个
“>><<<>>”,
“>><<<>>”,
“>>>><>>”,
“>>>><>>”,
“<<<<<=?”,
“>>>>?>>”,
“<<<<=”
即这个
代码如下
#include
#include//包含exit()函数
#include//跟字符处理有关的头文件
#include
#define Stack_Size 1000 //认为Stack_Size为1000
char cmp[7][8]= {">><<<>>",">><<<>>",">>>><>>",">>>><>>","<<<<<=?",">>>>?>>","<<<<="};
typedef struct{//定义一个运算符栈
char Elem[Stack_Size];
int top;//标记变量,记录表尾的变化
}Opter;
typedef struct{//定义一个操作数栈
double Elem[Stack_Size];
int top;
}Opnd;
准备活动完成了,接下来是对栈的一些基础操作进行设定
比如说
(下面的S是传进来的栈的指针地址)
代码如下
void InitOpter(Opter *S){//初始化运算符栈
S->top=-1;
}
void InitOpnd(Opnd *S){//初始化操作数栈
S->top=-1;
}
int PopOpter(Opter *S)//弹出运算符栈
{
if(S->top==-1)
{
printf("运算符栈为空\n");
exit(10);
}
S->top--;
return 1;
}
int PopOpnd(Opnd *S)
{
if(S->top==-1)
{
printf("运算符栈为空\n");
exit(11);
}
S->top--;
return 1;
}
int PushOpter(Opter* S,char ch)
{
if(S->top==Stack_Size-1)
{
printf("运算符栈满\n");
exit(12);
}
S->top++;
S->Elem[S->top]=ch;
return 1;
}
int PushOpnd(Opnd* S,double ch)//入操作数栈
{
if(S->top==Stack_Size-1)
{
printf("运算符栈满\n");
exit(13);
}
S->top++;
S->Elem[S->top]=ch;
return 1;
}
char GetOpter(Opter *S)//获取运算符栈的栈顶元素,注意区分返回值类型
{
if(S->top==-1)
{
printf("运算符栈为空\n");
exit(17);
}
return S->Elem[S->top];
}
double GetOpnd(Opnd *S)
{
if(S->top==-1)
{
printf("操作数栈为空\n");
exit(18);
}
return S->Elem[S->top];
}
有了这些基本工具,接下来是计算函数
double Calc(double a,double b,char opt)//计算函数,传入两个数以及一个运算符
{
double T; //T用于存放计算得出的结果
if(opt=='+') T=a+b;
if(opt=='-') T=a-b;
if(opt=='*') T=a*b;
if(opt=='/') //要防止发生除0错误
{
if(fabs(b)<0.00001)
{
printf("发生除0错误\n");
exit(15);
}
T=a/b;
}
printf("中间过程输出: %.2lf %c %.2lf = %.2lf\n",a,opt,b,T);
return T; //返回得到的结果
}
在程序执行过程中,我们需要比较两个运算符的优先级来进行计算,所以我们需要创建一个比较函数
int change(char ch)
{
switch(ch)
{
case '+':
return 0;
case '-':
return 1;
case '*':
return 2;
case '/':
return 3;
case '(':
return 4;
case ')':
return 5;
case '#':
return 6;
}
}
int Compare(char ch1,char ch2)
{
if(cmp[change(ch1)][change(ch2)]=='?'){
printf("输入表达式错误");
exit(16);
}
return cmp[change(ch1)][change(ch2)];
}
将运算符转变为优先级比较的数组的下标,通过查阅优先级比较数组中对应的结果,再传给函数,其中change函数就是起到查阅的作用
我们可能输入表达式的时候会多输入了一些不在范围内的字符,这就需要一个检查函数来排查
int Check(char *S,int len)//检查函数,记得考虑输入带小数点的数字的情况
{
int i;
for(i=0;i='0'&&S[i]<='9')continue;
if(S[i]=='('||S[i]==')'||S[i]=='*'||S[i]=='/'||S[i]=='+'||S[i]=='-'||S[i]=='.')continue;
return 0;
}
return 1;
}
有了上面的这些函数,我们就可以在主函数中进行调试了,我们一些具体的运算操作也是要在主函数中实现的
我们直接在代码中进行解释
代码如下
int main()
{
char a[1000],b[1000]; //创建两个数组,a是用来存输入的表达式的,b是用来存操作数的
int len; //len为输入表达式的长度,通过strlen求得
Opter S; //创建一个运算符栈
Opnd N; //创建一个操作数栈
InitOpnd(&N); //初始化操作数栈
InitOpter(&S); //初始化运算符栈
PushOpter(&S,'#');
//注意这里,我们事先在运算符栈中压入一个'#',在输入表达式后,在表达式数组中最后一个位置也设为'#',
//之后在运算结束时这两个#会相见,比较函数返回'=',使得最终的运算结束
printf("输入表达式:\n");
scanf("%s",a); //输入表达式,注意这里a不用取地址符&,因为数组其实就是一个地址,它保留的是数组第一个元素的首地址,a其实就是&a[0]
len=strlen(a); //求输入的表达式的长度,并打印出来
printf("字符长度为%d\n",len);
if(Check(a,len)==0) //检查是否多输入了一些奇奇怪怪的东西
{
printf("输入中存在多余字符\n");
exit(19);
}
int i,j=0,k=0;
double x,y; //x,y是从操作数中取出的两个即将用于计算的数
a[len]='#'; //注意
for(i=0;i<=len;i++) //遍历我们输入的表达式
{
if((a[i]>='0'&&a[i]<='9')||a[i]=='.')//如果为数字
{
b[k++]=a[i];//将数字存入数组b中,注意此时数字仍为字符
j=1;
continue; //在该循环下其余部分都不做了,直接进入下一次xunh
}
if(j)//条件成功即遇到了运算字符,将操作数压入操作数栈中
{
//此时数组b已经有了一个或者几个数字在里面,需要加一个'\0'使其成为字符串
//再通过atof函数使其由字符型变为双精度型,然后加入操作数栈中进行相应运算
b[k]='\0';
PushOpnd(&N,atof(b));//atof函数可以使char变为double
j=0;
k=0; //k置零为下一次计数做准备
}
switch(Compare(GetOpter(&S),a[i]))//比较运算符栈的栈顶运算符top和运算符a[i]的优先级
{ //底下的部分我们按照之前给的规则来写
case '<'://即top':
//当为‘>'的情况,即需要进行运算,先取操作数栈中最上面的两个元素
y=GetOpnd(&N),PopOpnd(&N);
x=GetOpnd(&N),PopOpnd(&N);
//然后将计算结果压入操作数栈中
PushOpnd(&N,Calc(x,y,GetOpter(&S)));
//已经用过的运算符就废掉了,弹出!!!
PopOpter(&S);
i--;//这句是为了重新把反括号入栈,使之与左括号配对,否则会造成左括号多余出来
break;
}
}
double answer=GetOpnd(&N);//最终操作数栈中的数就是我们想要的结果
printf("最终结果为%.2lf",answer);
return 0;
}
那么用算符优先法对算术表达式求值的程序就算是写完啦
#include
#include
#include
#include
#define Stack_Size 1000
char cmp[7][8]= {">><<<>>",">><<<>>",">>>><>>",">>>><>>","<<<<<=?",">>>>?>>","<<<<="};
typedef struct{//定义一个运算符栈
char Elem[Stack_Size];
int top;
}Opter;
typedef struct{//定义一个操作数栈
double Elem[Stack_Size];
int top;
}Opnd;
void InitOpter(Opter *S){//初始化运算符栈
S->top=-1;
}
void InitOpnd(Opnd *S){//初始化操作数栈
S->top=-1;
}
int PopOpter(Opter *S)//弹出运算符栈
{
if(S->top==-1)
{
printf("运算符栈为空\n");
exit(10);
}
S->top--;
return 1;
}
int PopOpnd(Opnd *S)
{
if(S->top==-1)
{
printf("运算符栈为空\n");
exit(11);
}
S->top--;
return 1;
}
int PushOpter(Opter* S,char ch)
{
if(S->top==Stack_Size-1)
{
printf("运算符栈满\n");
exit(12);
}
S->top++;
S->Elem[S->top]=ch;
return 1;
}
int PushOpnd(Opnd* S,double ch)//入操作数栈
{
if(S->top==Stack_Size-1)
{
printf("运算符栈满\n");
exit(13);
}
S->top++;
S->Elem[S->top]=ch;
return 1;
}
char GetOpter(Opter *S)//获取运算符栈的栈顶元素
{
if(S->top==-1)
{
printf("运算符栈为空\n");
exit(17);
}
return S->Elem[S->top];
}
double GetOpnd(Opnd *S)
{
if(S->top==-1)
{
printf("操作数栈为空\n");
exit(18);
}
return S->Elem[S->top];
}
double Calc(double a,double b,char opt)//计算函数,传入两个数以及一个运算符
{
double T; //T用于存放计算得出的结果
if(opt=='+') T=a+b;
if(opt=='-') T=a-b;
if(opt=='*') T=a*b;
if(opt=='/') //要防止发生除0错误
{
if(fabs(b)<0.00001)
{
printf("发生除0错误\n");
exit(15);
}
T=a/b;
}
printf("中间过程输出: %.2lf %c %.2lf = %.2lf\n",a,opt,b,T);
return T; //返回得到的结果
}
int change(char ch)
{
switch(ch)
{
case '+':
return 0;
case '-':
return 1;
case '*':
return 2;
case '/':
return 3;
case '(':
return 4;
case ')':
return 5;
case '#':
return 6;
}
}
int Compare(char ch1,char ch2)
{
if(cmp[change(ch1)][change(ch2)]=='?'){
printf("输入表达式错误");
exit(16);
}
return cmp[change(ch1)][change(ch2)];
}
int Check(char *S,int len)//检查函数,记得考虑输入带小数点的数字的情况
{
int i;
for(i=0;i='0'&&S[i]<='9')continue;
if(S[i]=='('||S[i]==')'||S[i]=='*'||S[i]=='/'||S[i]=='+'||S[i]=='-'||S[i]=='.')continue;
return 0;
}
return 1;
}
int main()
{
char a[1000],b[1000]; //创建两个数组,a是用来存输入的表达式的,b是用来存操作数的
int len; //len为输入表达式的长度,通过strlen求得
Opter S; //创建一个运算符栈
Opnd N; //创建一个操作数栈
InitOpnd(&N); //初始化操作数栈
InitOpter(&S); //初始化运算符栈
PushOpter(&S,'#');
//注意这里,我们事先在运算符栈中压入一个'#',在输入表达式后,在表达式数组中最后一个位置也设为'#',
//之后在运算结束时这两个#会相见,比较函数返回'=',使得最终的运算结束
printf("输入表达式:\n");
scanf("%s",a); //输入表达式,注意这里a不用取地址符&,因为数组其实就是一个地址,它保留的是数组第一个元素的首地址,a其实就是&a[0]
len=strlen(a); //求输入的表达式的长度,并打印出来
printf("字符长度为%d\n",len);
if(Check(a,len)==0) //检查是否多输入了一些奇奇怪怪的东西
{
printf("输入中存在多余字符\n");
exit(19);
}
int i,j=0,k=0;
double x,y; //x,y是从操作数中取出的两个即将用于计算的数
a[len]='#'; //注意
for(i=0;i<=len;i++) //遍历我们输入的表达式
{
if((a[i]>='0'&&a[i]<='9')||a[i]=='.')//如果为数字
{
b[k++]=a[i];//将数字存入数组b中,注意此时数字仍为字符
j=1;
continue; //在该循环下其余部分都不做了,直接进入下一次xunh
}
if(j)//条件成功即遇到了运算字符,将操作数压入操作数栈中
{
//此时数组b已经有了一个或者几个数字在里面,需要加一个'\0'使其成为字符串
//再通过atof函数使其由字符型变为双精度型,然后加入操作数栈中进行相应运算
b[k]='\0';
PushOpnd(&N,atof(b));//atof函数可以使char变为double
j=0;
k=0; //k置零为下一次计数做准备
}
switch(Compare(GetOpter(&S),a[i]))//比较运算符栈的栈顶运算符top和运算符a[i]的优先级
{ //底下的部分我们按照之前给的规则来写
case '<'://即top':
//当为‘>'的情况,即需要进行运算,先取操作数栈中最上面的两个元素
y=GetOpnd(&N),PopOpnd(&N);
x=GetOpnd(&N),PopOpnd(&N);
//然后将计算结果压入操作数栈中
PushOpnd(&N,Calc(x,y,GetOpter(&S)));
//已经用过的运算符就废掉了,弹出!!!
PopOpter(&S);
i--;//这句是为了重新把反括号入栈,使之与左括号配对,否则会造成左括号多余出来
break;
}
}
double answer=GetOpnd(&N);//最终操作数栈中的数就是我们想要的结果
printf("最终结果为%.2lf",answer);
return 0;
}