逆波兰表达式
先说一下中缀表达式,平时我们使用的运算表达式就是中缀表达式,例如1+3*2,中缀表达式的特点就是:二元运算符总是置于与之相关的两个运算对象之间
人读起来比较好理解,但是计算机处理起来就很麻烦,运算顺序往往因表达式的内容而定,不具规律性
后缀表达式,后缀表达式的特点就是:每一运算符都置于其运算对象之后,以上面的中缀表达式1+2*3为例子,转为后缀表达式就是123*+
下面先分析怎么把中缀表达式转换为后缀表达式,这里我们考虑六种操作符'+'、'-'、'*'、'/'、'('、')',完成中缀转后缀我们需要两个数组,都以栈的方式来操作,一个数组用来存放后缀表达式(char num[100]),
一个数组用来临时存放操作数(char opera[100])(这里说临时存放,是因为最后都要入栈到后缀表达式数组num中,这个数组就相当于一个中转站)
1、从左往右扫描中缀表达式(这里我们以1*(2+3)为例)
2、如果是数字那么将其直接入栈到数组num中
3、如果是操作数,需要进一步判断
(1)如果是左括号'('直接入栈到数组opera中
(2)如果是运算符('+'、'-'、'*'、'/'),先判断数组opera的栈顶的操作数的优先级(如果是空栈那么直接入栈到数组opera),如果是左括号那么直接入栈到数组opera中,如果栈顶是运算符,且栈顶运算符的优先级大于该运算符
那么将栈顶的运算符出栈,并入栈到数组num中,重复步骤3,如果栈顶运算符优先级小于该运算符,那么直接将该运算符入栈到opera中
(3)如果是右括号')',那么说明在opera数组中一定有一个左括号与之对应(在你没输错的情况下),那么将opera中的运算符依次出栈,并入栈到num中,直到遇到左括号'('(注意左括号不用入栈到num)
4、如果中缀表达式扫描完了,那么将opera中的操作数依次出栈,并入栈到num中就可以了,如果没有没有扫描完重复1-3步
上面就是中缀表达式转后缀表达式的步骤了,下面用图来直观的了解一下这个过程
需要注意的是:opera中操作数,越靠近栈顶,优先级越高,下面附上实现代码


1 void PexpretoSexpre(char *ss)
2 {
3 char num[100] = "0"; /* 存储后缀表达式 */
4 char opera[100] = "0"; /* 存储运算符 */
5 /*
6 num----j
7 opera----op
8 ss----i
9 */
10 int i, j, op;
11
12 op = i = j = 0;
13
14 while (ss[i] != '\0')
15 {
16 if (isdigit(ss[i])) /* 如果是数字 */
17 {
18 num[j] = ss[i]; /* 数字直接入后缀表达式栈 */
19 j++;
20 i++;
21 }
22 else
23 {
24 switch (ss[i]) /* 如果是操作数 */
25 {
26 case '+':
27 {
28 if (op == 0) /* 如果是空栈 */
29 {
30 PushOperation(opera, ss, &op, &i); /* 入运算符栈 */
31 break;
32 }
33 if (opera[op-1] == '+' || opera[op-1] == '-' || opera[op-1] == '*' || opera[op-1] == '/' || opera[op-1] == ')' || opera[op-1] == '(')
34 {
35 switch (opera[op-1])
36 {
37 case '+':
38 {
39 PushOperation(opera, ss, &op, &i);
40 break;
41 }
42 case '-':
43 {
44 PushOperation(opera, ss, &op, &i);
45 break;
46 }
47 case '*':
48 { /* 加法优先级低于乘法 */
49 num[j] = opera[op-1]; /* 将操作数出栈 */
50 opera[op-1] = ss[i]; /* 将新的操作数压入栈中 */
51 j++;
52 i++;
53 break;
54 }
55 case '/':
56 {
57 num[j] = opera[op-1];
58 opera[op-1] = ss[i];
59 j++;
60 i++;
61 break;
62 }
63 case '(':
64 {
65 PushOperation(opera, ss, &op, &i);
66 break;
67 }
68 }
69 }
70 break;
71 }
72 case '-':
73 {
74 if (op == 0)
75 {
76 PushOperation(opera, ss, &op, &i);
77 break;
78 }
79 if (opera[op-1] == '+' || opera[op-1] == '-' || opera[op-1] == '*' || opera[op-1] == '/' || opera[op-1] == ')' || opera[op-1] == '(')
80 {
81 switch (opera[op-1])
82 {
83 case '+':
84 {
85 PushOperation(opera, ss, &op, &i);
86 break;
87 }
88 case '-':
89 {
90 PushOperation(opera, ss, &op, &i);
91 break;
92 }
93 case '*':
94 {
95 num[j] = opera[op-1];
96 opera[op-1] = ss[i];
97 j++;
98 i++;
99 break;
100 }
101 case '/':
102 {
103 num[j] = opera[op-1];
104 opera[op-1] = ss[i];
105 j++;
106 i++;
107 break;
108 }
109 case '(':
110 {
111 PushOperation(opera, ss, &op, &i);
112 break;
113 }
114 }
115 }
116 break;
117 }
118 case '*':
119 {
120 if (op == 0)
121 {
122 PushOperation(opera, ss, &op, &i);
123 break;
124 }
125 if (opera[op-1] == '+' || opera[op-1] == '-' || opera[op-1] == '*' || opera[op-1] == '/' || opera[op-1] == ')' || opera[op-1] == '(')
126 {
127 switch (opera[op-1])
128 {
129 case '+':
130 {
131 PushOperation(opera, ss, &op, &i);
132 break;
133 }
134 case '-':
135 {
136 PushOperation(opera, ss, &op, &i);
137 break;
138 }
139 case '*':
140 {
141 PushOperation(opera, ss, &op, &i);
142 break;
143 }
144 case '/':
145 {
146 PushOperation(opera, ss, &op, &i);
147 break;
148 }
149 case '(':
150 {
151 PushOperation(opera, ss, &op, &i);
152 break;
153 }
154 }
155 }
156 break;
157 }
158 case '/':
159 {
160 if (op == 0)
161 {
162 PushOperation(opera, ss, &op, &i);
163 break;
164 }
165 if (opera[op-1] == '+' || opera[op-1] == '-' || opera[op-1] == '*' || opera[op-1] == '/' || opera[op-1] == ')' || opera[op-1] == '(')
166 {
167 switch (opera[op-1])
168 {
169 case '+':
170 {
171 PushOperation(opera, ss, &op, &i);
172 break;
173 }
174 case '-':
175 {
176 PushOperation(opera, ss, &op, &i);
177 break;
178 }
179 case '*':
180 {
181 PushOperation(opera, ss, &op, &i);
182 break;
183 }
184 case '/':
185 {
186 PushOperation(opera, ss, &op, &i);
187 break;
188 }
189 case '(':
190 {
191 PushOperation(opera, ss, &op, &i);
192 break;
193 }
194 }
195 }
196 break;
197 }
198 case '(':
199 {
200 PushOperation(opera, ss, &op, &i);
201 break;
202 }
203 case ')': /* 如果遇到右括号 */
204 {
205 while (opera[op-1] != '(')
206 {
207 num[j] = opera[op-1]; /* 将运算符栈中的元素依次入栈到后缀表达式栈中,直到遇到左括号为止 */
208 j++;
209 op--;
210 }
211 op--;
212 i++;
213 break;
214 }
215 default:
216 {
217 printf("传入表达式不符合要求\n");
218 exit(0);
219 }
220
221 }
222 }
223 }
224 while (op != 0)
225 {
226 num[j] = opera[op-1]; /* 将运算符栈中的元素依次入栈到后缀表达式栈中 */
227 j++;
228 op--;
229 }
230 num[j] = '\0';
231 i = 0;
232 while (num[i] != '\0') /* 将后缀表达式存储到传入的形参ss中 */
233 {
234 ss[i] = num[i];
235 i++;
236 }
237 ss[i] = '\0';
238 }
239
240 /* Function: 入运算符栈*/
241 void PushOperation(char *opera, char *ss, int *op, int *s)
242 {
243 opera[*op] = ss[*s];
244 (*op)++;
245 (*s)++;
246 }
后缀表达式的计算
完成了中缀表达式转后缀表达式,接下来就是后缀表达式的计算了,后缀表达式的计算比中缀转后缀要稍微简单一点,只需要对我们转换好的后缀表达式从左往右依次扫描,并依次入栈就行了,
意思是只需要用一个数组(double num[100])就OK了
需要考虑的情况如下
1、如果是数字,那么直接入栈到num中
2、如果是运算符,将栈顶的两个数字出栈(因为我们考虑的运算符加、减、乘、除都是双目运算符,只需要两个操作数),出栈后对两个数字进行相应的运算,并将运算结果入栈
3、直到遇到'\0'
下面用几张图,来直观了解下这个过程,以上面转换好的后缀表达式"123+*"为例(这里用ss来存储后缀表达式,num来存储计算结果,注意不要与上面图中num搞混淆了)
(注意:这里将计算结果5入栈后,栈顶从之前的[3]变成[2])
到这里后缀表达式的计算就结束了,下面附上实现代码


1 #include <stdio.h>
2 #include <stdlib.h>
3
4 #define MAX 100
5
6 void JudgeFopen_s(errno_t err); /* 判断文件打开是否成功 */
7 void ReadFile(FILE *fp, char *ss); /* 读取文件内容 */
8 double TransformCtoD(char ch); /* 将char类型数组的每一个元素转换为double */
9 void CalculateAndPush(double *num, int *i, int *j, char mm); /* 计算结果并入栈 */
10
11 int main()
12 {
13 FILE *fp;
14 errno_t err;
15
16 char ss[MAX]; /* 存储逆波兰表达式 */
17 int i = 0;
18 int j = 0;
19 double num[MAX]; /* 栈 */
20
21 err = fopen_s(&fp, "E:\\ww.txt", "r");
22
23 JudgeFopen_s(err); /* 判断文件打开是否成功 */
24 ReadFile(fp, ss); /* 读取文件内容,存储到ss中*/
25
26 while (ss[i] != '\0')
27 {
28 if (ss[i] >= '0' && ss[i] <= '9') /* 如果是数字 */
29 {
30 /* 因为num是char类型的,需要转换为double类型方便计算 */
31 num[j] = TransformCtoD(ss[i]); /* 将数字存储到栈中 */
32 j++;
33 i++;
34 }
35 else if (ss[i] == '+' || ss[i] == '-' || ss[i] == '*' || ss[i] == '/')
36 {
37 CalculateAndPush(num, &i, &j, ss[i]); /* 计算结果并入栈 */
38 }
39 else if (ss[i] == '\n') /* 如果是换行符,结束循环*/
40 {
41 break;
42 }
43 }
44
45 printf("%lf", num[0]);
46
47 return 0;
48 }
49
50 /* Function: 计算结果并入栈 */
51 void CalculateAndPush(double *num, int *i, int *j, char mm)
52 {
53 switch (mm)
54 {
55 case '+':
56 {
57 num[(*j)-2] = num[(*j)-1] + num[(*j)-2];
58 (*j)--;
59 (*i)++;
60 break;
61 }
62 case '-':
63 {
64 num[(*j)-2] = num[(*j)-1] - num[(*j)-2];
65 (*j)--;
66 (*i)++;
67 break;
68 }
69 case '*':
70 {
71 num[(*j)-2] = num[(*j)-1] * num[(*j)-2];
72 (*j)--;
73 (*i)++;
74 break;
75 }
76 case '/':
77 {
78 num[(*j)-2] = num[(*j)-1] / num[(*j)-2];
79 (*j)--;
80 (*i)++;
81 break;
82 }
83 default:
84 {
85 exit(0);
86 }
87 }
88 }
89 /* Function: 判断文件打开是否成功 */
90 void JudgeFopen_s(errno_t err)
91 {
92 if (err != 0)
93 {
94 printf("文件打开失败\n");
95 system("pause");
96 exit(0);
97 }
98 }
99
100 /* Function: 读取文件内容*/
101 void ReadFile(FILE *fp, char *ss)
102 {
103 int i = 0;
104
105 while (!feof(fp))
106 {
107 fscanf_s(fp, "%c", &ss[i]);
108 i++;
109 }
110 ss[i-1] = '\0';
111 }
112
113 /* Function: 将char类型数组的每一个元素转换为double */
114 double TransformCtoD(char ch)
115 {
116 return (double)(ch - '0');
117 }
来源:oschina
链接:https://my.oschina.net/u/4264749/blog/3555605