NYOJ-35-表示式求值
描述
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
題目比較容易理解,就是實現四則運算。
說到四則運算,不得不提到逆波蘭表示法,也就是字尾表示法。這種表示法不需要括號,對於9 + (3 - 1) x 3 + 10 ÷ 2用字尾表示法的樣子則是:9 3 1 - 3 * + 10 2 / +。之所以叫字尾的原因是,所有的運算子號都是要在運算數字的後邊出現,那麼很容易的想到,我們平時寫的式子,也就是第一個式子,是中綴表示法。
中綴表示式轉字尾表示式規則:從左到右遍歷中綴表示式的每個數字和符號,若是數字就輸出,即成為字尾表示式的一部分;若是符號,則判斷其與棧頂符號的優先順序,是右括號或者優先順序低於棧頂符號(乘除優先於加減)則棧頂元素依次出棧並輸出,並將當前符號進棧,一直到最終輸出字尾表示式為止,這裡我們需要一個棧來實現,用於符號的進棧出棧。
9 + (3 - 1) x 3 + 10 ÷ 2(中綴表示式) >>> 9 3 1 - 3 * + 10 2 / +(字尾表示式)
字尾表示式使用規則:從左到右遍歷字尾表示式的每一個數字和符號,遇到是數字就進棧,遇到是符號,就當處於棧頂的兩個數字出棧進行運算,運算結果進棧,一直到最終獲得結果。
這裡存在兩個過程,
1.將中綴表示式轉化為字尾表示式(棧用來進出運算子號)。
2.將字尾表示式進行運算得到結果(棧用來進出運算的數字)。
程式碼如下:
/*
不知道為啥一直WA,可是我試了很多資料都可以的,哎,頭疼死了。暫且記下,來日再戰,我需要靜靜。
後邊的兩個程式碼均是AC程式碼。
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define INF 10000000
#define ADD INF + 1 //+
#define SUB INF + 2 //-
#define MUL INF + 3 //x
#define DIV INF + 4 //÷
char str[1005]; //原公式
int len; //公式長度
char symbol[1000]; //運算子號棧
float suffix[1005]; //字尾式
float answer[1000]; //運算棧
//從s[*pc]開始獲取一個浮點數
int StrToInt(char s[], int * pc, float *pout)
{
char buf[100];
int i = 0;
if(s[*pc]<'0' || s[*pc]>'9')
return 1;
else
{
while((s[*pc] >= '0' && s[*pc] <= '9') || s[*pc] == '.')
{
buf[i] = s[*pc];
(*pc)++;
i++;
}
buf[i] = '\0';
*pout = (float)atof(buf);
return 0;
}
}
void swi(int *key, char sym)
{
switch (sym)
{
case '+':
suffix[(*key)++] = ADD;
break;
case '-':
suffix[(*key)++] = SUB;
break;
case '*':
suffix[(*key)++] = MUL;
break;
case '/':
suffix[(*key)++] = DIV;
break;
}
}
void perform(int suf, int *_key)
{
float a = answer[(*_key)--];
float b = answer[(*_key)];
float c;
switch (suf)
{
case (INF + 1):
c = a + b;
break;
case (INF + 2):
c = b - a;
break;
case (INF + 3):
c = a * b;
break;
default:
c = b / a;
break;
}
answer[*_key] = c;
// printf("%.2f\n", answer[*_key]);
return ;
}
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
scanf("%s", str);
len = (int)strlen(str) - 1;
int key = 0;
int top = -1;
float pout;
//中綴式轉字尾式
for (int i = 0; i < len; )
{
if (!StrToInt(str, &i, &pout))
{
suffix[key++] = pout;
}
else if (top != -1 && str[i] == ')')
{
while (symbol[top] != '(')
{
swi(&key, symbol[top--]);
}
i++;
top--;
}
else if (top != -1 && (str[i] == '+' || str[i] == '-') && (symbol[top] == '*' || symbol[top] == '/'))
{
while (symbol[top] != '(' && top >= 0)
{
swi(&key, symbol[top--]);
}
symbol[++top] = str[i++];
}
else if (top != -1 && (str[i] == '*' || str[i] == '/') && (symbol[top] == '*' || symbol[top] == '/'))
{
swi(&key, symbol[top]);
symbol[top] = str[i++];
}
else
{
symbol[++top] = str[i++];
}
}
while (top >= 0)
{
swi(&key, symbol[top--]);
}
// for (int i = 0; i < key; i++)
// {
// printf("%f ", suffix[i]);
// }
// printf("\n");
//字尾式運算
int _key = -1;
for (int i = 0; i < key; i++)
{
if (suffix[i] < INF)
{
answer[++_key] = suffix[i];
// printf("%.2f\n", answer[_key]);
}
else
{
perform((int)suffix[i], &_key);
}
}
printf("%.2f\n", answer[0]);
}
return 0;
}
另外還有兩種方法,第一種是將字元和資料分別入兩個棧,然後根據優先順序的比較,對資料棧頂的兩個元素進行出棧操作然後進棧。
#include<stdio.h>
#include<stdlib.h>
//資料棧
typedef struct DA
{
float data[1000];
int pop;
} SDA;
//運算子棧
typedef struct OP
{
char op[1000];
int pop;
} SOP;
//初始化資料棧
int InitSDA(SDA * p)
{
p->pop = 0;
return 0;
}
//初始化運算子棧
int InitSOP(SOP * p)
{
p->pop = 0;
(p->op[p->pop]) = '=';
(p->pop)++;
return 0;
}
//資料入棧
int PushSDA(SDA * p, float d)
{
if(p->pop < 1000)
{
p->data[p->pop] = d;
(p->pop)++;
return 0;
}
else
return 1; //棧滿
}
//運算子入棧
int PushSOP(SOP * p, char c)
{
if(p->pop < 1000)
{
p->op[p->pop] = c;
(p->pop)++;
return 0;
}
else
return 1; //棧滿
}
//資料出棧
int PopSDA(SDA * p, float * d)
{
(p->pop)--;
if(p->pop >= 0)
{
*d = p->data[p->pop];
return 0;
}
else
return 1;
}
//運算子出棧
int PopSOP(SOP * p, char * c)
{
(p->pop)--;
if(p->pop >= 0)
{
*c = p->op[p->pop];
return 0;
}
else
return 1;
}
//從s[*pc]開始獲取一個浮點數
int StrToInt(char s[], int * pc, float *pout)
{
char buf[100];
int i = 0;
if(s[*pc]<'0' || s[*pc]>'9')
return 1;
else
{
while((s[*pc] >= '0' && s[*pc] <= '9') || s[*pc] == '.')
{
buf[i] = s[*pc];
(*pc)++;
i++;
}
buf[i] = '\0';
*pout = (float)atof(buf);
return 0;
}
}
//從s[*pc]獲取一個char
int StrToChar(char s[], int *pc, char *pout)
{
if('+'==s[*pc] || '-'==s[*pc] || '*'==s[*pc] || '/'==s[*pc] || '('==s[*pc] || ')'==s[*pc])
{
*pout = s[*pc];
(*pc)++;
return 0;
}
else
return 1;
}
//獲取優先順序
char GetPri(char c1, char c2)
{
char f[7][7] = {'>', '>', '<', '<', '<', '>', '>',
'>', '>', '<', '<', '<', '>', '>',
'>', '>', '>', '>', '<', '>', '>',
'>', '>', '>', '>', '<', '>', '>',
'<', '<', '<', '<', '<', '=', '\0',
'>', '>', '>', '>', '\0', '>', '>',
'<', '<', '<', '<', '<', '\0', '=',};
int i=0, j=0;
switch(c1)
{
case '+': i = 0; break;
case '-': i = 1; break;
case '*': i = 2; break;
case '/': i = 3; break;
case '(': i = 4; break;
case ')': i = 5; break;
case '=': i = 6; break;
}
switch(c2)
{
case '+': j = 0; break;
case '-': j = 1; break;
case '*': j = 2; break;
case '/': j = 3; break;
case '(': j = 4; break;
case ')': j = 5; break;
case '=': j = 6; break;
}
return f[i][j];
}
//計算表示式
float Operate(float a, char op, float b)
{
switch(op)
{
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/': return a / b;
default: return 0;
}
}
int main(void)
{
char s[10][1000];
int c = 0;
float bufda;
char bufop;
float a, b;
SDA sda;
SOP sop;
int n;
int i;
scanf("%d", &n);
for(i = 0; i < n; i++)
scanf("%s", s[i]);
for(i = 0; i < n; i++)
{
c = 0;
InitSDA(&sda); //初始化資料棧
InitSOP(&sop); //初始化符號棧
while(s[i][c] != '=' || sop.op[sop.pop - 1] != '=') // 計算未完成
{
if(0 == StrToInt(s[i], &c, &bufda))
PushSDA(&sda, bufda); //資料入棧
else
{
switch(GetPri(sop.op[sop.pop - 1], s[i][c]))
{
case '<':
if(0 == StrToChar(s[i], &c, &bufop))
PushSOP(&sop, bufop);
break;
case '=':
PopSOP(&sop, &bufop);
c++;
break;
case '>':
PopSOP(&sop, &bufop);
PopSDA(&sda, &b);
PopSDA(&sda, &a);
PushSDA(&sda, Operate(a, bufop, b));
break;
}
}
}
PopSDA(&sda, &a);
printf("%.2f\n", a);
}
return 0;
}
//改寫成C++,並簡化後如此
//#include <stack>
//#include <stdio.h>
//#include <ctype.h>
//#include <string.h>
//#include <stdlib.h>
//
//using namespace std;
//
//int priority(char c)
//{
// if(c == '=') return 0;
// if(c == '+') return 1;
// if(c == '-') return 1;
// if(c == '*') return 2;
// if(c == '/') return 2;
// return 0;
//}
//
//void compute(stack<double>& Num,stack<char>& Op)
//{
// double b = Num.top();
// Num.pop();
// double a = Num.top();
// Num.pop();
// switch(Op.top())
// {
// case '+':Num.push(a+b);break;
// case '-':Num.push(a-b);break;
// case '*':Num.push(a*b);break;
// case '/':Num.push(a/b);break;
// }
// Op.pop();
//}
//
//int main()
//{
// int z;
// char str[1005];
// stack<double> Num;
// stack<char> Op;
// scanf("%d",&z);
// while(z--)
// {
// scanf("%s",str);
// int len = strlen(str);
// for(int i=0;i<len;i++)
// {
// if(isdigit(str[i]))
// {
// double n = atof(&str[i]);
// while(i<len && (isdigit(str[i]) || str[i]=='.'))
// i++;
// i--;
// Num.push(n);
// }
// else
// {
// if(str[i] == '(')
// Op.push(str[i]);
// else if(str[i] == ')')
// {
// while(Op.top()!='(')
// compute(Num,Op);
// Op.pop();
// }
// else if(Op.empty() || priority(str[i])>priority(Op.top()))
// Op.push(str[i]);
// else
// {
// while(!Op.empty() && priority(str[i])<=priority(Op.top()))
// compute(Num,Op);
// Op.push(str[i]);
// }
// }
// }
// Op.pop();
// printf("%.2f\n",Num.top());
// Num.pop();
// }
// return 0;
//}
第二種是動態規劃,將一個大問題切割成些許小問題,和歸併排序的思想相仿。
// AC(動態規劃)
#include<stdio.h>
#include<string.h>
int len;
int fst[1005];
char str[1005];
double Jud(int begin, int end); /*計算並返回表示式在區間[begin end]中的值*/
int main()
{
int T, i;
double ans;
scanf("%d", &T);
while(T--)
{
memset(fst, 0, sizeof(fst)); /*一定要清0*/
scanf("%s", str);
len = (int)strlen(str)-1;
fst[0] = 1;
for(i = 1; i <= len - 1; i++) /*fst[i]表示優先順序,fst[i]越大,說明優先順序越高↓↓*/
{ /*例如str[] -- ((1+2)*5+1)/4=*/
if(str[i - 1]== '(') /*對應fst[] -- 12333222221110*/
fst[i] = fst[i - 1] + 1;
else if(str[i] == ')')
fst[i] = fst[i - 1] - 1;
else
fst[i] = fst[i - 1];
}
ans = Jud(0, len - 1); /*傳入整個表示式,不包括=*/
printf("%.2f\n", ans);
}
return 0;
}
double Jud(int begin, int end)
{ /*規定區間[begin, end]的優先順序標準為fst[begin]*/
int i;
double k;
for(i = begin; i <= end; i++) /*先從做左到右找到第一個處於指定優先順序的'+'運算子*/
{
if(str[i]== '+' && fst[i] == fst[begin])
{
k = Jud(begin, i - 1) + Jud(i + 1, end); /*將其拆成兩個個表示式的和*/
return k;
}
}
for(i = end; i >= begin; i--) /*如果找不到'+',再從右往左找到第一個處於指定優先順序的'-'運算子*/
{
if(str[i]=='-' && fst[i] == fst[begin])
{
k = Jud(begin, i - 1) - Jud(i + 1, end); /*將其拆成兩個個表示式的差*/
return k;
}
}
for(i = begin; i <= end; i++) /*如果還找不到,再從左往右找到第一個處於指定優先順序的'*'運算子*/
{
if(str[i] == '*' && fst[i] == fst[begin])
{
k = Jud(begin, i - 1) * Jud(i + 1, end); /*將其拆成兩個個表示式的積*/
return k;
}
}
for(i = end; i >= begin; i--) /*同上,從右往左找到第一個處於指定優先順序的'/'運算子*/
{
if(str[i] == '/' && fst[i] == fst[begin])
{
k = Jud(begin, i - 1) / Jud(i + 1, end); /*將其拆成兩個個表示式的商*/
return k;
}
}
if(str[begin]=='(') /*如果在這個[begin,end]區間裡的指定優先順序中沒有任何運算子,說明此區間可能完全包含上一級*/
{
for(i = begin + 1; fst[i] >= fst[begin + 1]; i++);
k = Jud(begin + 1, i - 1);
}
else /*既然沒有包含上一級,說明這個區間就只剩下一個數啦*/
{
char *p = str;
sscanf(p+begin, "%lf", &k); /*將這個數賦值給k,並返回*/
}
return k;
}
理論上這三種均可以AC…可是我不知道第一種哪裡出了BUG,等回頭有空了再看吧…就這樣吧。