【利用栈实现算术表达式的求值】
//https://blog.csdn.net/YanHS_/article/details/78702294
[问题描述]
利用栈实现算术表达式的求值。可以简单一些,假设表达式中含有一位正整数,
以及+、-、*、/、(、)。(难易程度:中)
[实验内容及要求]
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)如果我们读到了输入的末尾,则将栈中所有元素依次弹出。
对该过程仍有疑问的可以阅读以下推荐链接,了解该过程的转化算法思路。
链接2:中缀表达式转化为后缀表达式——石锅拌饭
通过以上描述,给出实现以上功能的源码。
void trans(char s1[],char 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;
}
}
测试数据的测试
- #6+3*(9-7)-8/2#
- #(8-2)/(3-1)*(9-6)#
- #5+a*(8-4)/2#
- #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#