利用栈实现算术表达式的求值

来源:互联网 发布:金马甲网络交易平台 编辑:程序博客网 时间:2024/05/17 03:39

以下源码均为原创,仅供参考,如有差错或优化欢迎指正交流。

以下出现源码均由DevC++编译通过。


下面是原题目


利用栈实现算术表达式的求值

[问题描述]

利用栈实现算术表达式的求值。可以简单一些,假设表达式中含有一位正整数,以及+、-、*、/、(、)。但不受此限制。(难易程度:中)

[实验内容及要求]

1、表达式以字符串形式输入,并以‘#’开始和结束(‘#’也作为算法来处理)。如输入:#6+3*(9-7)-8/2#

2、能够有效判别表达式的输入格式是否有误(如缺失操作数、非法算符、括号不匹配等错误),若输入格式错误,输出错误提示。

[测试数据]

1、#6+3*(9-7)-8/2#

2、#(8-2)/(3-1)*(9-6)#

3、#5+a*(8-4)/2#

4、#5+(7-3)*6#


[题目简析]

本题中给出了描述,运算数均为“一位正整数”,这个条件大大降低了本题的难度,可以将数字和字符一同输入,然后再进行分开处理。
考虑用字符串作为表达式的存储方式,需要进行对数字的处理时再单独拿出来转化处理。

[基本步骤]

  1. 中缀表达式——>后缀表达式(逆波兰表达式)(利用栈)
  2. 后缀表达式求解(利用栈)


[程序细节]

  • 表达式通过字符串的方式输入和存储
  • 使用的栈类型为顺序栈
  • 数字以字符型变量存储与整型变量存储的区别和相互转化


[实现过程]

先给出需要用到的功能性子函数。

这些函数均是基础性内容,不是本例重点。


本例使用的是顺序栈,以下给出顺序栈的结构类型定义和基本操作的子函数定义。

顺序栈结构类型定义

typedef struct{char data[50];int top;}stack;//顺序栈类型定义


栈的基本操作

void newstack(stack *&S)//初始化顺序栈{S=(stack*)malloc(sizeof(stack));S->top=-1;} int push(stack*S,char e)//进栈 {if(S->top>49){printf("in error!\n");return 0;}else{S->data[++S->top]=e;return 1;}} int empty(stack*S)//判断栈空{if(S->top<0)    return 1;else    return 0; } char pop(stack*S)//出栈{char e;if(empty(S)){printf("out error!\n");return 0;}else{e=S->data[S->top--];return e;}} 

表达式字符串中数字和运算符号均以字符型变量存储,需要对数字进行识别,以及整型与字符型的转变。以下给出相关功能的函数。

字符型变量是否是数字

int isnum(char e)//判断是否为数字 {if(e>='0'&&e<='9')    return 1;else    return 0;}

字符型转化为整型

int num(char e)//字符型转换为整型 {int n;n=e-48;//ASCII码 return n;} 


整型转化为字符型

char nonum(int n)//整型转换为字符型 {char e;e=n+48;//ASCII码 return e;} 

下面依据题目要求进行主体内容的编码实现。


题目中要求需要对输入的表达式正确性进行必要的判断,其中括号是否匹配的判断单独进行更为简便。以下给出该功能的函数。

int correct(char s[])//判断括号是否匹配 {stack *S;newstack(S);int flag=1,i=1;while(s[i]!='#'&&flag){if(s[i]=='(')    push(S,s[i]);if(s[i]==')')    if(pop(S)!='(')        flag=0;i++;}//最先遇到的后括号前必定是与之对应的前括号,如若匹配不成功,则flag记为0,即括号不匹配if(!empty(S))    flag=0;return flag;} 


本例的第一个重点,也是第一个重要的考察点,即中缀表达式转化为后缀表达式。(这里引用后缀表达式的介绍,这是本程序实现的必要途径。)

转化过程中的要点

  1. 运算符号优先级的判断
  1. 运算符号遇到括号时的优先级判断
其中,第二条(运算符号遇到括号时的优先级判断)是本例的一个难点,本例中将在对括号的单独处理中(而不是对运算符或运算符和括号的处理)解决这个问题。

括号的问题将单独考虑,所以在对运算符优先级的判断中暂不考虑这个问题,只对加‘+’、减‘-’、乘‘*’、除‘/’进行判断,以下给出该功能函数。

int rank(char a,char b)//判断运算符优先级,不包括括号的优先级 {if((a=='*'||a=='/')&&(b=='+'||b=='-'))    return 1;    else        return 0;}

之后是中缀表达式转化为后缀表达式的实现,思路描述如下:

中缀表达式a + b*c + (d * e + f) * g,其转换成后缀表达式则为a b c * + d e * f + g * +。
转换过程需要用到栈,具体过程如下:
1)如果遇到操作数,我们就直接将其输出。
2)如果遇到操作符,则我们将其放入到栈中,遇到左括号时我们也将其放入栈中。
3)如果遇到一个右括号,则将栈元素弹出,将弹出的操作符输出直到遇到左括号为止。注意,左括号只弹出并不输出。
4)如果遇到任何其他的操作符,如(“+”, “*”,“(”)等,从栈中弹出元素直到遇到发现更低优先级的元素(或者栈为空)为止。弹出完这些元素后,才将遇到的操作符压入到栈中。有一点需要注意,只有在遇到" ) "的情况下我们才弹出" ( ",其他情况我们都不会弹出" ( "。
5)如果我们读到了输入的末尾,则将栈中所有元素依次弹出。

对该过程仍有疑问的可以阅读以下推荐链接,了解该过程的转化算法思路。

链接1:后缀表达式——凌波ling

链接2:中缀表达式转化为后缀表达式——石锅拌饭


通过以上描述,给出实现以上功能的源码。

void trans(char s1[],char s2[])//中缀表达式转换为后缀表达式,s1为中缀表达式,s2为后缀表达式{int i=1,j=0;char e;stack *S;newstack(S);while(s1[i]!='#'){if(isnum(s1[i]))//是数字直接输出     s2[j++]=s1[i];else//运算符号,括号的处理 {if(s1[i]=='(')//前括号直接入栈     push(S,s1[i]);if(s1[i]==')')//遇到后括号输出栈顶运算符直至遇到左括号,左括号出栈但不输出 while((e=pop(S))!='(')s2[j++]=e;if(s1[i]=='+'||s1[i]=='-'||s1[i]=='*'||s1[i]=='/')//栈空入栈,栈顶为左括号入栈,优先级高于栈顶运算符入栈,否则出栈并再次判断,实际实现时先进行是否出栈判断 {while(!(empty(S)||S->data[S->top]=='('||rank(s1[i],S->data[S->top])))    s2[j++]=pop(S);    if((empty(S)||S->data[S->top]=='('||rank(s1[i],S->data[S->top])))        push(S,s1[i]);} if(!(s1[i]=='+'||s1[i]=='-'||s1[i]=='*'||s1[i]=='/'||s1[i]=='('||s1[i]==')'))    printf("非法运算符!\n"); //除过四则运算和括号(+、-、*、/、(、))外,其余判定为非法运算符}i++;}while(!empty(S))//表达式读毕将栈内运算符一一输出     s2[j++]=pop(S);s2[j]='\0';//字符串结尾 }

最后进行后缀表达式的求值,如果理解后缀表达式的话,这个步骤应用栈很容易实现,这里不做赘述。需要注意的是,是否缺少运算符的判断将在这一步最后进行。

下面给出这部分子函数。

int workout(char s2[])//计算后缀表达式的值 {int a,b,i=0;stack *S;newstack(S);while(s2[i]!='\0'){if(isnum(s2[i]))//数字入栈等待运算符操作     push(S,s2[i++]);else{b=num(pop(S)); a=num(pop(S));//字符型转为整型进行运算     switch(s2[i++])    {    case'+':    a=a+b;    push(S,nonum(a));    break;    case'-':    a=a-b;    push(S,nonum(a));    break;    case'*':    a=a*b;    push(S,nonum(a));    break;    case'/':    a=a/b;    push(S,nonum(a));    break;//运算后转为字符型入栈     default:    printf("error!");}}}if(S->top!=0)//缺少运算符时,输出提示,但仍然带回栈顶元素的值作为运算结果输出{printf("缺少运算符!得到中间结果!\n");}a=num(pop(S));//字符型转为整形带入返回值 return a;} 

至此整个题目中需要的功能的子函数都已经完成,只需要在主函数中调用实现相应的功能就可以。对括号的判断过程和结果输出包括在主函数中。

下面给出主函数的源码。

int main(){int ans;char s1[80],s2[80];printf("输入表达式:"); gets(s1);if(!(correct(s1))){printf("括号不匹配!");return 0;} else{    trans(s1,s2);    printf("后缀表达式:");     puts(s2);     ans=workout(s2);    printf("ans=%d\n",ans);    system("pause");     return 0;}}

整个程序的主体已经完成,将上文的源码进行编译得到符合题目要求的可执行文件。


测试数据的测试

  1. #6+3*(9-7)-8/2#
  2. #(8-2)/(3-1)*(9-6)#
  3. #5+a*(8-4)/2#
  4. #5+(7-3)*6#

#6+3*(9-7)-8/2#



#(8-2)/(3-1)*(9-6)#



#5+a*(8-4)/2#


第三个表达式中有一个运算数为字母'a',在中缀转后缀处理过程中将作为运算符进行判断,经过判断为“非法运算符”,所以不入栈也不输出在后缀表达式中。

在计算后缀表达式的过程中,因为字符串仍有运算符存在,但栈中存储的数的数量因为缺少了'a'的位置,所以实际上不够两个,出栈时就会出错,出现出栈失败时会出现的“out error!”字样。

所以最后输出的结果其实也是一个没有壹壹的运算结果。


#5+(7-3)*6#



在目前的试验数据中尚未发现任何bug。

如果发现有源码中不完善的地方,欢迎交流讨论。

原创粉丝点击