逆波兰表达式——中缀表达式转后缀表达式
逆波兰表达式
先说一下中缀表达式,平时我们使用的运算表达式就是中缀表达式,例如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 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术