思路
- 将通常的中缀表达式(Infix Notation)转化为后缀表达式(Postfix Notation)
- 计算后缀表达式
后缀表达式的计算
比如中缀表达式9 + (3 - 1) * 3 + 10 / 2
转化成后缀表达式为9 3 1 - 3 * + 10 2 / +
具体计算步骤为:
9 2 3 * + 10 2 / +
9 6 + 10 2 / +
15 10 2 / +
15 5 +
20
计算时,遍历后缀表达式,碰到操作符后,就取符号前面的两个数字作为操作数,计算完后再把结果放回去,如此下来,直到全部算完只剩下一个数,就是结果。后缀表达式相较于中缀表达式更适合计算机计算,不需要括号,只依靠顺序来确定运算符优先级。
如何将中缀表达式转化为后缀表达式?
A方法
从头到尾读取中缀表达式的每一个对象,对不同对象按不同的情况处理。
- 运算数:直接输出;
- 左括号:压入堆栈;
- 右括号:将栈顶的运算符弹出并输出,直到遇到左括号(出栈,不输出);
- 运算符:
- 若优先级大于栈顶运算符,则压入堆栈;
- 若优先级小于等于栈顶运算符,将栈顶运算符弹出并输出;再比较新的栈顶运算符,直到该运算符大于栈顶运算符优先级为止,然后将该运算符压栈;
- 若各对象处理完毕,则把堆栈中存留的运算符一并输出;
思考一下为什么要这么做
用计算机的视角去看待这个问题:
我:输入a
计算机:a
:a是操作数吧
(于是输出a)
我:输入+
计算机: a +
: 知道了,+号的左操作数是a
(+入栈)
我:输入b
计算机:a+b
:+号的右操作数还不能断定是b,看下一个符号的情况吧
我:输入*
计算机:a+b*
:ok,*号的左操作数是b
我:输入c
计算机:a+b*c
:*号的右操作数是c
我:输入-
计算机:a+b*c-
:b*c是-的左操作数
我:输入d,回车
计算机:a+b*c-d
,d是-的右操作数
我:怎么感觉和递归好像...
B方法
例如表达式a + b * c - (d + e)
- 按照运算符优先级对所有运算单位加括号
((a + (b * c)) - (d + e))
- 将括号内的运算符移到括号外右边
((a (b c)*) + (d e)+)-
- 去掉括号,即得到了后缀表达式
a b c * + d e + -
具体实现(C语言)
该函数返回符号对应的优先级
int getPriority(char sign)
{
switch(sign) {
case '+':
case '-': return 1;
case '/':
case '*': return 2;
case '^': return 3;
case '(': return 0;
default: return -1;
}
}
该函数将中缀表达式转化为后缀表达式,使用了链式结构的堆栈。因为要返回一个新的字符串,所以使用了static修饰符,需要注意的是就是如果第一次用temp保存返回的结果,再次调用transform函数之后,temp指向的内容也会发生变化。还有就是如果出现了'-',那么它既有可能是单目的取反符号,也有可能是双目的减号,需要判断一下。如果是单目的取反符,那么就要在它前面补上0使之成为减法运算。
char* transform(const char infix[])
{
unsigned cnt = 0;
static char ret[MAX];
Stack signs = CreateStack();
for (; *infix != ' '; ++infix) {
switch(*infix) {
case '0':case '1':case '2':
case '3':case '4':case '5':
case '6':case '7':case '8':
case '9':case '.':
ret[cnt++] = *infix;break;
case '-':
if (cnt == 0 || !isdigit(*(infix-1))) {
ret[cnt++] = '0';
}
case '+':case '*':case '/':case '^':
ret[cnt++] = ' ';
while (!StackIsEmpty(signs) && getPriority(*infix) <= getPriority(StackTop(signs)))
{
ret[cnt++] = PopStack(signs);
ret[cnt++] = ' ';
}
case '(':
PushStack(signs, *infix);break;
case ')':
ret[cnt++] = ' ';
while (StackTop(signs) != '(') {
ret[cnt++] = PopStack(signs);
ret[cnt++] = ' ';
}
PopStack(signs);break;
}
}
ret[cnt++] = ' ';
while (!StackIsEmpty(signs)) {
ret[cnt++] = PopStack(signs);
ret[cnt++] = ' ';
}
ret[cnt] = ' ';
DeleteStack(signs);
return ret;
}
该函数计算后缀表达式的值,使用了顺序结构的堆栈。先将后缀表达式按空格分割成单个的运算数或符号。再进行遍历,遇到数字便进栈,遇到符号便取出栈顶的两个元素计算后放回堆栈。遍历完之后,栈顶元素就是计算结果。
double compute(const char *postfix)
{
double stack[MAX], op1, op2;
char sequence[MAX][16];
unsigned cnt = 0;
int top = -1;
char *sq = sequence[cnt];
const char *pf = postfix;
for (; *pf; ++pf)
if (*pf != ' ') {
for (; *pf != ' '; *sq++ = *pf++);
*sq = ' ';
sq = sequence[++cnt];
}
sq = sequence[0];
for (unsigned i = 0; i < cnt; sq = sequence[++i]) {
if (isdigit(sq[0])) stack[++top] = atof(sq);
else {
op1 = stack[top-1], op2 = stack[top];
top = top - 2;
switch (sq[0]) {
case '+': stack[++top] = op1 + op2; break;
case '-': stack[++top] = op1 - op2; break;
case '*': stack[++top] = op1 * op2; break;
case '/': stack[++top] = op1 / op2; break;
case '^': stack[++top] = pow(op1, op2); break;
}
}
}
return stack[0];
}
一些小收获
在C的函数里,函数内是无法获取数组长度的,只能在传入参数是给定长度,不过,通过宏可以实现调用函数时无需传入数组长度。
void func_(int n[], int len);
#define func(n) func_((n), sizeof(n)/sizeof(n[0]))