吴昊品命令行解释程序 Round 2 —— 一个带括号的四则运算表达式的解释器(逆波兰式RPN)
如图所示,此为两个基于android的计算器。简易的计算器是不带括号的,你在输入的时候要考虑到运算的优先级,而稍微复杂一些的计算器是带括号的,在 优先级上面的考虑更为细致。两者的理论皆为逆波兰式,我们先说简易版的计算器,然后再升华为附带括号的优先级考虑更细致的四则计算器。
逆波兰式是神马
逆波兰式(Reverse Polish notation,RPN,或逆波兰记法),也叫后缀表达式(将运算符写在操作数之后,当然,也存在前缀表达式和中缀表达式)
逆波兰式的作用
对于实现逆波兰式算法,难度并不大,但为什么要将看似简单的中序表达式转换为复杂的逆波兰式?原因就在于这个简单是相对人类的思维结构来说的,对计算机而言中序表达式是非常复杂的结构。相对的,逆波兰式在计算机看来却是比较简单易懂的结构。因为计算机普遍采用的内存结构是栈式结构,它执行先进后出的顺序。
逆波兰式的实现
理论永远是纠结的,附上一张图,便于理解……
首先需要分配2个栈,一个作为临时存储运算符的栈S1(含一个结束符号),一个作为输入逆波兰式的栈S2(空栈),S1栈可先放入优先级最低的运算符#,注意,中缀式应以此最低优先级的运算符结束。可指定其他字符,不一定非#不可。从中缀式的左端开始取字符,逐序进行如下步骤:
(1)若取出的字符是操作数,则分析出完整的运算数,该操作数直接送入S2栈
(2)若取出的字符是运算符,则将该运算符与S1栈栈顶元素比较,如果该运算符优先级大于S1栈栈顶运算符优先级,则将该运算符进S1栈,否则,将S1栈的栈顶运算符弹出,送入S2栈中,直至S1栈栈顶运算符低于(不包括等于)该运算符优先级,则将该运算符送入S1栈。
(3)若取出的字符是“(”,则直接送入S1栈栈顶。
(4)若取出的字符是“)”,则将距离S1栈栈顶最近的“(”之间的运算符,逐个出栈,依次送入S2栈,此时抛弃“(”。
(5)重复上面的1~4步,直至处理完所有的输入字符
(6)若取出的字符是“#”,则将S1栈内所有运算符(不包括“#”),逐个出栈,依次送入S2栈。
完成以上步骤,S2栈便为逆波兰式输出结果。不过S2应做一下逆序处理。便可以按照逆波兰式的计算方法计算了!
简易版(不带括号)
一个不带括号的,数值都为非负整数的四则运算表达式的解释器(输出保留两位数):
Source: HDOJ 1237
Input:
//这里格式很严格,整数和运算符之间必须有空格
1 + 2
4 + 2 * 5 - 7 / 11
Output:
3.00
13.36
Solve:
利用cal函数进行四则运算,compare函数来判定优先级的高低,用一个字符串数组读入整个一行表达式,再将其分别用两个数组装填,一个装操作数,一个装操作符。
2 #include<string.h>
3
4 //这里假设读入的表达式的字符数不会超过200左右的级别(当然,如果超过的话也不是一般的计算器了)
5 #define Max 205
6
7 //加减乘除四则运算,这里不考虑b不等于0的异常
8 double cal(double a,double b,char c)
9 {
10 switch(c)
11 {
12 case '+' : return a+b;
13 case '-' : return a-b;
14 case '*' : return a*b;
15 case '/' : return a/b;
16 }
17 }
18
19 //这里用compare函数来判断优先级,如果<的话表示前者比后者的优先级低,以此类推
20 char compare(char a,char b)
21 {
22 if(a=='+'||a=='-')
23 {
24 if(b=='+'||b=='-') return '=';
25 if(b=='*'||b=='/') return '<';
26 }
27 else
28 {
29 if(b=='+'||b=='-') return '>';
30 if(b=='*'||b=='/') return '=';
31 }
32 }
33
34 int main()
35 {
36 //分别存贮表达式(字符串),操作符和数值
37 char str[Max];
38 char oper[Max];
39 double num[Max];
40 //变量v作为十进制的转换
41 double v;
42 //这里用了一个寄存器存储i,利用这种方式可以得到较快的速率,但问题是寄存器毕竟有限,要慎用
43 register int i;
44 int len,b,a;
45 //每次读入完整的一行
46 while(gets(str))
47 {
48 //如果遇到一行只有一个'0'的情况,则表示退出
49 if(str[0]=='0'&&strlen(str)==1)
50 break;
51 len=strlen(str);
52 //赋予初始值,a,b分别是数值和运算符的装入下标
53 a=b=0;
54 for(i=0;i<len;i++)
55 {
56 if(str[i]>='0')
57 {
58 v=0;
59 //如果还没有读到结束的话,空格符的ASC码也是大于'0'的
60 while(str[i]!=' '&&i!=len)
61 {
62 v=v*10+(str[i]-'0');
63 i++;
64 }
65 //又见这种方式,num[a++],很优美
66 num[a++]=v;
67 continue;
68 }
69 else
70 {
71 //要保证已经读入了一个运算符
72 if(b!=0)
73 {
74 //在进行总运算之间就把优先级比较高的运算处理了
75 while((compare(str[i],oper[b-1])!='>')&&(b>=1)&&(i!=len))
76 {
77 //处理一次运算
78 num[a-2]=cal(num[a-2],num[a-1],oper[b-1]);
79 //数值和运算符的个数都减少一个
80 b--;
81 a--;
82 }
83 oper[b++]=str[i++];
84 continue;
85 }
86 else
87 {
88 //读入第一个运算符,每次不忘i++
89 oper[b++]=str[i];
90 i++;
91 }
92 continue;
93 }
94 }
95 //剩下的低优先级的运算可以在这里就地处理
96 while(b&&b>=1)
97 {
98 num[a-2]=cal(num[a-2],num[a-1],oper[b-1]);
99 b--;
100 a--;
101 }
102 printf("%.2lf\n",num[0]);
103 }
104 return 0;
105 }
106
复杂版(带括号)
这里运用token数组存贮整个字符串的数组,利用n作为字符串数组的下标,利用match函数作为符号的匹配,报告异常。括号的优先级最高,其次是乘 除,最后是加减。这里对于除法,考虑到了除数不等于0的异常,甚至对于浮点数的算术也考虑到了,通过atof函数将字符串转换为浮点数。
2 #include<ctype.h>
3 #include<stdlib.h>
4
5 char token[61]; /*存放表达式字符串的数组*/
6 int n=0;
7
8 void error(void) /*报告错误函数*/
9 {
10 printf("ERROR!\n");
11 exit(1);
12 }
13
14 void match(char expected) /*检查字符匹配的函数*/
15 {
16 if(token[n]==expected)
17 token[++n]=getchar();
18 else error();
19 }
20
21 double term(void); /*计算乘除的函数*/
22 double factor(void); /*处理括号和数字的函数*/
23
24 double exp(void) /*计算加减的函数*/
25 {
26 double temp=term();
27 while((token[n]=='+')||(token[n]=='-'))
28 {
29 switch(token[n])
30 {
31 case'+':
32 match('+');
33 temp+=term();
34 break;
35 case'-':
36 match('-');
37 temp-=term();
38 break;
39 }
40 }
41 return temp;
42 }
43
44 double term(void)
45 {
46 double div;
47 double temp=factor();
48 while((token[n]=='*')||(token[n]=='/'))
49 {
50 switch(token[n])
51 {
52 case'*':
53 match('*');
54 temp*=factor();
55 break;
56 case'/':
57 match('/');
58 div=factor();
59 if(div==0) /*处理除数为零的情况*/
60 {
61 printf("The divisor is zero!\n");
62 exit(1);
63 }
64 temp/=div;
65 break;
66 }
67 }
68 return temp;
69 }
70
71 double factor(void)
72 {
73 double temp;
74 char number[61];
75 int i=0;
76 if(token[n]=='(')
77 {
78 match('(');
79 temp=exp();
80 match(')');
81 }
82 else if(isdigit(token[n])||token[n]=='.')
83 {
84 while(isdigit(token[n])||token[n]=='.') /*将字符串转换为浮点数*/
85 {
86 number[i++]=token[n++];
87 token[n]=getchar();
88 }
89 number='\0';
90 temp=atof(number);
91 }
92 else error();
93 return temp;
94 }
95
96 int main()
97 {
98 double result;
99 FILE *data=fopen("61590_4.dat","at");
100 if(data==NULL)
101 data=fopen("61590_4.dat","wt");
102 if(data==NULL)
103 return 0;
104 token[n]=getchar();
105 result=exp();
106 if(token[n]=='\n')
107 {
108 token[n]='\0';
109 printf("%s=%g\n",token,result);
110 fprintf(data,"%s=%g\n",token,result);
111 }
112 else error();
113 fclose(data);
114 return 0;
115 //这里主要起到一个暂停界面的作用
116 getch();
117 }