1 本文目标
分析用堆栈解析算术表达式的基本方法。给出的示例代码能解析任何包括+,-,*,/,()和0到9数字组成的算术表达式。
2 中缀表达式和后缀表达式
中缀表达式就是通常所说的算术表达式,比如(1+2)*3-4。
后缀表达式是指通过解析后,运算符在运算数之后的表达式,比如上式解析成后缀表达式就是12+3*4-。这种表达式可以直接利用栈来求解。
3 运算符的优先级
优先级 | 运算符 |
1 | 括号() |
2 | 负号- |
3 | 乘方** |
4 | 乘*,除/,求余% |
5 | 加+,减- |
6 | 小于<,小于等于<=,大于>,大于等于>= |
7 | 等于==,不等于!= |
8 | 逻辑与&& |
9 | 逻辑或|| |
大致的规律是,一元运算符 > 二元运算符 > 多元运算符。
4 利用堆栈解析算术表达式的过程
中缀表达式翻译成后缀表达式的方法如下:
(1)从 左 向 右 依次取得数据ch。
(2)如果ch是操作数,直接输出。
(3)如果ch是运算符(含左右括号),则:
a:如果ch = '(',放入堆栈。
b:如果ch = ')',依次输出堆栈中的运算符,直到遇到'('为止。
c:如果ch不是')'或者'(',那么就和堆栈顶点位置的运算符top做优先级比较。
1:如果ch优先级比top高,那么将ch放入堆栈。
2:如果ch优先级低于或者等于top,那么输出top,然后将ch放入堆栈。
(4)如果表达式已经读取完成,而堆栈中还有运算符时,依次由顶端输出。
如果我们有表达式(A-B)*C+D-E/F,要翻译成后缀表达式,并且把后缀表达式存储在一个名叫output的字符串中,可以用下面的步骤。
(1)读取'(',压入堆栈,output为空
(2)读取A,是运算数,直接输出到output字符串,output = A
(3)读取'-',此时栈里面只有一个'(',因此将'-'压入栈,output = A
(4)读取B,是运算数,直接输出到output字符串,output = AB
(5)读取')',这时候依次输出栈里面的运算符'-',然后就是'(',直接弹出,output = AB-
(6)读取'*',是运算符,由于此时栈为空,因此直接压入栈,output = AB-
(7)读取C,是运算数,直接输出到output字符串,output = AB-C
(8)读取'+',是运算符,它的优先级比'*'低,那么弹出'*',压入'+",output = AB-C*
(9)读取D,是运算数,直接输出到output字符串,output = AB-C*D
(10)读取'-',是运算符,和'+'的优先级一样,因此弹出'+',然后压入'-',output = AB-C*D+
(11)读取E,是运算数,直接输出到output字符串,output = AB-C*D+E
(12)读取'/',是运算符,比'-'的优先级高,因此压入栈,output = AB-C*D+E
(13)读取F,是运算数,直接输出到output字符串,output = AB-C*D+EF
(14)原始字符串已经读取完毕,将栈里面剩余的运算符依次弹出,output = AB-C*D+EF/-
5 计算算术表达式
当有了后缀表达式以后,运算表达式的值就非常容易了。可以按照下面的流程来计算。
(1)从左向右扫描表达式,一个取出一个数据data
(2)如果data是操作数,就压入堆栈
(3)如果data是操作符,就从堆栈中弹出此操作符需要用到的数据的个数,进行运算,然后把结果压入堆栈
(4)如果数据处理完毕,堆栈中最后剩余的数据就是最终结果。
比如我们要处理一个后缀表达式1234+*+65/-,那么具体的步骤如下。
(1)首先1,2,3,4都是操作数,将它们都压入堆栈
(2)取得'+',为运算符,弹出数据3,4,得到结果7,然后将7压入堆栈
(3)取得'*',为运算符,弹出数据7,2,得到数据14,然后将14压入堆栈
(4)取得'+',为运算符,弹出数据14,1,得到结果15,然后将15压入堆栈
(5)6,5都是数据,都压入堆栈
(6)取得'/',为运算符,弹出数据6,5,得到结果1.2,然后将1.2压入堆栈
(7)取得'-',为运算符,弹出数据15,1.2,得到数据13.8,这就是最后的运算结果.
下面是将中缀表达式转化为后缀表达式的代码实现。
/************************************************************************/
/* Description: Use the stack to change \
* infix expression into postfix expression.
* Author: Gecko
* Date: 2012-12-25 10:59
* Note:
/************************************************************************/
#include "iostream"
#include "string"
#include <Windows.h>
using namespace std;
int op[50];
int p=-1;
void ProcessString(string str)
{
int len = str.length();
int op_nxt = 0;
int flag = 0;
for(int i=0;i<len;i++)
{
//如果是操作数
if ((str[i]>='A' && str[i]<='Z') ||(str[i]>='0' && str[i]<='9'))
{
cout<<str[i];
}//如果是运算符
else if(str[i] !=' ')
{
switch(str[i])
{
case '+':
op_nxt = 0;
break;
case '-':
op_nxt = 1;
break;
case '*':
op_nxt = 2;
break;
case '/':
op_nxt = 3;
break;
case '^':
op_nxt = 4;
break;
case '(':
op_nxt = 5;
break;
case ')':
op_nxt = 6;
break;
}
//如果是'(',直接将运算符压入
if (op_nxt == 5)
{
op[++p] = op_nxt;
flag = 1;
}
//如果是')',将运算符弹出直到'('为止
else if(op_nxt == 6 )
{
while (op[p] != 5)
{
switch(op[p])
{
case 0:
cout<<'+';
break;
case 1:
cout<<'-';
break;
case 2:
cout<<"*";
break;
case 3:
cout<<'/';
break;
case 4:
cout<<'^';
break;
}
p--;
}
flag = 0;
p--;
}
//如果运算符优先级高于栈顶运算符,直接压入
else if(p==-1 || op_nxt > op[p] || flag == 1)
op[++p] = op_nxt;
else //如果运算符优先级小于等于栈顶运算符,弹出
{
while(p>=0 && op_nxt/2<=op[p]/2)
{
switch(op[p])
{
case 0:
cout<<'+';
break;
case 1:
cout<<'-';
break;
case 2:
cout<<"*";
break;
case 3:
cout<<'/';
break;
case 4:
cout<<'^';
break;
}
p--;
}
op[++p] = op_nxt;
}
}
}
while(p>=0)
{
switch(op[p])
{
case 0:
cout<<'+';
break;
case 1:
cout<<'-';
break;
case 2:
cout<<"*";
break;
case 3:
cout<<'/';
break;
case 4:
cout<<'^';
break;
}
p--;
}
cout<<endl;
}
int main()
{
//string str = "(1+2)*3-4";
//string str = "1+2*3";
//string str = "(1+2)*3";
string str = "A+B*(C+D)-E/F";
ProcessString(str);
system("pause");
return 0;
}
上述代码在书写的过程中碰到了些许问题:
1. 关于在符号栈中,如果存在'(',这是应该添加将碰到的运算符直接压入栈即可,如何判断'('是否已经在栈中,我们添加了flag标识符用以区别。
2. 在设置符号优先级的过程中,为了接下来的运算的方便,我们将每个运算符都设置了不同的级别,但是在实际运算中,'+'与'-';'*'与'/'的优先级是相同的,这点需要注意,因为在碰到优先级小于等于栈顶运算符的优先级时需要弹出栈顶元素后压入。'+'(0)与'-'(1),此处如何判断?技巧是:使用了除2的方法。
3.在遇到')'时,需要弹出运算符,直到遇到')'为止,这个地方需要讨论的时候在循环结束的时候,栈顶元素'('并没有弹出,于是在跳出循环后,再次做了一遍p--,但是不做p--程序依旧可以得到正确的结果,是因为在输入的时候没有输入'('与')'的情况。循环最关键的步骤是把flag置为1.
/************************************************************************/
/* Description: Calculate the equation.
* Author: Gecko
* Date: 2012-12-25 10:59
* Note:
/************************************************************************/
#include "iostream"
#include "string"
#include <Windows.h>
using namespace std;
int num[50],op[50];
int p=-1,q=-1;
int pow_int( int a,int b )
{
int k = 1;
for( int i = 0;i < b;i++ )
k *= a;
return k;
}
void cul( int p,int op )
{
if( op == 0 )
num[p-1] = num[p-1]+num[p];
if( op == 1 )
num[p-1] = num[p-1]-num[p];
if( op == 2 )
num[p-1] = num[p-1]*num[p];
if( op == 3 )
num[p-1] = num[p-1]/num[p];
if( op == 5 )
num[p-1] = pow_int( num[p-1],num[p] );
}
int ProcessString(string str)
{
int len = str.length();
int op_nxt = 0;
int num_nxt = 0;
int flag = 0;
int change = 0;
for(int i=0;i<len;i++)
{
//如果是操作数
if (str[i]>='0' && str[i]<='9')
{
num_nxt = num_nxt*10+str[i]-'0';
change = 1;
}//如果是运算符
else if(str[i] !=' ')
{
if (change==1)
{
num[++q]=num_nxt;
num_nxt = 0;
change = 0;
}
switch(str[i])
{
case '+':
op_nxt = 0;
break;
case '-':
op_nxt = 1;
break;
case '*':
op_nxt = 2;
break;
case '/':
op_nxt = 3;
break;
case '^':
op_nxt = 4;
break;
case '(':
op_nxt = 5;
break;
case ')':
op_nxt = 6;
break;
}
//如果是'(',直接将运算符压入
if (op_nxt == 5)
{
op[++p] = op_nxt;
//flag = 1;
}
//如果是')',将运算符弹出直到'('为止
else if(op_nxt == 6 )
{
while (op[p] != 5)
{
cul(q--,op[p]);
p--;
}
flag = 0;
p--;
}
//如果运算符优先级高于栈顶运算符,直接压入
/*else if(p==-1 || op_nxt > op[p] || flag == 1)
op[++p] = op_nxt;*/
else //如果运算符优先级小于等于栈顶运算符,弹出
{
while(p>=0 && op[p] <= 4 && op_nxt/2<=op[p]/2)
{
cul(q--,op[p]);
p--;
}
op[++p] = op_nxt;
}
}
}
while(p>=0)
{
if( change == 1)
{
num[++q] = num_nxt;
num_nxt = 0;
change == 0;
}
cul(q--,op[p]);
p--;
}
return num[0];
}
int main()
{
//string str = "(1+2)*3-4";
//string str = "1+2*3";
//string str = "(1+2)*3/2";
string str = "12*(0+0*5)";
cout<<ProcessString(str)<<endl;
system("pause");
return 0;
}
上述代码的注意事项:
1.将“如果运算符优先级高于栈顶运算符,直接压入”与“如果运算符优先级小于等于栈顶运算符,弹出”合并,先判断然后直接压入.
2.将字符串中的数字提取出来,"12"提取出12,而不是1和2,同时还要对包含0的情况加以区分。