以下源码均为原创,仅供参考,如有差错或优化欢迎指正交流。
以下出现源码均由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#
本例使用的是顺序栈,以下给出顺序栈的结构类型定义和基本操作的子函数定义。
顺序栈结构类型定义
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;
}
本例的第一个重点,也是第一个重要的考察点,即中缀表达式转化为后缀表达式。(这里引用后缀表达式的介绍,这是本程序实现的必要途径。)
转化过程中的要点
括号的问题将单独考虑,所以在对运算符优先级的判断中暂不考虑这个问题,只对加‘+’、减‘-’、乘‘*’、除‘/’进行判断,以下给出该功能函数。
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;
}
}
测试数据的测试
#(8-2)/(3-1)*(9-6)#
#5+a*(8-4)/2#
第三个表达式中有一个运算数为字母'a',在中缀转后缀处理过程中将作为运算符进行判断,经过判断为“非法运算符”,所以不入栈也不输出在后缀表达式中。
在计算后缀表达式的过程中,因为字符串仍有运算符存在,但栈中存储的数的数量因为缺少了'a'的位置,所以实际上不够两个,出栈时就会出错,出现出栈失败时会出现的“out error!”字样。
所以最后输出的结果其实也是一个没有壹壹的运算结果。
#5+(7-3)*6#
在目前的试验数据中尚未发现任何bug。
如果发现有源码中不完善的地方,欢迎交流讨论。