栈在表达式求值中的应用
引导
在开始我们之前,请思考如何不使用界限符即括号实现下面表达式的计算:
( ( 15 ÷ ( 7 − ( 1 + 1 ) ) ) × 3 ) − ( 2 + ( 1 + 1 ) ) ((15\div(7-(1+1)))\times3)-(2+(1+1)) ((15÷(7−(1+1)))×3)−(2+(1+1))
下面我们就来研究如何不使用括号计算上面的表达式并在最后使用栈和队列进行代码实现。
三种算数表达式
中缀表达式
在引导中你见到的这种表达式就是中缀表达式,顾名思义所谓中缀就是将运算符写在两个操作数之间,但是这种写法对于只会进行四则运算不会判断括号执行顺序的计算机(CPU)来说是非常难以“理解”的,所以我们需要将中缀表达式改写为下面两种后缀和前缀形式。
后缀表达式
后缀表达式即将运算符放在两个操作数之后,所以上述表达式的后缀表达式变为
15 7 1 1 + − ÷ 3 × 2 1 1 + + − 15\quad7\quad1\quad1+\quad-\quad\div\quad3\quad\times\quad2\quad1\quad1\quad+\quad+\quad- 15711+−÷3×211++−
计算方法为从左到右依次扫描,每遇到运算符就将运算符前的两个数进行运算,然后将结果放回原位置,再扫描下一个。
例如:从左往右读,读到1后面的”+“号,此时用1+1=2的结果2代替原来字符串中的1 1 +,新字符串变为15 7 2 - ÷ 3 × 2 1 1 + + -,然后再读“-”号,此时用7-2=5的结果5替原来字符串中的7 2 - ,新字符串变为15 5 ÷ 3 × 2 1 1 + + -,以此类推,扫描÷,此时用15÷3=5的结果代替15 5 ÷,新字符串变为3 3 × 2 1 1 + + -,然后扫瞄×,读此处不在赘述。
但是聪明的你肯定会发现,同一个表达式他的后缀表达式不唯一,如何制定一个规则,从而当输入一个中缀表达式时,可以将其转化为唯一的以后后缀表达式?从而方便计算机的计算
所以这里我们在将中缀表达式准化成后缀表达式的时候引入不成文的规定即左优先原则(在表达式靠左优先级高的四则运算符先进行计算),下面我们来看两个例子,例中字母表示操作数:
左面的表达式使用了左优先原则,右面的表达式在保证四则运算优先级正确的前提下不采用左优先原则。
可以看到两种后缀表达式运算逻辑都正确,但是采用了左优先原则的后缀表达式,运算符出现的先后顺序刚好是中缀表达式中运算符先后执行的顺序。
所以采用这种方法便可以在用我前面提到的运算方式,“从左往右读,读到1后面的”+“号,此时用1+1=2的结果2代替原来字符串中的1 1 +,新字符串变为15 7 2 - ÷ 3 。”
前缀表达式
前缀表达式即将运算符放到操作数的前面,但是特别说明这里我们使用右优先原则这样我们的表达式操作符从右往左出现的顺序(不同于后缀表达式从左往右)就是该后缀表达式对应的中缀表达式中操作符靠右优先执行的顺序:
计算方法刚好是将后缀表达式的计算过程反过来,即从右往左扫描,遇到操作符将操作符后面的两个数计算并用结果替换原来的数和运算符,此处不再举例。
代码实现(以后缀表达式为例)
为了减少c语言处理数字和字符组成字符串的代码量,尽量展示栈和队列的使用思想所以用0到9的数字计算,代码中保留了一些我本人的测试代码,便于读者了解程序的运行过程
输入输出描述
输入描述:一个10以内加减法的中缀表达式字符串:
例如:((5/(7-(1+1)))*3)-(2+(1+1))
输出描述:运算结果
-1
使用栈和队列实现的思路介绍
首先我们需要定义一个操作数栈(用于临时保存操作数和计算结果),一个操作符栈(存储操作符)和一个队列(队列用于存中缀表达式转后缀的结果),比如输入((5/(7-(1+1)))3)-(2+(1+1)),我们经过一些列的出栈入栈操作并结合“左优先原则”使其变为后缀5711±/3211+±并存储在队列中,并利用我前面提到的后缀表达式计算符方法计算结果。
中缀转后缀方法
初始化一个操作符栈,用于保存暂时还不能确定运算顺序的运算符。
从左往右处理各个元素,直到末尾。可能遇到三种情况
- 遇到操作数。直接加入后缀表达式。
- 遇到界限符。遇到“(”直接入栈;遇到“)”依此弹出栈内运算符并加入后缀表达式队列,直到弹出“(“为止。注意:“(”不能加入后缀表达式队列。
- 遇到四则运算符。依此弹出栈中优先级高于或等于当前四则运算符的所有运算符,并加入后缀表达式,若碰到“(”或栈空则停止。之后再把当前运算符入栈。
按上述方法处理完所有字符后,将操作符栈中剩余运算符依此弹出加入后缀表达式队列
具体代码
头文件
#include <iostream>//头文件
#include <string>
using namespace std;
定义数栈和基本操作
typedef struct NumNode {
double data;
NumNode *next;
} NumNode, *NumStack;
bool InitNumStack(NumStack &NS) {
NumNode *s;
s = (NumNode *) malloc(sizeof(NumNode));
if (s == NULL)
return false;
s->next = NULL;
NS = s;
return true;
}
bool NumStackEmpty(NumStack &NS) {
return NS->next == NULL;
}
bool NumPush(NumStack &NS, double num) {
NumNode *p;
p = (NumNode *) malloc(sizeof(NumNode));
if (p == NULL)
return false;
p->data = num;
p->next = NS->next;
NS->next = p;
return true;
}
bool NumPop(NumStack &NS, double &num) {
if (NumStackEmpty(NS))
return false;
NumNode *p;
p = NS->next;
num = p->data;
NS->next = p->next;
free(p);
return true;
}
定义操作符栈和基本操作
typedef struct OpNode {
char data;
OpNode *next;
} OpNode, *OpStack;
bool InitOpStack(OpStack &O) {
OpNode *s;
s = (OpNode *) malloc(sizeof(OpNode));
if (s == NULL)
return false;
s->next = NULL;
O = s;
return true;
}
bool OpStackEmpty(OpStack O) {
return O->next == NULL;
}
bool OpPush(OpStack &O, char op) {
OpNode *p;
p = (OpNode *) malloc(sizeof(OpNode));
if (p == NULL)
return false;
p->data = op;
p->next = O->next;
O->next = p;
return true;
}
bool OpPop(OpStack &O, char &elem) {
if (OpStackEmpty(O))
return false;
OpNode *p;
p = O->next;
elem = p->data;
O->next = p->next;
free(p);
return true;
}
bool OpGetTop(OpStack &O, char &elem) {
if (OpStackEmpty(O))
return false;
OpNode *p;
p = O-><