目录
一、引言:
花了一天的时间,用代码实现了这个功能,过程很痛苦,出现了很多错误,可能我比较菜,调试了一天才调试出来。俗话说:出现错误才是提升自我的开始。如果没有出现错误,那就永远发现不了问题,只有通过出现问题,分析问题,解决问题,才能有所收获,有所提升,过程是艰难的,但解决了问题的那一刻,像是中了大奖一样。算术表达式转换成后缀表达式是一个经典的栈的应用。思想是一样的,掌握了这个思想,所有的算术表达式转换成后缀表达式都能运用这个思想,代码通用。
转载请注明本文链接!
二、问题分析:
什么是算术表达式:就是我们平时做的数学题的表达式,比如:a*b+(c-d/e)*f
什么是后缀表达式:把操作符放在操作数的后面,上式对应的后缀表达式为:ab*cde/-f*+,越在前面的运算符越先执行。除此之外操作数之间的相对次序是不变的,并且后缀表达式中不含括号。
我们要分析一下,如何设置条件,以至于让计算机能够明白我们的思想,明白我们想要计算机干什么,有些东西我们一眼就能,看出来,但是计算机却需要我们给它传达指令它才能去按照我们的想法去工作。
对于栈来说,我们要首先知道栈是一个先进后出的数组,并且只允许在栈顶进行出栈和进栈操作。数组肯定有大小,我们可以先定义一个足够大的数组来存储我们输入的算术表达式。然后定义一个栈结构体,一个栈S1用来存储最终的后缀表达式,栈S1存储的位置是后缀表达式的相反顺序,我们可以用一个数组来获取这个栈S1的元素,输出的时候,从最后一位往前输出即可,为什么用栈S1来存储后缀表达式而不用数组呢,因为栈操作起来非常方便,执行时只需调用出栈进栈等函数就可以,当然你也可以用数组直接存储。另一个栈S2用来存储算术表达式中的操作符,先出栈的操作符先执行运算!所以我们就需要设置一些条件来判断什么时间S2中的操作符出栈,什么时间,让当前扫描到的操作符进栈。
扫描当前算术表达式,获取当前字符C:有以下几个出栈进栈的规则:
(1)、如果c=‘(’左括号,直接把它存入到后缀表达式的栈S2中。
//如果是左括号,直接进栈
if(c=='(')
{
Push(&S2,c);
}
(2)、如果c=‘)’右括号,我们获取当前存储操作符的栈S2的栈顶元素,如果它不等于左括号‘(’,将栈S2的栈顶元素先进栈到S1中,然后S2执行出栈操作,接着再次获取栈S2的栈顶元素,继续执行(2)这个步骤,循环执行。直到第一次遇到‘(‘左括号,就停止循环,将’(‘从S2中出栈即可,继续扫描算术表达式的下一个字符。
else if(c==')')
{
char b=Get(S2);//获取存操作符的栈顶元素
while(b!='(')//遇见左括号之前一直循环出栈S2
{
Push(&S1,b);//存入到栈S1
Pop(&S2); //进行出栈S2
b=Get(S2);//再获取栈顶S2的元素
//printf("--------++%c\n",b);
}
Pop(&S2);
(3)、如果c!='+'并且c!='-'并且c!='*'并且c!='/'并且c!=')'。那我们就把它直接存入到后缀表达式的栈S1中。这里可以理解为c为数字或者是c为未知的一系列变量x。继续扫描算术表达式的下一个字符。
else if(c!='+'&&c!='-'&&c!='*'&&c!='/'&&c!=')')//根据题目变换
{
Push(&S1,c);
}
(4)、如果c=='+'或者c=='-',也就是我们现在遇到了加减这两个操作符,我们先判断一下栈S2是否为空栈,如果为空栈的话,那我们直接将当前的操作符c进入栈S2中即可。如果不为空的话,我们获取一下栈S2的栈顶元素是什么。如果是左括号’(‘,那我们也直接将当前的操作符c进入栈S2中即可;如果不是左括号’(‘,那我们就将栈S2的栈顶元素先进栈到S1中,然后让S2执行出栈操作,接着再次获取栈S2的栈顶元素,循环执行(4)这个步骤,直到第一次遇到‘(‘左括号,或者是栈为空就停止循环,将’(‘从S2中出栈即可,继续扫描算术表达式的下一个字符,如果没有遇到左括号,那我们就一直出栈,直到栈空,然后把c压入到栈S2中。继续扫描算术表达式的下一个字符。
else if(c=='+'||c=='-')
{
if(S2.top==-1) //如果操作符栈为空,直接进栈
{
Push(&S2,c);
}
else
{
b=Get(S2);
while(b!='(') //只要不为左括号或者是栈不为空,那就一直出栈
{
Push(&S1,b); //运算符存入到栈S1中
Pop(&S2);
if(S2.top!=-1) //栈不为空,执行一次出栈
{
b=Get(S2); //再获取栈顶
}
else
{
break;
}
}
Push(&S2,c);
}
}
(5)、如果c=='*'||c=='/',也就是我们遇到了乘除这两个操作符,我们先判断一下栈S2是否为空栈,如果为空栈的话,我们直接将当前的操作符c进入到栈S2中即可。如果不为空的话,我们获取一下栈S2的栈顶元素,如果b!='('并且b!='+'并且b!='-',这里是用的并且,不是用的或者,我这里就出现了问题。只有当它不为这三个时,同时成立。我们才将当前S2的栈顶元素压入到栈S1中,然后栈S2执行出栈操作,然后再次获取栈S2的栈顶元素,再循环执行(5),直到当栈为空或者是栈顶元素等于上面的三个中的一个,我们才停止循环,将当前的操作符c进入栈S2中。继续扫描算术表达式的下一个字符。
else if(c=='*'||c=='/')
{
if(S2.top==-1)
{
Push(&S2,c);
}
else
{
b=Get(S2);
while(b!='('&&b!='+'&&b!='-')
{
Push(&S1,b); //运算符存入到栈S1中
Pop(&S2);
if(S2.top!=-1) //执行一次出栈
{
b=Get(S2);
}
else
{
break;
}
}
Push(&S2,c);
}
}
(6)、当扫描完算术表达式的数组后,将栈S2中存储的操作符依次出栈并且进入到栈S1中,这时栈S1中的顺序是我们要求的后缀表达式的逆序,我们把这个逆序存入到一个数组中,将这个数组反向输出就是我们要求的后缀表达式。
while(S2.top!=-1) //将S2中剩余的操作符存入到栈S1中
{
char c=Get(S2);
Push(&S1,c);
Pop(&S2);
}
int a=S1.top; //记录后缀表达式的长度
for(int i=0;S1.data[S1.top]!=-1;i++) //将栈S1中的元素存入到数组中
{
data1[i]=S1.data[S1.top];
S1.top--;
}
for(int i=a;i>=0;i--) //反向输出数组即可
{
printf("%c",data1[i]);
}
下图是李春葆书中给出的将算术表达式转换成后缀表达式的步骤:看懂我的或者是看懂下面这张图都可以。
三、例题应用——一般算术表达式转换成后缀式
Description
对于一个基于二元运算符的算术表达式,转换为对应的后缀式,并输出之。
Input
输入一个算术表达式,以‘#’字符作为结束标志。
Output
输出该表达式转换所得到的后缀式。
Sample
Input
a*b+(c-d/e)*f#
Output
ab*cde/-f*+
(1)栈的基本操作函数的定义
//定义一个栈结构体
typedef struct
{
char data[Maxsize]; //存储元素
int top; //栈顶标记位
}Stack;
//初始化
void Init(Stack *L)
{
L->top=-1;
}
//进栈操作
void Push(Stack *L,char x)
{
if(L->top>=Maxsize)//判断是否栈满
{
return;
}
L->top++;
L->data[L->top]=x; //栈没满就将x入栈
}
//出栈操作
void Pop(Stack *L)
{
if(L->top==-1)//判断是否为空
{
return;
}
L->top--; //不空就将栈顶标记位减一
}
//获取栈顶元素
char Get(Stack L)
{
if(L.top==-1)
{
return 0;
}
else
{
return L.data[L.top];
}
}
(2)主函数
主函数中的代码块的意思我已经在第二部分问题分析里面进行了详细的介绍,可以集合这代码理解那些话,逻辑理清楚就很简单。
int main()
{
Stack S1;//存放最终的表达式
Stack S2;//存放运算符
Init(&S1);
Init(&S2);
char data[Maxsize];//存放用户输入的表达式
scanf("%s",data);
char data1[Maxsize];
char b;
for(int i=0;data[i]!='#';i++)
{
char c=data[i];
if(c=='(')
{
Push(&S2,c);
}
else if(c==')')
{
char b=Get(S2);//获取存操作符的栈顶元素
while(b!='(')//遇见左括号之前一直循环出栈S2
{
Push(&S1,b);//存入到栈S1
Pop(&S2); //进行出栈S2
b=Get(S2);//再获取栈顶S2的元素
}
Pop(&S2);
}
else if(c!='+'&&c!='-'&&c!='*'&&c!='/'&&c!=')')//根据题目变换
{
Push(&S1,c);
}
else if(c=='+'||c=='-')
{
if(S2.top==-1) //如果操作符栈为空,直接进栈
{
Push(&S2,c);
}
else
{
b=Get(S2);
while(b!='(') //只要不为左括号或者是栈不为空,那就一直出栈
{
Push(&S1,b); //运算符存入到栈S1中
Pop(&S2);
if(S2.top!=-1) //栈不为空,执行一次出栈
{
b=Get(S2); //再获取栈顶
}
else
{
break;
}
}
Push(&S2,c);
}
}
else if(c=='*'||c=='/')
{
if(S2.top==-1)
{
Push(&S2,c);
}
else
{
b=Get(S2);
while(b!='('&&b!='+'&&b!='-')
{
Push(&S1,b); //运算符存入到栈S1中
Pop(&S2);
if(S2.top!=-1) //执行一次出栈
{
b=Get(S2);
}
else
{
break;
}
}
}
}
}
while(S2.top!=-1)
{
char c=Get(S2);
Push(&S1,c);
Pop(&S2);
}
int a=S1.top;
for(int i=0;S1.data[S1.top]!=-1;i++)
{
data1[i]=S1.data[S1.top];
S1.top--;
}
for(int i=a;i>=0;i--)
{
printf("%c",data1[i]);
}
return 0;
}
(3)运行结果
(四)、完整代码
#include <stdio.h>
#include <stdlib.h>
#define Maxsize 1000
//定义一个栈结构体
typedef struct
{
char data[Maxsize]; //存储元素
int top; //栈顶标记位
}Stack;
//初始化
void Init(Stack *L)
{
L->top=-1;
}
//进栈操作
void Push(Stack *L,char x)
{
if(L->top>=Maxsize)//判断是否栈满
{
return;
}
L->top++;
L->data[L->top]=x; //栈没满就将x入栈
}
//出栈操作
void Pop(Stack *L)
{
if(L->top==-1)//判断是否为空
{
return;
}
L->top--; //不空就将栈顶标记位减一
}
//获取栈顶元素
char Get(Stack L)
{
if(L.top==-1)
{
return 0;
}
else
{
return L.data[L.top];
}
}
int main()
{
Stack S1;//存放最终的表达式
Stack S2;//存放运算符
Init(&S1);
Init(&S2);
char data[Maxsize];//存放用户输入的表达式
scanf("%s",data);
char data1[Maxsize];
char b;
for(int i=0;data[i]!='#';i++)
{
char c=data[i];
if(c=='(')
{
Push(&S2,c);
}
else if(c==')')
{
char b=Get(S2);//获取存操作符的栈顶元素
while(b!='(')//遇见左括号之前一直循环出栈S2
{
Push(&S1,b);//存入到栈S1
Pop(&S2); //进行出栈S2
b=Get(S2);//再获取栈顶S2的元素
}
Pop(&S2);
}
else if(c!='+'&&c!='-'&&c!='*'&&c!='/'&&c!=')')//根据题目变换
{
Push(&S1,c);
}
else if(c=='+'||c=='-')
{
if(S2.top==-1) //如果操作符栈为空,直接进栈
{
Push(&S2,c);
}
else
{
b=Get(S2);
while(b!='(') //只要不为左括号或者是栈不为空,那就一直出栈
{
Push(&S1,b); //运算符存入到栈S1中
Pop(&S2);
if(S2.top!=-1) //栈不为空,执行一次出栈
{
b=Get(S2); //再获取栈顶
}
else
{
break;
}
}
Push(&S2,c);
}
}
else if(c=='*'||c=='/')
{
if(S2.top==-1)
{
Push(&S2,c);
}
else
{
b=Get(S2);
while(b!='('&&b!='+'&&b!='-')
{
Push(&S1,b); //运算符存入到栈S1中
Pop(&S2);
if(S2.top!=-1) //执行一次出栈
{
b=Get(S2);
}
else
{
break;
}
}
}
}
}
while(S2.top!=-1)
{
char c=Get(S2);
Push(&S1,c);
Pop(&S2);
}
int a=S1.top;
for(int i=0;S1.data[S1.top]!=-1;i++)
{
data1[i]=S1.data[S1.top];
S1.top--;
}
for(int i=a;i>=0;i--)
{
printf("%c",data1[i]);
}
return 0;
}
(五)、总结
这道题让我做了一天,真的是我一点一点调试,在每一个条件里我都加上了一句打印语句,看一看是哪个条件出了问题,最后确定问题是出现在了当c=*或者是c=/这个判断上,当扫描到这个时,我们应该定义这个b!='('&&b!='+'&&b!='-'条件,而不是b!='('||b!='+'||b!='-',因为&&的意思是全真则为真,而||的意思是只要有一个为真就为真,就会执行条件里面的循环体。就比如我们拿一个例子:a+(b*c+d)#,当我们扫描到右括号)时,如果我们用的是b!='('||b!='+'||b!='-'这个条件,当我们扫描到*号时,我们直到此时操作符栈顶位左括号b=(,那我们用b!='('||b!='+'||b!='-'当作条件时,这个条件却是成立的,因为b此时确实不等于+,-。所以会执行里面的内容。但我们想表达的是当b=(,我们直接让*进栈,而不是让它出栈。
虽然今天就做了这一道题,但真的是让我刻苦铭心,有所收获,设置判断条件时,一定要正面和反面一起考虑,只有条件设置对了我们的程序才能按照我们想让它执行的方向去执行。加油!