如图所示,此为两个基于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 }