一、题目描述
问题描述
带有变量的中缀表达式是常见的数学表达式。如果规定变量由长度不超过 8 个小写字母组成;end为保留字,表示程序段结束;用?表示输出指定变量的值,则可以设计出比较复杂的表达式(即一个可顺序执行语句序列)。例如,如果有如下语句段:
abc=10
def=8
c=abc+def
abc=abc+5-c*2
? c
? abc
end则输出为:
c=18
abc=-21注意:为了简化编程实现,运算符只有+,-,*,/ ,%和^(指数运算),可以处理圆括号(),并假定输入的算术表达式正确。
要求:使用栈结构实现。
输入:表达式序列
输出:全部指定变量的值
表达式中的全部计算结果均为整数。如果在计算过程中出现除数为0的情况,则输出:Divide 0.
特殊情况说明:
在表达式中,如果操作数出现负数(例如-8),则要特别注意。例如:
10加-8表示为:10+-8。
10减-8表示为:10--8。
二、几个测试用例
1.输入
abcdefgh=4
ten=10
a=18-32
? a
b=18/abcdefgh
? b
c=18%(b-1)
? c
d=ten+(ten+ten)*abcdefgh
? d
e=ten-2*ten/abcdefgh
? e
f=(18-3)*3
? f
ten=ten*(ten)
? ten
ten=ten/10
ten=(ten+2)/(8-ten)
? ten
h=(2*3)/(5*2)
? h
ten=10
x=ten-(80-30)/3*3+abcdefgh
y=(((2+8)*2-(2+abcdefgh)/2)*2-8)*2 6ka max
z=(((8+2)*(abcdefgh/2)))
? x
? y
? z
end输出:
- a=-14↵
- b=4↵
- c=0↵
- d=90↵
- e=5↵
- f=45↵
- ten=100↵
- ten=-6↵
- h=0↵
- x=-34↵
- y=52↵
- z=20↵
2.输入
a=10↵
? a↵
b=-10+10↵
? b↵
a=-10-10↵
? a↵
c=a+-10+-10↵
? c↵
d=-20+-8-8↵
? d↵
e=a-12*-2↵
? e↵
f=80--10+2↵
? f↵
g=-10--10↵
? g↵
h=-10+-10↵
? h↵
i=(90)↵
? i↵
k=(-100)↵
? k↵
end↵输出:
- a=10↵
- b=0↵
- a=-20↵
- c=-40↵
- d=-36↵
- e=4↵
- f=92↵
- g=0↵
- h=-20↵
- i=90↵
- k=-100↵
3.输入:
- a=12↵
- b=5↵
- c=a/b↵
- d=(a+1)%(b+1)↵
- e=a^2↵
- f=-12+a↵
- g=(a+5)*d-c↵
- ? c↵
- ? d↵
- ? e↵
- ? f↵
- ? g↵
- a=a+10↵
- ? a↵
- end
输出:
- c=2↵
- d=1↵
- e=144↵
- f=0↵
- g=15↵
- a=22↵
三、具体代码
这是借鉴学习学长(摇摆的小土豆z)代码后自己手敲一边并且做了保姆级讲解的代码。
本来是想用C写,但是由于这个题本身就挺复杂的,还要自己实现栈的话就太麻烦了,所以调用C++库的栈了。
不过要是觉得别扭,就直接手动搓一个栈出来,然后以相同的变量命名栈,其他不做任何改变,那么这就是一个真正的C代码啦。
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stack>
#define max 50
using namespace std;
int priority(char);
int calculate(int,int,char);
//定义结构体,存放某个变量的名字和该变量的值
typedef struct Node
{
char name[10];
int value;
}eval;
//结构体数组
eval num[max];
//两个栈,分别是数字栈和操作符栈
stack <int> Value;
stack <char> op;
int main()
{
char str[max];//存放某一句语言
int count=0,length;//count记录有多少个变量,length是对此时语句长度计算
memset(str,'\0',sizeof(char)*max);//先初始化为'\0'
while(!Value.empty())//初始化栈
{
Value.pop();
}
while(!op.empty())
{
op.pop();
}
while(1)
{
scanf("%s",str);
if(strcmp(str,"end")==0)//end就结束(这也是为什么用'\0'初始化str,因为strcmp对比的是'\0'以前的字符串,如果不初始化或者初始化为其他,那么就可能会多比较,即便是end也不会停止)
{
break;
}
while(!Value.empty())//初始化栈(每次进入一个新等式时都要初始化一次,虽然不初始化也对,毕竟每次遍历完赋值都只是数字栈栈顶的元素。但是上一次的计算结果留着感觉膈应的很,还是清空吧)(另外放在end判断的后面是因为这样可以使得最后输入为end的那次不用遍历清空了,减少操作次数)
{
Value.pop();
}
while(!op.empty())
{
op.pop();
}
length=strlen(str);//此语言的长度
int judge=0;//这是判断此次变量是否以前出现过
if(str[0]=='?')//这表示后面的变量你需要计算值
{
scanf("%s",str);//一切都那么的恰到好处,输入是? a,刚好?用于第一次str判断,然后a用于后面的str输出值,空格直接就忽略掉了,根本不用gets都能实现(用gets反而不好)
for(int i=0;i<count;i++)
{
if(strcmp(str,num[i].name)==0)
{
printf("%s",str);
printf("=");
printf("%d\n",num[i].value);
break;
}
}
}
else//若不是要此次进行输出值的变量(不是?打头的),那我们就进入这里进行计算并储存,以便后续要输出的时候能输出
{
char temp_name[10]={'\0'};//道理同上,我们用strcpy和strcmp都是以'\0'作为字符串截至的标志,如果不初始化为'\0'就有可能一直输往后找知道找到'\0',那么多出来的那些也算作你也对比的字符串,因此就不对了
int flag=0,index;//flag表示对该语句判断到哪一个了,index表示该变量在num数组中的位置
while(str[flag]!='=')//=之前,也就是变量名
{
temp_name[flag]=str[flag];
flag++;
}
if(count==0)//没有变量
{
strcpy(num[0].name,temp_name);//直接存num[0]中
count++;//++表示有一个了
index=0;//此时其在数组的位置就是0
}
else//有变量
{
int judge=0;//判断是否重复出现
for(int i=0;i<count;i++)//遍历已有的变量
{
if((strcmp(temp_name,num[i].name))==0)//看此次的是否跟已有的一样
{
judge=1;//标记
index=i;//与相同变量名的下标一致
break;//跳出
}
}
if(judge==0)//不是的话就在num中新找一个位置给此次的变量储存
{
index=count;//num中的下标
strcpy(num[count].name,temp_name);//变量名名字
count++;//变量数++
}
}
flag++;//此时flag指向'=',我们要考虑等号后的,所以要++
int start=flag;//此时的flag是起始的,给start用于后续判断
int judge=1;//此时就是标记数字正负的
while(flag<length)//只要没有完就要进入循环
{
if(str[flag]>='0' && str[flag]<='9')//如果遇到的是数字
{
int t=0;//中间计算结果
while((flag<length) && (str[flag]>='0' && str[flag]<='9'))//那就从当前数字一直向后,直到遇到第一个不是数字的或者遍历完了
{
t=t*10+str[flag]-'0';//字符转整数加
flag++;//下一位
}
Value.push(t*judge);//计算结果压入栈中待使用(*judge意味着正负也考虑了)
judge=1;//***这很重点,一旦本次的正负考虑了(也就是t*judge并压人栈了),我们就把正负标记(judge)初始化。有一个错在这了
}
else if(str[flag]=='+' || str[flag]=='-' || str[flag]=='*' || str[flag]=='/' || str[flag]=='%' || str[flag]=='^' || str[flag]=='(' || str[flag]==')')
{
//如果是运算符(还要包括左右括号,有一次错在这了)
if(op.size()==0)//操作栈空
{
if(flag==start && str[flag]=='-')//***此时是第一个并且还是'-'的话,那么它不是操作符而是表示负数(这里很坑),所以judge要为-1表示是负数
{
judge=-1;
}
else//只要不是上面内中坑人的情况,我们就直接压入栈就行
{
op.push(str[flag]);
}
}
else//操作栈不空
{
if(flag>start && str[flag]=='-' && (str[flag-1]=='+' || str[flag-1]=='-' || str[flag-1]=='*' || str[flag-1]=='/' || str[flag-1]=='%' || str[flag-1]=='^' || str[flag-1]=='(' || str[flag-1]==')'))
{
judge=-1;//***当前没考虑完并且当前是'-'并且前一个还是操作符,那么这个'-'也不是操作符而是负数的负号,所以judge为-1(注意这里两个点:1.是flag-1而非flag 2.左右括号都包括 有两次就错这了)
}
else if(str[flag]=='(')//左括号压入栈中
{
op.push(str[flag]);
}
else if(str[flag]==')')//右括号就要把左边的表达式计算完
{
while(op.size()!=0 && op.top()!='(')//什么才叫计算完?就是要么操作栈空了(没有操作符了自然完了)(这个就当放屁,因为有右括号一定有左括号啊,咋可能就空了),要么遇到左括号了(表示这个括号计算完了)
{
char temp_op=op.top();//拿栈顶操作符
op.pop();//使用了就弹出
int value_temp=Value.top();//拿栈顶value
Value.pop();//使用了就弹出
Value.top()=calculate(Value.top(),value_temp,temp_op);//将数字栈上面两个用操作符操作了然后赋值给新的数字栈顶
}
op.pop();//出来表示遇到左括号了,左括号屁用没用,直接弹
}
else//其余情况
{
if(op.size()!=0 && str[flag]=='^' && op.top()=='^')//多次累乘方的情况
{
op.push(str[flag]);
}
else//其余中的其余情况(更加一般)
{
if(op.size()!=0 && priority(str[flag])<=priority(op.top()))//不空并且当前要放的比操作栈顶的优先级小,那么就对此时栈顶的操作符对应的两边的数字进行操作(这个原因很好解释),相同也操作,因为可以减少储存量
{
while(op.size()!=0 && priority(str[flag])<=priority(op.top()))//进入循环,就从此开始往前找,只要操作栈不空并且每一轮的栈顶的优先级都大于要放的这个操作符,就进入操作,即将该栈顶操作符两边控制的数字进行操作计算
{
char temp_op=op.top();//拿栈顶操作符
op.pop();//使用了就弹出不要了
int value_temp=Value.top();//拿栈顶数字
Value.pop();//使用了就弹出不要了
Value.top()=calculate(Value.top(),value_temp,temp_op);//将数字栈上面两个用操作符操作了然后赋值给新的数字栈顶
}
op.push(str[flag]);
}
else//大于的话就继续压入不操作
{
op.push(str[flag]);
}
}
}
}
flag++;//没进入这个else一次,代表当前这个操作符就考虑完了(不管进入到这个大else里面的哪一个if中),我们就要进入下一个字符的考虑,所以flag++
}
else//不是操作符也不是数字,在这个题目中那就只能是变量了
{
char temp_name[10]={'\0'};//道理同上
int j=0;//是为了控制temp_name的
while(flag<length && (str[flag]>='a' && str[flag]<='z'))//现在是找这个变量的全部名字,那就从前往后遍历,既不能出总字符长度又不能不是字母。
{
temp_name[j]=str[flag];//将此时str的字符给temp_name,这就是这个变量名字的全部或者一部分
j++;//temp_name数组位置到下一个,存下一个字母
flag++;//str也考虑下一个字母
}
for(int i=0;i<count;i++)//对已有的变量进行遍历,看有没有跟这个一样的
{
if(strcmp(num[i].name,temp_name)==0)//在已有计算好value的变量中找到了和当前这个等式右边正在考虑的这个变量
{
Value.push(num[i].value*judge);//将这个变量值*judge(表明正负)然后压入栈
judge=1;//同理,对于正负判断变量,使用过了就要初始化为1
break;//找到了一个就break(不可能有多个跟这个同名的,因为多出来的那些在最开始就没有在num中占地方,他们相当于归一化了,所以一定只有一个,这个放心吧)
}
}
}
}
//以上表示一个等式考虑完了
//下面就是要对于等式左边的变量赋值
while(op.size())//如果操作栈不空就是还有操作没完,那我们就对已有的操作找它所控制的两边的数字进行计算
{
int temp_value=Value.top();
Value.pop();
Value.top()=calculate(Value.top(),temp_value,op.top());
op.pop();
}
num[index].value=Value.top();//计算完后,此时这个变量在num中的位置就是index,其值value就是计算结果就是数字栈顶的数字,所以进行赋值就行
}
}
return 0;
}
//判断优先级函数,优先级哪个高哪个低其实都是固定的,记住就行
int priority(char c)
{
if(c==')')
{
return 5;
}
if(c=='^')
{
return 4;
}
if(c=='*' || c=='/' || c=='%')
{
return 3;
}
if(c=='+' || c=='-')
{
return 2;
}
if(c=='(')
{
return 1;
}
}
//计算函数,这个很简单
int calculate(int top,int value,char op)
{
if(op=='+')
{
return top+value;
}
if(op=='-')
{
return top-value;
}
if(op=='*')
{
return top*value;
}
if(op=='/')
{
return top/value;
}
if(op=='%')
{
return top%value;
}
if(op=='^')
{
return pow(top,value);
}
}