1.首先弄清前缀、中缀、后缀表达式的含义
<1>
中缀表达式即运算符处在两个操作数的中间,该表达式广泛运用于日常,需要人主观进行判断运算符的优先级,不易被机器识别。
<2>
前缀表达式即将运算符写在前面,没有括号的表达式,为纪念波兰籍发明者,也称为波兰式。
虽然将中缀改为前缀之后,表达式的求值可以化简成入栈和出栈两个操作,但较于后缀表达式还是比较复杂。
<3>
后缀表达式即将运算符写在后面,也称为逆波兰式,因为运算顺序就是从左到右运算符出现的顺序,所以操作最简单。
2.中缀表达式的运算逻辑
<1>
分清优先级。
先计算括号内,后计算括号外
在无括号或同层括号类,乘除运算的优先级高于加减运算的优先级
同一优先级运算,从左向右依次进行
<2>
使用两个工作栈,一个存放运算符,一个存放操作数
操作数直接入栈
运算符则与运算栈的栈顶元素比较优先级
⑴若该运算符的优先级更高,则直接入栈
⑵若该运算符的优先级更低,则运算栈的栈顶运算符依次退栈直到左括号或结束⑶若两运算符同级,说明左右括号相遇,左括号此时应该可以直接退栈
<3>
需要进行很多次的条件判断,逻辑有点不清晰,什么时候运算符入栈,什么时候出栈、括号的问题无法很好的处理,不能直接一次解决,又感觉要嵌到许多种情况里判断,最后也无法对整体有一个完整性的把握,无法确定算法处理好了所有可能的情况,无法对结构有清晰的判断。在复杂的逻辑判断的锻炼上还要加强,中缀表达式的直接判断就放弃了,知道转后缀和后缀的计算也行。
3.中缀表达式转后缀表达式
在表达式中存在操作数、小数点、运算符、括号和最后的等号。
需要一个存储后缀表达式的字符数组,和一个存储运算符的字符栈
<1>
表达式实际是存储在字符串中,因此首先要处理操作数的读入。
如果是操作数,直接存入后缀表达式的字符数组中。包括多位数和小数位。
<2>
然后处理括号。
如果是左括号,直接入栈然后处理下一个字符。
如果是右括号,说明左右括号中间的计算完成,将栈顶元素依次退栈存储到字符数组中,直到栈顶元素变为左括号,然后再次退栈,继续下一个字符。
<3>
然后是运算符,首先要确定优先级。可以直接量化,假设
‘=’、‘(‘、’)’的优先级是0
‘+’、‘-’的优先级是1
‘*’、‘/’的优先级是2
当读入的字符是一个运算符时,将它与栈顶元素比较,
如果它的优先级大于栈顶元素,
说明它前面一个操作数要先和它后面一个操作数做该运算,因此直接入栈。
如果它的优先级小于等于栈顶元素,
说明它前面一个数是和这个数前面一个数进行运算的。
所以栈顶元素退栈存入字符数组,然后再将该运算符和新的栈顶元素进行比较。(即一直要退栈顶元素到当前运算的优先级高时入栈)
(在一开始运算栈应该要初始化一个优先级为0的元素,显然如果栈顶元素为该元素,说明当前判断运算符之前的运算已经全部完成,就可以直接入栈)
<4>
最后的’=’直接加上就好,这样一来所有的字符就都能够处理好,并且通过条件判断非常清晰。
//NOJ467题实现代码
#include
#include
#include
using namespace std;
char f(char a,char b)
{
int aa;int bb;
if(a=='='||a=='('||a==')') aa=0;
else if(a=='+'||a=='-') aa=1;
else if(a=='*'||a=='/') aa=2;
if(b=='='||b=='('||b==')') bb=0;
else if(b=='+'||b=='-') bb=1;
else if(b=='*'||b=='/') bb=2;
if(aa>bb) return '>';
else if(aa==bb) return '=';
else if(aareturn '<';
}
int main()
{
int s;cin>>s;
int t=0,h=0;
while(s--)
{
char S[1000];//栈
S[0]='=';int top=0;
char expresion[2000],str[5000];
cin>>expresion;
int i=0;int j=0;
while(expresion[i]!='\0')
{
if(isdigit(expresion[i])||expresion[i]=='.')
{
if(expresion[i+1]=='.'||isdigit(expresion[i+1]))
{
str[j++]=expresion[i++];
}
else{
str[j++]=expresion[i++];
str[j++]=' ';
}
}
//处理完操作数和小数点,处理括号
else if(expresion[i]=='(')
{
S[++top]=expresion[i];
i++;
}
else if(expresion[i]==')')
{
while(S[top]!='(')
{
str[j++]=S[top];
str[j++]=' ';
top--;
}
top--;i++;
}
//处理完括号处理最后的等于
else if(expresion[i]=='=')
{
while(S[top]!='=')
{
str[j++]=S[top];
str[j++]=' ';
top--;
}
str[j++]='=';str[j]='\0';
break;
}
//最后剩下运算符
else
{
switch(f(expresion[i],S[top]))
{
case '>':S[++top]=expresion[i++];break;
case '=':
case '<':
{
while(f(expresion[i],S[top])!='>')
{
str[j++]=S[top];
str[j++]=' ';
top--;
}
S[++top]=expresion[i++];break;
}
}
}
}
int t=0;
while(str[t]!='\0')
{
cout<<str[t];
str[t++]='\0';
}
cout<
4.前、后缀表达式的求值
后缀表达式的计算逻辑非常简单。
如果是操作数,就入栈。
如果是运算符,就将栈顶最上方两个元素出栈进行运算(注意顺序),然后将结果入栈。(退2进1)
而如果是前缀表达式的求值,本来是从右向左扫描,是操作数就入栈,是运算符就取栈顶两元素运算。
但实际上将字符串反序,就和后缀表达式的计算基本一样,(差别主要是顺序和操作数在字符串中的处理)
贴一道计算10以内前缀表达式的代码
//NOJ128题实现代码
#include
#include
#include
using namespace std;
double Stack[510]; //操作数栈
//字符串s2反序存储到s1
void strdeal(char *s1,char *s2)
{
int i=0;
while(s2[i]!='\0')i++; //出循环s2[i]=0
int j=0;i--;
while(i>=0)
{
s1[j]=s2[i];
j++;i--;
}
s1[j]='\0';
}
int main()
{
char str[2000];
while(gets(str)) //完成输入
{
//反序
char s[2000];
strdeal(s,str); //实现 s为待处理字符串
int i=0;
int top = -1; //栈顶指针
int tag = 0 ;
while(s[i]!='\0')
{
if(isspace(s[i])) tag=0; //空格
if(tag==1&&s[i]!='.')
{
Stack[top]=Stack[top]/10;
Stack[top]+=s[i]-'0';
}
else if(isdigit(s[i]))
{
tag=1;
Stack[++top]=s[i]-'0';
}
else
{
switch(s[i])
{
case '+' :
Stack[top-1]=Stack[top]+Stack[top-1];
Stack[top--]=0;break;
case '-' :
Stack[top-1]=Stack[top]-Stack[top-1];
Stack[top--]=0;break;
case '*' :
Stack[top-1]=Stack[top]*Stack[top-1];
Stack[top--]=0;break;
case '/' :
Stack[top-1]=Stack[top]/Stack[top-1];
Stack[top--]=0;break;
default:break;
}
}
i++;
}
cout<2)<cout<
5.中缀表达式转后缀进行求值
结构中缀转后缀,和后缀表达式的算法,就可以实现中缀表达式的求值了。
贴一道中缀表达式求值的代码
//NOJ35题实现代码
#include
#include
#include
using namespace std;
char f(char a,char b)
{
int aa;int bb;
if(a=='='||a=='('||a==')') aa=0;
else if(a=='+'||a=='-') aa=1;
else if(a=='*'||a=='/') aa=2;
if(b=='='||b=='('||b==')') bb=0;
else if(b=='+'||b=='-') bb=1;
else if(b=='*'||b=='/') bb=2;
if(aa>bb) return '>';
else if(aa==bb) return '=';
else if(aareturn '<';
}
int main()
{
int s;cin>>s;
while(s--)
{
char expresion[1010],S[100],str[2010];
S[0]='=';int top=0;
cin>>expresion;int i=0,j=0;
while(expresion[i]!='\0')
{
if(isdigit(expresion[i])||expresion[i]=='.')
{
if(expresion[i+1]=='.'||isdigit(expresion[i+1]))
str[j++]=expresion[i++];
else{ str[j++]=expresion[i++];
str[j++]=' ';}
}//处理完操作数和小数点,处理括号
else if(expresion[i]=='(')
S[++top]=expresion[i++];
else if(expresion[i]==')')
{
while(S[top]!='(')
{
str[j++]=S[top];
str[j++]=' ';
top--;
}
top--;i++;
}//处理完括号处理最后的等于
else if(expresion[i]=='=')
{
while(S[top]!='=')
{
str[j++]=S[top];
str[j++]=' ';
top--;
}
str[j++]='=';str[j]='\0';
break;
}//最后剩下运算符
else
{
switch(f(expresion[i],S[top]))
{
case '>':S[++top]=expresion[i++];break;
case '=':
case '<':{
while(f(expresion[i],S[top])!='>')
{
str[j++]=S[top];
str[j++]=' ';
top--;
}
S[++top]=expresion[i++];break;
}
}
}
}//str存储了后缀表达式,最后以=收尾
double Stack[1010];//操作数栈,接下来进行存数
i=0;top=-1;int tag=0;int t;//小数位t
while(str[i]!='\0')
{
if(isdigit(str[i]))
{
if(tag==2)
{
double temp=str[i]-'0';
int tt=t;
while(tt--)
temp=temp/10;
Stack[top]+=temp;
t++;i++;
}
else if(tag==1)
{
Stack[top]=Stack[top]*10+str[i]-'0';
i++;
}
else if(tag==0)
{
Stack[++top]=str[i]-'0';
tag=1;i++;
}
}
else if(str[i]=='.')
{
tag=2;t=1;i++;
}
else if(isspace(str[i]))
{
tag=0;i++;
}
else
{
switch(str[i++])
{
case '+':Stack[top-1]=Stack[top-1]+Stack[top];S[top--]=0;break;
case '-':Stack[top-1]=Stack[top-1]-Stack[top];S[top--]=0;break;
case '*':Stack[top-1]=Stack[top-1]*Stack[top];S[top--]=0;break;
case '/':Stack[top-1]=Stack[top-1]/Stack[top];S[top--]=0;break;
case '=':break;
}
}
}
cout<2)<while(str[i]!='\0')
str[i++]='\0';
}
}
6.过程逻辑与栈的运算
以前处理的问题简单的时候,总是想一想,过程基本就那样几个步骤,一两个条件判断,一两个循环操作,很清晰。
但是当一个问题不是靠着一条路就穿到结尾,而是结构复杂要考虑相互影响的时候,就需要非常清晰有条理的思路了,这大概就是所谓逻辑思维的体现吧,怎么考虑问题的各个层次才可以不漏情况,怎么把问题先抽象成几个步骤然后在分块去实现,这些还要加强。
对于栈,有句在论坛看到的感觉很贴切
“以前我一直以为栈是一种数据结构,但是我做了**题后,才觉得,栈其实是一种思想。”