算术表达式的三种表示方式
- 中缀表达式:运算符位于与其相关的操作数中间,如
(1+2)*(3-4)
- 前缀表达式(波兰式):运算符位于与其相关的操作数前面,如
* + 1 2 - 3 4
- 后缀表达式(逆波兰式):运算符位于与其相关的操作数后面,如
1 2 + 3 4 - *
表达式求值思路
基本思路:栈+线性扫描(当前操作符比栈顶的操作符优先级低,则进行一次实际的运算)
中缀表达式求值的C代码实现
思路参考邓俊辉的《数据结构C++版本》的第4章-栈与队列以及《C程序设计语言》的4.3节-外部变量((另一种思路是先将中缀表达式转换成后缀表达式,再对后缀表达式求值,具体分析可参考 详解如何将中缀表达式转化为后缀表达式)
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define MAXSIZE 100
#define OPNUMLEN 20
#define NUMBER '0' //操作数的标志
#define BUFSIZE 100
char buf[BUFSIZE];
int bufp = 0;
double stack[MAXSIZE]; //操作数栈
int sp = 0; //栈顶索引
char optrOrderMat[][8] = {">><<><>",
">><<><>",
">>>>><>",
">>>>><>",
"<<<<=<=",
"<<<<=<=",
"======="}; //操作符优先级表,顺序为+-*/'\0'()
char stackC[MAXSIZE]; //操作符栈
int spC = 0;
char getch() //从缓冲区中读取一个字符
{
return (bufp>0) ? buf[--bufp]:getchar();
}
void ungetch(char c) //将字符退回到缓冲区
{
if(bufp > BUFSIZE)
printf("ungetch: exceed buffer size");
buf[bufp++] = c;
}
int getop(char s[]); //获取一个操作对象,s保存操作数串,返回操作符
void push(double n); //栈压入
double pop(void); //栈弹出
int optr2idx(char optr); //将运算符转换成下标索引
int orderBetween(char optr1, char optr2); //比较两个运算符的优先级(限于四则运算)
double calcMidExpAndGotRPM(char RPM[]); //中缀表达式求值并将其转换成逆波兰表达式
int main()
{
//在windows的powershell中测试通过
char rpm[MAXSIZE]={'\0'};
double res = calcMidExpAndGotRPM(rpm);
printf("%f\n%s\n", res, rpm);
return 0;
}
//获取一个操作对象,s保存操作数串,返回操作符
int getop(char s[])
{
int i, c;
while((s[0]=c=getch()) == ' ' || c=='\t') //去除前导空白符
;
if(!isdigit(c) && c!='.') //.xx算一个操作数
return c; // 不是一个数,直接返回
i = 0;
if(isdigit(c)) //收集整数部分
while(isdigit(s[++i]=c=getch())) //跳出循环时s[i]保存了第一个非数字字符
;
if(c == '.') //收集小数部分
while(isdigit(s[++i]=c=getch()))
;
s[i] = '\0';
if(c != EOF)
ungetch(c); //将多读入的一个字符放回缓冲区
return NUMBER;
}
void push(double n)
{
if(sp < MAXSIZE)
stack[sp++] = n;
else
printf("error:stack full, can't push %g\n", n);
}
double pop()
{
if(sp > 0)
return stack[--sp]; //只是将栈顶指针前移了,在stack中的元素并未被删除
else
{
printf("error:stack empty!\n");
return 0.0;
}
}
void pushC(char n)
{
if(spC < MAXSIZE)
stackC[spC++] = n;
else
printf("error:stack full, can't push %g\n", n);
}
char popC()
{
if(spC > 0)
{
char c = stackC[--spC];
// stackC[spC] = '\0';
return c;
}
else
{
printf("error:stack empty!\n");
return 0.0;
}
}
int optr2idx(char optr)
{
int i;
switch(optr){
case '+':
i = 0;
break;
case '-':
i = 1;
break;
case '*':
i = 2;
break;
case '/':
i = 3;
break;
case '\0':
i = 4;
break;
case '(':
i = 5;
break;
case ')':
i = 6;
break;
default:
i = -1;
break;
}
return i;
}
//比较两个运算符的优先级(限于四则运算)
int orderBetween(char optr1, char optr2)
{
int idx1,idx2;
if((idx1=optr2idx(optr1))!=-1 && (idx2=optr2idx(optr2))!=-1)
return optrOrderMat[idx1][idx2];
printf("unsupported operator\n");
return -1;
}
//中缀表达式求值并将其转换成逆波兰表达式
double calcMidExpAndGotRPM(char RPM[])
{
double optn[MAXSIZE];
char c, op, optr[MAXSIZE];
pushC('\0'); //字符串结束符先入操作符栈
int j, i=0;
char str[OPNUMLEN];
while(spC>0)
{
c = getop(str);
if(c == NUMBER)
{
push(atof(str)); //将操作数转换为浮点数并入栈
strcat(str, " ");
strcat(RPM, str);
}
else
{
if(c == '\n') //一行字符串表达式结束时,用'\0'替换末尾的'\n'
c = '\0';
if(c == EOF) //windows下按Ctrl+Z相当于EOF
break;
switch(orderBetween(stackC[spC-1],c)){
case '<': //栈顶运算符优先级更低
pushC(c);
break;
case '=': //优先级相同(当前运算符为右括号或'\0',且栈顶为左括号或'\0')
popC();
break;
case '>': //栈顶运算符优先级更高,实施相应运算,结果入栈
op = popC();
j = strlen(RPM);
RPM[j++] = op;
RPM[j] = '\0';
strcat(RPM, " ");
double opnd2;
switch(op){
case '+':
push(pop()+pop());
break;
case '-':
opnd2 = pop(); //不能写成(pop()-pop()),因为C并没有规定'-'两边的求值顺序
push(pop()-opnd2);
break;
case '*':
push(pop()*pop());
break;
case '/':
opnd2 = pop();
if(opnd2 != 0.0)
push(pop()/opnd2);
else
printf("error:zero divisor\n");
break;
}
ungetch(c); // 执行完运算符操作后需把当前读入的操作符“反读”回去
}
}
for(i=1; i<spC; ++i)
printf("%c ", stackC[i]);
printf("\t%s\n", RPM);
}
return pop(); // 弹出最后的求值结果
}