1.前言
学到栈的时候,中缀表达式与后缀表达式往往是永远绕不开的问题。网上很多教程对于中缀表达式到后缀表达式的转换已经讲解的非常详细,但是为什么要这么转换,其中的原理是什么呢?只有了解了这些,才能更好的学会这个算法,而不是生搬硬套。能力一般,水平有限。我把自己的理解和大家分享出来,如有错误欢迎指出!干货满满,没有废话,全是手打的,如果对您有帮助!麻烦评论点赞!谢谢!
2.从读懂后缀表达式,手写后缀表达式开始
首先我们应该知道我们平常所列的式子如 2+3*5 这样的式子就称为中缀表达式。这个式子的后缀表达式是这样的 235*+ 。之所以这样做,是因为后缀表达式更容易让计算机读懂,而中缀表达式更符合我们自己的理解。在这里很多文章和书都会直接告诉你如何进行转化,相信大家也都看的头晕目眩。而我认为,想理解如何实现,我们先要完成两步骤:读懂中缀表达式、手写转化中缀表达式。
首先我们先要读懂后缀表达式,把自己想象成一个没有感情的机器,它想象成为一个字符串,其中的内容有两种:数字(1,2,3)和操作数(+,-,*,/),我们分别称之为Num 和Op。以2+3*5为例从左到右,找到第一个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必须为个位数,没做字符串整合(如果想做很简单,就把连续的数字合并做一下类型转换即可)
经过以上简化,我们先手撸一下栈,栈的代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define Max 1000
struct node{
int data;//数据
struct node *nextNode;//下一个结点
};
struct Stack {
struct node *head;//头结点
};
struct Stack* creatStack(){
struct Stack *ptr= (struct Stack *)malloc(sizeof(struct Stack));
ptr->head = NULL;
return ptr;
};
struct Stack* push(struct Stack* str,int data){
if(str->head==NULL)
{ str->head =(struct node *)malloc(sizeof(struct node));
str->head->data = data;
str->head->nextNode = NULL;
}
else
{
struct node *newnode =(struct node *)malloc(sizeof(struct node));
newnode->data = data;
newnode->nextNode = str->head;
str->head = newnode ;
}
return str;
};
bool isEmpty(struct Stack* str)
{
if(str->head==NULL)
return 1;
else return 0;
}
struct Stack* pop(struct Stack* str)
{
if(isEmpty(str))
{
printf("Pop false,stack empty.\n");
return str;
}
else{
struct node* temp = str->head;
str->head = str->head->nextNode;
free(temp);
printf("Pop successfully.\n");
return str;
}
};
int getTop(struct Stack* str){
if(!isEmpty(str))
return str->head->data;
else
{
printf("Get failed, stack empty");
return NULL;
}
}
接下来我们用栈去实现中缀表达式到后缀表达式的转换,我们为了便于实现,设定
‘+’为-2,‘-’为-1,‘*’为1,'\'为2,存进栈中,这样我们的那个优先级规则就变成了:栈顶元素小于0且即将压栈元素大于0才可以直接压栈,剩余情况一律弹出。
代码如下:
void change(char Array[])
{
/***********
注意,这个Op栈 可以用stl的map等实现映射,
但为了让初学者更容易读懂,
我们设+为-2,-为-1,*为1,/为2。
***********/
struct Stack* stk = creatStack() ;
char Result[Max];int j=0,temp;
for(int i=0;i<strlen(Array);i++)
{
if(Array[i]>='0'&&Array[i]<='9')
{
Result[j++] = Array[i];
}
else{
if(isEmpty(stk))
{
//直接压栈
if(Array[i]=='+')
{
stk = push(stk,-2);
}
if(Array[i]=='-')
{
stk = push(stk,-1);
}
if(Array[i]=='*')
{
stk = push(stk,1);
}
if(Array[i]=='/')
{
stk = push(stk,2);
}
}
else{
if(getTop(stk)<0&&Array[i]>0)//优先级对
//直接压栈
{
if(Array[i]=='+')
{
stk = push(stk,-2);
}
if(Array[i]=='-')
{
stk = push(stk,-1);
}
if(Array[i]=='*')
{
stk = push(stk,1);
}
if(Array[i]=='/')
{
stk = push(stk,2);
}
}
else
{
do{
//一直弹栈并加入队尾
temp = getTop(stk);
pop(stk);
if(temp==-2)
Result[j++]='+';
if(temp==-1)
Result[j++]='-';
if(temp==1)
Result[j++]='*';
if(temp==2)
Result[j++]='/';
}while((getTop(stk)>=0||Array[i]<=0)&&!(isEmpty(stk)));//这个逻辑比较麻烦,请大家捋顺了
//把这个字符压栈
if(Array[i]=='+')
{
stk = push(stk,-2);
}
if(Array[i]=='-')
{
stk = push(stk,-1);
}
if(Array[i]=='*')
{
stk = push(stk,1);
}
if(Array[i]=='/')
{
stk = push(stk,2);
}
}
}//bk
}//num or op
}//for
//遍历过一遍了,把栈中剩余元素pop出来
while(!isEmpty(stk)) {
//一直弹栈并加入队尾
temp = getTop(stk);
pop(stk);
if(temp==-2)
Result[j++]='+';
if(temp==-1)
Result[j++]='-';
if(temp==1)
Result[j++]='*';
if(temp==2)
Result[j++]='/';
}
//复制数组
strcpy(Array,Result);
}
对于转化后的计算,我们就不做重点讨论啦,如上文说过的,非常简单,遇到Num压栈,遇到Op连续弹两次栈,计算后再压回去,直到只有一个数,那就是结果。实现比较简单,我们就不做了。附上源代码!:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define Max 1000
struct node{
int data;//数据
struct node *nextNode;//下一个结点
};
struct Stack {
struct node *head;//头结点
};
struct Stack* creatStack(){
struct Stack *ptr= (struct Stack *)malloc(sizeof(struct Stack));
ptr->head = NULL;
return ptr;
};
struct Stack* push(struct Stack* str,int data){
if(str->head==NULL)
{ str->head =(struct node *)malloc(sizeof(struct node));
str->head->data = data;
str->head->nextNode = NULL;
}
else
{
struct node *newnode =(struct node *)malloc(sizeof(struct node));
newnode->data = data;
newnode->nextNode = str->head;
str->head = newnode ;
}
return str;
};
bool isEmpty(struct Stack* str)
{
if(str->head==NULL)
return 1;
else return 0;
}
struct Stack* pop(struct Stack* str)
{
if(isEmpty(str))
{
printf("Pop false,stack empty.\n");
return str;
}
else{
struct node* temp = str->head;
str->head = str->head->nextNode;
free(temp);
printf("Pop successfully.\n");
return str;
}
};
int getTop(struct Stack* str){
if(!isEmpty(str))
return str->head->data;
else
{
printf("Get failed, stack empty");
return NULL;
}
}
void change(char Array[])
{
/***********
注意,这个Op栈 可以用stl的map等实现映射,
但为了让初学者更容易读懂,
我们设+为-2,-为-1,*为1,/为2。
***********/
struct Stack* stk = creatStack() ;
char Result[Max];int j=0,temp;
for(int i=0;i<strlen(Array);i++)
{
if(Array[i]>='0'&&Array[i]<='9')
{
Result[j++] = Array[i];
}
else{
if(isEmpty(stk))
{
//直接压栈
if(Array[i]=='+')
{
stk = push(stk,-2);
}
if(Array[i]=='-')
{
stk = push(stk,-1);
}
if(Array[i]=='*')
{
stk = push(stk,1);
}
if(Array[i]=='/')
{
stk = push(stk,2);
}
}
else{
if(getTop(stk)<0&&Array[i]>0)//优先级对
//直接压栈
{
if(Array[i]=='+')
{
stk = push(stk,-2);
}
if(Array[i]=='-')
{
stk = push(stk,-1);
}
if(Array[i]=='*')
{
stk = push(stk,1);
}
if(Array[i]=='/')
{
stk = push(stk,2);
}
}
else
{
do{
//一直弹栈并加入队尾
temp = getTop(stk);
pop(stk);
if(temp==-2)
Result[j++]='+';
if(temp==-1)
Result[j++]='-';
if(temp==1)
Result[j++]='*';
if(temp==2)
Result[j++]='/';
}while((getTop(stk)>=0||Array[i]<=0)&&!(isEmpty(stk)));//这个逻辑比较麻烦,请大家捋顺了
//把这个字符压栈
if(Array[i]=='+')
{
stk = push(stk,-2);
}
if(Array[i]=='-')
{
stk = push(stk,-1);
}
if(Array[i]=='*')
{
stk = push(stk,1);
}
if(Array[i]=='/')
{
stk = push(stk,2);
}
}
}//bk
}//num or op
}//for
//遍历过一遍了,把栈中剩余元素pop出来
while(!isEmpty(stk)) {
//一直弹栈并加入队尾
temp = getTop(stk);
pop(stk);
if(temp==-2)
Result[j++]='+';
if(temp==-1)
Result[j++]='-';
if(temp==1)
Result[j++]='*';
if(temp==2)
Result[j++]='/';
}
//复制数组
strcpy(Array,Result);
}
int main()
{
char Array[1000];
scanf("%s",Array);
change(Array);
printf("%s",Array);
}
5.应用
应用很明显,就是做计算器了。。。可以搞一个比较智能的一大长串的计算器。后面没什么难点了,就不一一实现了~
6.结语
能力一般,水平有限。我把自己的理解和大家分享出来,如有错误欢迎指出!干货满满,没有废话,全是手打的,如果对您有帮助!麻烦评论点赞!谢谢!