NYOJ 题目35 表达式求值 (栈的应用)前中后缀,

来源:互联网 发布:alphago的算法 编辑:程序博客网 时间:2024/05/29 10:55

一。中缀表达式求值


表达式求值

时间限制:3000 ms  |  内存限制:65535 KB
难度:4
描述
ACM队的mdd想做一个计算器,但是,他要做的不仅仅是一计算一个A+B的计算器,他想实现随便输入一个表达式都能求出它的值的计算器,现在请你帮助他来实现这个计算器吧。
比如输入:“1+2/4=”,程序就输出1.50(结果保留两位小数)
输入
第一行输入一个整数n,共有n组测试数据(n<10)。
每组测试数据只有一行,是一个长度不超过1000的字符串,表示这个运算式,每个运算式都是以“=”结束。这个表达式里只包含+-*/与小括号这几种符号。其中小括号可以嵌套使用。数据保证输入的操作数中不会出现负数。
数据保证除数不会为0
输出
每组都输出该组运算式的运算结果,输出结果保留两位小数。
样例输入
2
1.000+2/4=
((1+2)*5+1)/4=
样例输出
1.50
4.00
此题为中缀表达式,也可以先转化成后缀表达式(逆波兰表达式)或前缀表达式(波兰表达式)求值;下面的例子会讲到

o算法分析:
n扫描三遍:第一遍去括号;第二遍算乘除;第三遍算加减。
o数据结构:
n用数组模拟栈;需要两个栈,一个是char 型用来存储号,另一个是double型用来存储数值。

栈的应用:

1.提取数字,有可能是浮点数,可以使用sscanf函数,sscanf(str,"%[^+()*/-]",buf);  先将操作数提取成字符串 
然后使用 double shu =  atof(buf);将字符串转换成double型;

2,操作符比较
左括号入栈,遇到右括号,弹出栈顶运算符,计算,入栈,直到遇到左括号,弹栈
栈定指针优先级大于当前操作符,则入栈,
否则则出栈两个操作数,计算后然后入栈;

3,最后操作数栈定元素即为结果


 #define TRUE 1#define FALSE 0#define STACK_INIT_SIZE 10002#include "iostream"#include "cstdlib"#include "cstdio"#include "cstring"using namespace std;typedef int Status;//Status 相当于 inttypedef struct Stack1//运算符栈{char * base;char * top;}SqStack1;typedef struct Stack2  //数字栈{double * base;double * top;}SqStack2;//运算符栈操作void  InitStack1(SqStack1 &S)//创建一个空栈{S.base = (char *)malloc(sizeof(char) * STACK_INIT_SIZE);S.top = S.base;}Status Stack1Empty(SqStack1 S)//判断是否为空{if(S.top == S.base)return TRUE;else return FALSE;}void Push1(SqStack1 &S,char e )//插入元素e为新的栈顶元素{* S.top++ = e;}void Pop1(SqStack1 &S,char &e)//出栈{e = * --S.top ;}char GetTop1(SqStack1 S){return *(S.top-1);}void TraverseStack1(SqStack1 S)//输出当前顺序表{char * p = S.base;cout<<"运算栈中的元素为:";while( p != S.top  ){cout<<*p<<" ";p++;}cout<<endl;}//数字栈操作void  InitStack2(SqStack2 &S)//创建一个空栈{S.base = (double *)malloc(sizeof(double) * STACK_INIT_SIZE);S.top = S.base;}void Push2(SqStack2 &S,double e )//插入元素e为新的栈顶元素{* S.top++ = e;}void Pop2(SqStack2 &S,double &e)//出栈{e = * --S.top ;}double GetTop2(SqStack2 S){return *(S.top-1);}void TraverseStack2(SqStack2 S)//输出当前顺序表{double * p = S.base;cout<<"数字栈栈中的元素为:";while( p != S.top  ){cout<<*p<<" ";p++;}cout<<endl;}//运算操作Status In(char ch)//判断ch是否为运算符{if(ch == '+' || ch == '('|| ch == '-' || ch == '*' || ch == '/' || ch == ')'  || ch == '=') return TRUE;else return FALSE;}char Precede(char a, char b) // 计算运算符优先级{char r;switch(b) {case '+' :      //此处由于加减几乎优先级一样,故放在一起case '-' :if (a=='(' || a=='=') r = '<';else r = '>';break;case '*' :      //此处由于乘除优先级一样,故放在一起case '/' :if (a=='*' || a=='/' || a==')') r = '>';else r = '<';break;case '(' :r = '<';break;case ')' :if (a=='(') r = '=';else r = '>';break;case'=':if (a=='=') r = '=';else r = '>';break;}return r;}double Operate(double a,char theta, double b){switch (theta){case '+': return a + b;break;case '-': return a - b;break;case '*': return a * b;break;case '/': return a / b;break;default : return FALSE;}}double EvaluateExpression(char str[]){char *p = str;SqStack1 OPTR;SqStack2 OPND;//设OPTR和OPND分别为运算符栈和运算数栈InitStack1(OPTR); InitStack2(OPND);Push1(OPTR,'=');char x,theta,c;double a,b,k,t,m;c = *p;while( !Stack1Empty(OPTR)){if(!In(c) ){for(k = 0; !In(c) && c!='.'&&c!='='; c = * ++p)k = 10*k + c-'0';t = 0 ;if(c=='.'){c = * ++p;for(t = 0,m = 0.1 ; !In(c) ; c = * ++p){t += m * (c-'0');m *= 0.1;}}Push2(OPND,k+t);}elseswitch(Precede(GetTop1(OPTR),c)){case '<': Push1(OPTR,c); c = * ++p;break;case '=': Pop1(OPTR,x);  c = * ++p; break;case '>': Pop1(OPTR,theta);Pop2(OPND,b); Pop2(OPND,a);Push2(OPND,Operate(a,theta,b));break;}}return GetTop2(OPND);}int main(){int n;scanf("%d",&n);while(n--){char str[1002];scanf("%s",str);printf("%.2lf\n",EvaluateExpression(str));}return 0;}        


二。后缀表达式求值。

首先我们需要先将中缀表达式转化为后缀表达式,
看下面这道题

中缀式变后缀式

时间限制:1000 ms  |  内存限制:65535 KB
难度:3
描述
人们的日常习惯是把算术表达式写成中缀式,但对于机器来说更“习惯于”后缀式,关于算术表达式的中缀式和后缀式的论述一般的数据结构书都有相关内容可供参看,这里不再赘述,现在你的任务是将中缀式变为后缀式。
输入
第一行输入一个整数n,共有n组测试数据(n<10)。
每组测试数据只有一行,是一个长度不超过1000的字符串,表示这个运算式的中缀式,每个运算式都是以“=”结束。这个表达式里只包含+-*/与小括号这几种符号。其中小括号可以嵌套使用。数据保证输入的操作数中不会出现负数。
数据保证除数不会为0
输出
每组都输出该组中缀式相应的后缀式,要求相邻的操作数操作符用空格隔开。
样例输入
21.000+2/4=((1+2)*5+1)/4=
样例输出
1.000 2 4 / + =1 2 + 5 * 1 + 4 / =

规则:
中缀表达式a + b*c + (d * e + f) * g,其转换成后缀表达式则为a b c * + d e * f  + g * +。

转换过程需要用到栈,具体过程如下:

1)如果遇到操作数,我们就直接将其输出。

2)如果遇到操作符,则我们将其放入到栈中,遇到左括号时我们也将其放入栈中。

3)如果遇到一个右括号,则将栈元素弹出,将弹出的操作符输出直到遇到左括号为止。注意,左括号只弹出并不输出。

4)如果遇到任何其他的操作符,如(“+”, “*”,“(”)等,从栈中弹出元素直到遇到发现更低优先级的元素(或者栈为空)为止。弹出完这些元素后,才将遇到的操作符压入到栈中。有一点需要注意,只有在遇到" ) "的情况下我们才弹出" ( ",其他情况我们都不会弹出" ( "。也就是说这种操作," + "的优先级最低," ( "优先级最高。

5)如果我们读到了输入的末尾,则将栈中所有元素依次弹出。

 

2)实例

规则很多,还是用实例比较容易说清楚整个过程。以上面的转换为例,输入为a + b * c + (d * e + f)*g,处理过程如下:

1)首先读到a,直接输出。

2)读到“+”,将其放入到栈中。

3)读到b,直接输出。

此时栈和输出的情况如下:

 

4)读到“*”,因为栈顶元素"+"优先级比" * " 低,所以将" * "直接压入栈中。

5)读到c,直接输出。

此时栈和输出情况如下:

 

6)读到" + ",因为栈顶元素" * "的优先级比它高,所以弹出" * "并输出, 同理,栈中下一个元素" + "优先级与读到的操作符" + "一样,所以也要弹出并输出。然后再将读到的" + "压入栈中。

此时栈和输出情况如下:

 

7)下一个读到的为"(",它优先级最高,所以直接放入到栈中。

8)读到d,将其直接输出。

此时栈和输出情况如下:

 

9)读到" * ",由于只有遇到" ) "的时候左括号"("才会弹出,所以" * "直接压入栈中。

10)读到e,直接输出。

此时栈和输出情况如下:

 

11)读到" + ",弹出" * "并输出,然后将"+"压入栈中。

12)读到f,直接输出。

此时栈和输出情况:

 

 

13)接下来读到“)”,则直接将栈中元素弹出并输出直到遇到"("为止。这里右括号前只有一个操作符"+"被弹出并输出。

 

14)读到" * ",压入栈中。读到g,直接输出。

 

15)此时输入数据已经读到末尾,栈中还有两个操作符“*”和" + ",直接弹出并输出。

至此整个转换过程完成。程序实现代码后续再补充了。

 

 3)转换的另一种方法

1)先按照运算符的优先级对中缀表达式加括号,变成( ( a+(b*c) ) + ( ((d*e)+f) *g ) )

2)将运算符移到括号的后面,变成((a(bc)*)+(((de)*f)+g)*)+

3)去掉括号,得到abc*+de*f+g*+

 

计算就好说了,根据后缀表达式,遇到运算符则弹出两个操作数,计算后压栈,最后栈顶元素为计算结果


 #define TRUE 1#define FALSE 0#define STACK_INIT_SIZE 10002#include "iostream"#include "cstdlib"#include "cstdio"#include "cstring"using namespace std;typedef int Status;//Status 相当于 inttypedef struct Stack1//运算符栈{char * base;char * top;}SqStack1;//运算符栈操作void  InitStack1(SqStack1 &S)//创建一个空栈{S.base = (char *)malloc(sizeof(char) * STACK_INIT_SIZE);S.top = S.base;}Status Stack1Empty(SqStack1 S)//判断是否为空{if(S.top == S.base)return TRUE;else return FALSE;}void Push1(SqStack1 &S,char e )//插入元素e为新的栈顶元素{* S.top++ = e;}void Pop1(SqStack1 &S,char &e)//出栈{e = * --S.top ;}char GetTop1(SqStack1 S){return *(S.top-1);}void TraverseStack1(SqStack1 S)//输出当前顺序表{char * p = S.base;cout<<"运算栈中的元素为:";while( p != S.top  ){cout<<*p<<" ";p++;}cout<<endl;}Status In(char ch)//判断ch是否为运算符{if(ch == '+' || ch == '('|| ch == '-' || ch == '*' || ch == '/' || ch == ')'  || ch == '=') return TRUE;else return FALSE;}void In_to_Ne(char s[1002], char p[1002],int &l){char c,ch;Stack1 S;   InitStack1(S);int i,k, len;k = 0; len = strlen(s);for(i = 0; i< len ; ++i){c =s[i];//printf("c = %c\n",c);if( !In(c) ) { while(!In(c) ){                p[k++] = c;  c = s[++i];            }  p[k++] = ' '; }//操作数if( Stack1Empty(S) )Push1(S,c); else if( c == '(' )  Push1(S,c);else if(c == ')'  ) {while( GetTop1(S)!= '('){Pop1(S,ch);p[k++] = ch; p[k++] =' '; }Pop1(S,ch); //将(出栈}//将栈顶元素弹出并放到p else if( (c == '+' || c=='-' )&&(GetTop1(S)=='(' ) )   Push1(S,c);else if( (c == '+' || c=='-' )&&(GetTop1(S) =='+' ||GetTop1(S)=='-'||GetTop1(S)=='*' ||GetTop1(S)=='/' ) ) {  Pop1(S,ch);  p[k++] = ch; p[k++] =' ';  i = i-1;  }//将栈顶元素弹出并放到pelse if( (c == '*' || c=='/' )&&(GetTop1(S) =='+' ||GetTop1(S)=='-'|| GetTop1(S)=='(' ) )   Push1(S,c);else if( (c == '*' || c=='/' )&&(GetTop1(S) =='*' ||GetTop1(S)=='/') )   {  Pop1(S,ch);  p[k++] = ch; p[k++] =' '; i = i-1;  }//将栈顶元素弹出并放到p//TraverseStack1(S);}while(!Stack1Empty(S) ){Pop1(S,ch);  p[k++] = ch; p[k++] =' ';}l = k;return ;}/*21.000+2/4=((1+2)*5+1)/4=*/int main(){char s[1002], p[1002];int n,i,len;scanf("%d",&n);while(n--){scanf("%s",s);In_to_Ne( s, p,len);for(i =0; i<len; ++i)printf("%c",p[i]);printf("=\n");}return 0;}           



然后看后缀表达式求值:从左向右依次读取,遇到操作数入栈,操作符则出栈两个元素结果入栈,直到读完,输出站定元素值,这里很简单

郁闷的C小加(二)

时间限制:1000 ms  |  内存限制:65535 KB
难度:4
描述

聪明的你帮助C小加解决了中缀表达式到后缀表达式的转换(详情请参考“郁闷的C小加(一)”),C小加很高兴。但C小加是个爱思考的人,他又想通过这种方法计算一个表达式的值。即先把表达式转换为后缀表达式,再求值。这时又要考虑操作数是小数和多位数的情况。

输入
第一行输入一个整数T,共有T组测试数据(T<10)。
每组测试数据只有一行,是一个长度不超过1000的字符串,表示这个运算式,每个运算式都是以“=”结束。这个表达式里只包含+-*/与小括号这几种符号。其中小括号可以嵌套使用。数据保证输入的操作数中不会出现负数并且小于1000000。
数据保证除数不会为0。
输出
对于每组测试数据输出结果包括两行,先输出转换后的后缀表达式,再输出计算结果,结果保留两位小数。两组测试数据之间用一个空行隔开。
样例输入
21+2=(19+21)*3-4/5=
样例输出
12+=3.001921+3*45/-=119.20
 #include <iostream>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <algorithm>#include <iomanip>#include <stack>using namespace std; int In(char e){if(e=='(' || e==')' || e=='+' || e=='-' || e=='*' || e=='/') return 1;else return 0;}int cmp(char c){switch(c){case '+':case '-': return 1;case '*':case '/': return 2;default : return 0;}}double calculate(char op,double k1,double k2){double k;switch (op){case '+': k = k1+k2; break;case '-': k = k1-k2; break;case '*': k = k1*k2; break;case '/': k = k1/k2; break;}return k;}/*20(2.6)-(1.666)=1+2=(19+21)*3-4/5=*/void change(char str[]){int t,i,k,m;char e,buf[1002],op,s[1002];double kk,k1,k2;stack <char> S1;stack <double> S2;k=0;S1.push('=');for(i=0;i<strlen(str)-1;++i) //最后一位是 = {if( In(str[i])  ){switch(str[i]){case '(': S1.push(str[i]); break;case ')': //遇到右括号, while(S1.top()!='(')//弹出栈元素输出 直到 (,{s[k++]=S1.top();k1 = S2.top(); S2.pop();k2 = S2.top(); S2.pop();S2.push(calculate(S1.top(),k2,k1));S1.pop();}if(S1.top()=='(')//弹出(不输出{S1.pop();} break;default :while(cmp(str[i])<=cmp(S1.top())){s[k++]=S1.top();k1 = S2.top(); S2.pop();k2 = S2.top(); S2.pop();S2.push(calculate(S1.top(),k2,k1));S1.pop();}S1.push(str[i]);break;}//switch}//ifelse{sscanf(str+i,"%[^=()+*/-]",buf); //操作数 for(m=0;m<strlen(buf);m++) s[k++]=buf[m];//printf("%s",buf);i += strlen(buf)-1;kk = atof(buf);//printf("k = %lf\n",k);S2.push(kk);}}//forwhile(!S1.empty()){if(S1.top()!='(' && S1.top()!='='){s[k++]=S1.top();k1 = S2.top(); S2.pop();k2 = S2.top(); S2.pop();S2.push(calculate(S1.top(),k2,k1));}S1.pop();}for(i=0;i<k;i++) printf("%c",s[i]);printf("=\n");printf("%.2lf\n",S2.top());} int main(){int t,i;char e,str[1002],buf[1002],op;double k,k1,k2;scanf("%d",&t); getchar();while(t--){gets(str); //puts(str);change(str);if(t>0) printf("\n");}return 0;}        


三。前缀表达式(波兰表达式)

2694:逆波兰表达式

  • Submit
  • Statistics
  • Hint
  • Clarify
总Time Limit: 
1000ms 
Memory Limit: 
65536kB
Description
逆波兰表达式是一种把运算符前置的算术表达式,例如普通的表达式2 + 3的逆波兰表示法为+ 2 3。逆波兰表达式的优点是运算符之间不必有优先级关系,也不必用括号改变运算次序,例如(2 + 3) * 4的逆波兰表示法为* + 2 3 4。本题求解逆波兰表达式的值,其中运算符包括+ - * /四个。
Input
输入为一行,其中运算符和运算数之间都用空格分隔,运算数是浮点数。
Output
输出为一行,表达式的值。
可直接用printf("%f\n", v)输出表达式的值v。
Sample Input
* + 11.0 12.0 + 24.0 35.0
Sample Output
1357.000000
Hint
可使用atof(str)把字符串转换为一个double类型的浮点数。atof定义在math.h中。
此题可使用函数递归调用的方法求解。





























求值方法

对于一个前缀表达式的求值而言,首先要从右至左扫描表达式,从右边第一个字符开始判断,如果当前字符是数字则一直到数字串的末尾再记录下来,如果是运算符,则将右边离得最近的两个“数字串”作相应的运算,以此作为一个新的“数字串”并记录下来。一直扫描到表达式的最左端时,最后运算的值也就是表达式的值。例如,前缀表达式“- 1 + 2 3“的求值,扫描到3时,记录下这个数字串,扫描到2时,记录下这个数字串,当扫描到+时,将+右移做相邻两数字串的运算符,记为2+3,结果为5,记录下这个新数字串,并继续向左扫描,扫描到1时,记录下这个数字串,扫描到-时,将-右移做相邻两数字串的运算符,记为1-5,结果为-4,所以表达式的值为-4。

原始代码:
#include <stack>#include <math.h>#include <iostream>#include <string.h>#include <stdio.h>#include <stdlib.h>using namespace std;void fan(char s[]){char buf[10001];int i,j=0;for(i=strlen(s)-1;i>=0;i--)buf[j++]=s[i];buf[j]='\0';for(i=0;i<strlen(s);i++)s[i]=buf[i];}stack <double> s1;bool In(char c){if(c=='-' || c=='+' || c=='*' || c=='/') return true;return false;}double calc(double k2,double k1, char op){switch( op ){case '+': return k2+k1; case '-': return k2-k1;case '*': return k2*k1;case '/': return k2/k1;}}double solve(char s[]){char buf[10001],op;double k1,k2;int i,num_shu=0;//printf("%s\n",s);for(i=strlen(s)-1;i>=0;i--){//printf("c=%c\n",s[i]);if(s[i]==' ') continue;if(In( s[i]) ){k1=s1.top(); s1.pop();k2=s1.top(); s1.pop();//printf("k1=%f k2=%f,calc=%f\n",k1,k2,calc(k2,k1,op));s1.push(calc(k1,k2,s[i]));}//caozuofuelse //caozuoshu{int j=0;memset(buf,0,sizeof(buf));while(s[i]!=' ') {buf[j]=s[i]; j++;i--;}//strrev(buf);fan(buf);//printf("buf=%s\n",buf);k1 = atof(buf);//i+= strlen(buf)-1;//printf("shu=%lf\n",k1);s1.push(k1);//cin>>i;}}//while(!s2.empty())//{//k1=s1.top(); s1.pop();//k2=s1.top(); s1.pop();//op = s2.top(); s2.pop();////printf("k1=%f k2=%f,calc=%f\n",k1,k2,calc(k2,k1,op));//s1.push(calc(k2,k1,op)); //num_shu = 1;//}return s1.top();}//     * + 11.0 12.0 + 24.0 35.0int main(){char s[10001];gets(s);printf("%f\n",solve(s));return 0;}

简单代码:
#include <iostream>#include <stdlib.h>#include <string>#include <cstdio>using namespace std;double recur(){    char s1[10];    scanf("%s",s1);    switch(s1[0])    {        case '+':        return recur()+recur();        case '-':        return recur()-recur();        case '*':        return recur()*recur();        case '/':        return recur()/recur();        default : return atof(s1);    }    return 0;}int main(){    //freopen("in.txt","r",stdin);    double res=0;    res=recur();    printf("%lf\n",res);    return 0;}


原创粉丝点击