1.前言
学到栈的时候,中缀表达式与后缀表达式往往是永远绕不开的问题。网上很多教程对于中缀表达式到后缀表达式的转换已经讲解的非常详细,但是为什么要这么转换,其中的原理是什么呢?只有了解了这些,才能更好的学会这个算法,而不是生搬硬套。能力一般,水平有限。我把自己的理解和大家分享出来,如有错误欢迎指出!干货满满,没有废话,全是手打的,如果对您有帮助!麻烦评论点赞!谢谢!注:本篇文章更注重让你理解这个原理,而不是copy代码哈,我的代码为了更好的讲解都做了简化,让大家容易读懂【注:未经允许,请勿转载】
2.从读懂后缀表达式,手写后缀表达式开始
首先我们应该知道我们平常所列的式子如 2+3*5 这样的式子就称为中缀表达式。这个式子的后缀表达式是这样的 235*+ 。之所以这样做,是因为后缀表达式更容易让计算机读懂,而中缀表达式更符合我们自己的理解。在这里很多文章和书都会直接告诉你如何进行转化,相信大家也都看的头晕目眩。而我认为,想理解如何实现,我们先要完成两步骤:读懂后缀表达式、手写转化中缀表达式。
首先我们先要读懂后缀表达式,把自己想象成一个没有感情的机器,它想象成为一个字符串,其中的内容有两种:数字(1,2,3)和操作数(+,-,*,/),我们分别称之为Num 和Op。以235*+为例从左到右,找到第一个Op,是*从这个Op的前面依次找到两个数字(5和3),然后执行 Num1 op Num2 (即5*3)(注意这里要注意一下Num1和2的顺序了,因为减法除法会要求顺序)计算出来的结果再放回刚才的位置 。也就是说我们现在的表达式变成了 2(15)+ 没错吧?我们再按照刚才的方法执行,找到+号,再找前面两个值(2和15)再做加法,就得到了结果17。当我们发现表达式里面只有数字了,没有感情的机器人也就结束了循环。是不是很简单?我下面列几个式子,你来计算一下对不对吧
后缀表达式 | 中缀表达式 | 结果 |
235*+ | 2+3*5 | 17 |
8462*-+ | 8+4-6*2 | 0 |
35+2*71/+4- | 2*(3+5)+7/1-4 | 19 |
经过刚才的训练你应该已经能够看懂后缀表达式了,相信理解能力比较强的同学也可以自己模仿着拿中缀表达式写成后缀表达式了。下面我们来看一下,如何手写转化吧。大家应该已经注意到计算机通过遍历中缀表达式,找Op,然后从前找两个数去做运算。当我们自己逆过来自己写的时候,就应该把Num1Num2写着前面,后面紧跟要进行运算的Op。比如3*5我们就要写成35*;2+6我们就要写成26+;那么2+3*5我们需要先进行运算的是3*5然后再和2相加,所以我们对于Op是+这个运算来说Num1是2,Num2是3*5的结果所以我们写的是2(3和5相乘的结果)+ 。对吧?然后3和5相乘是什么呢?35*对吧。所以我们写出来的后缀表达式就是235*+;再把上面的中缀表达式看一遍,大家能不能写出后缀表达式了呢?
3.用栈实现中缀转后缀并计算的思路
分析一下我们刚才做Num1 op Num2这个过程,找一个最近的Op然后把最近的两个数字拿出来,算结果,再放回去。这不就是栈吗!!!!!用栈再好不过了啊啊啊啊啊啊!!!我们只需要在后缀表达式中遇到数字就往里压,遇到op弹出俩数字,算完了压进去,然后循环往复走到头不就完了吗!!!这样一看大家应该就明白了为什么我们要辛辛苦苦的转换成后缀表达式了,这对计算机来说太方便了。
还剩下最后一个难点,计算机如何把中缀表达式转化成后缀表达式呢?其实也很简单我们也需要一个存放Op的栈来做辅助,间接的改变这些运算的顺序。我们先看看借用这个栈,转化遵循的规则(我尽量用自己的话说,书面语书上和其他人的文章中想必很多),如下,一定要一个字一个字读:
- 在从左向右遍历过程中,我们逐渐生成后缀表达式,只有Op会进行入栈弹栈操作,碰见数字直接依次写到正在生成的后缀表达式之后
- 我们定义优先级:乘号=除号>加号=减号;在从左向右遍历过程中,若碰见Op我们试图进行压栈,但是压栈前我们要做一个判断:如果该OP的优先级比栈顶的大(如:这个Op是*栈顶是+),那我们直接压栈;如果Op优先级小于等于栈顶优先级(如:这个Op是+栈顶是-或者*)我们进行弹栈,并且弹出的符号直接添加到正在生成的后缀表达式之后,直到出现该Op优先级大于栈顶元素为止。
- 若中缀表达式扫描完了,栈内还有元素,我们依次弹出,加入到后缀表达式中
可能大家看完了还是有些绕,那我带着大家走一遍,大家看看下面的表自行体会,我们将a+b*c-d\e这个表达式进行转化:
扫描中 | Op Stack (右边是栈顶) | 已经生成的后缀表达式 | 备注 |
a | 空 | a | 是Num,直接加到表达式后面 |
a+ | + | a | 是Op,栈空,压栈 |
a+b | + | ab | 是Num,直接加到表达式后面 |
a+b* | +* | ab | 是Op,*优先级大于+,压栈 |
a+b*c | +* | abc | 是Num,直接加到表达式后面 |
a+b*c- | - | abc*+ | 【重点!】-<*,故*弹出,-=+故+弹出,然后-进栈 |
a+b*c-d | - | abc*+d | 是Num,直接加到表达式后面 |
a+b*c-d\ | -/ | abc*+d | 是Op,/优先级大于-,直接将/压栈 |
a+b*c-d\e | -/ | abc*+de | 是Num,直接加到表达式后面,结束 |
a+b*c-d\e | 空 | abc*+de\- | 栈不是空,依次弹栈到栈空 |
相信看完这个表,大家应该都明白了吧?补充一下,()括号的问题我没有说,有括号方法也很简单,只需要在压栈时候如果是左括号就压栈是右括号就不断弹栈直到碰见左括号就可以了。为了讲的更清楚就不提()括号的问题了。原理理解了下面只剩下实现了。
4.代码实现
懂得了原理,代码实现就很简单了,这里由于大家都是学习数据结构,就不使用stl了。可以使用map做op与优先级的一一映射,使用#include<stack>轻松实现栈,为了强化基本功,我们手写一个链表栈并在此之上实现转换。特别指出,我们为了着重理解算法核心,省略了一些如字符串处理,MAP做映射关系等的繁枝末节。约束如下:
- 未对字符串做合法约束,默认输入的字符串皆正确
- 每个Num必须为个位数,没做字符串整合(如果想做很简单,就把连续的数字合并做一下类型转换即可)
对于转化后的计算,我们就不做重点讨论啦,如上文说过的,非常简单,遇到Num压栈,遇到Op连续弹两次栈,计算后再压回去,直到只有一个数,那就是结果。实现比较简单,我们就不做了。附上源代码!:
#include <iostream>
#include <stdio.h>
#include <stack>
#include<string.h>
using namespace std;
void change(char a[]);
int cauculate(char a[]);
int main()
{
char a[] ="3+2/1+5-2*2";
change(a);
printf("%s\n",a);
printf("%c",cauculate(a));
}
void change(char a[])
{
char res[1000];
stack<char> stk;
int index = 0;
for(int i = 0;a[i]!='\0';i++)
{
switch(a[i])
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
{
res[index++] = a[i];
break;
}
case '+':
{
while(!stk.empty())
{
res[index++] = stk.top();
stk.pop();
}
stk.push('+');
break;
}
case '-':
{
while(!stk.empty())
{
res[index++] = stk.top();
stk.pop();
}
stk.push('-');
break;
}
case '*':
{ if(stk.empty())
{stk.push('*');break;}
if(stk.top()=='+'||stk.top()=='-')
stk.push('*');
else
{
while(!stk.empty())
{
res[index++] = stk.top();
stk.pop();
}
stk.push('*');
}
break;
}
case '/':
{
if(stk.empty())
{stk.push('/');;break;}
if(stk.top()=='+'||stk.top()=='-')
stk.push('/');
else
{
while(!stk.empty())
{
res[index++] = stk.top();
stk.pop();
}
stk.push('/');
}
break;
}
}
}
while(!stk.empty())
{
res[index++] = stk.top();
stk.pop();
}
res[index]='\0';
strcpy(a,res);
}
int cauculate(char a[])
{ stack<int> stk;
for(int i=0;a[i]!='\0';i++)
{
if(a[i]>='0'&&a[i]<='9')
{
stk.push(a[i]);
}
else
{ int num2 = stk.top()-'0';
stk.pop();
int num1 = stk.top()-'0';
stk.pop();
switch(a[i])
{
case '+':
stk.push(num1+num2+'0');break;
case '-':
stk.push(num1-num2+'0');break;
case '*':
stk.push(num1*num2+'0');break;
case '/':
stk.push(num1/num2+'0');break;
}
}
}
return stk.top();
}
5.结语
能力一般,水平有限。我把自己的理解和大家分享出来,如有错误欢迎指出!干货满满,没有废话,全是手打的,如果对您有帮助!麻烦评论点赞!谢谢!