数据结构—栈的应用–简单表达式求值问题
问题描述
用户输入一个包含+、-、*、/、正整数和圆括号的合法算数表达式,计算该表达式的结果。
设计思路
计算简单表达式的值,其实主要是要处理运算符优先级不同以及可能有括号,从而造成运算顺序不一定按照从左往右顺序计算。
因此,需要对算数表达式符号的顺序进行调整,使之能够以一种计算机能够直接方便理解的方式来顺序地计算。
栈的主要特点是:先入后出,后入先出。因此可以利用这一特点将栈作为一种存放临时数据的容器,后存入的数据先处理。从而来调整算术表达式的各种字符之间的顺序,方便计算机的计算。
什么是后缀表达式
根据运算符在操作数之间的不同位置,可分为前缀表达式,中缀表达式,后缀表达式。
这里,主要讨论中缀表达式以及后缀表达式。
中缀表达式:运算符位于两个操作数中间的表达式。
后缀表达式(逆波兰表达式):运算符位于操作数后面的表达式。
平时我们使用的算术表达式,其实就是中缀表达式,对中缀表达式的运算一般遵循"先乘除,再加减,从左往右计算,先计算括号内,再计算括号外"的规则。而另一种算术表达式的形式,后缀表达式,已经考虑了运算符的优先级,且不包含括号,越放在前面的运算符越优先执行。
例如:6+5*2的后缀表达式为:6 5 2 * +
将一个中缀表达式转换为后缀表达式,操作数之间的相对次序是不变的,但可能会改变运算符之间的相对次序同时要去除括号,因为运算符的优先级已经被考虑进去,从左往右,越放在前面的运算符越优先执行。所以对于计算机的运算来说,后缀表达式会比中缀表达式更加方便,因此选用后缀表达式进行运算。
那么解决简单运算符的计算这个问题,就变为了如何将中缀表达式转换为后缀表达式和如何计算后缀表达式这两个小问题了。
将算数表达式转换为后缀表达式
在进行转换时,需要从左到右扫描算数表达式exp,将遇到的操作数直接存放在后缀表达式中,这里用数组postexp来存放,将遇到的每一个运算符或者左括号都暂时存在运算符栈Optr中,先执行的运算符先出栈。下面,分别讨论对不同运算符或者圆括号的不同处理情况。
- 若遇到’(’ ,直接将’('进栈;
- 若遇到’)’,一直出栈元素,并将出栈了的元素存入postexp中,直至出栈元素为’(’。
- 若遇到’+‘或’-’,判断运算符栈是否为空,若为空,则直接进栈;若不为空,判断栈顶元素是否为’(’,将不为’(‘的字符全部出栈存于postexp中,再将’+‘或’-‘进运算符栈,如果栈顶元素是’(’,则直接将’+‘或’-'进栈。
- 若遇到’*‘或’/’,判断运算符栈是否为空,若为空,则直接进栈;若不为空,判断栈顶元素是否为’‘或’/’,若是,则将栈顶元素出栈存于postexp中,再将当前的’‘或’/'存于运算符栈,若不是,直接进栈。
- 若遇到数字字符,则依次存于postexp中,并用’#'来标识字符串的结束。
- 当exp扫描结束时,判断运算符栈是否为空,若为空增,则给postexp表达式添加结束标识,如果不为空,则依次出栈元素存于postexp表达式中,再给postexp表达式添加结束标识。
小结:从exp算术表达式中依次读取字符,
__如果字符为’(’,将此括号直接进栈Optr;
__如果字符为’)’,将Optr依次出栈并存于postexp中,直到遇到’(’,再将左括号出栈;
__如果字符为数字,则将后续所有数字依次存于postexp中,并用’#‘标志字符串结束;
__如果为其他字符,先判断栈空或栈顶元素为’(’,若是,则直接进栈,若不是则根据栈顶元素和当前字符的优先级进行比较,若当前运算符的优先级更高,则直接进栈,若栈顶元素的优先级更高,则将栈顶元素出栈存于postexp中,将当前元素进栈。
这部分代码如下:
void trans(char * exp,char postexp[])//将算数表达式exp转换为后缀表达式postexp
{
int i=0;//用来记录postexp中的元素个数
char e;
SqStack * Optr;//定义一个顺序栈,用来存放运算符
InitStack(Optr);//初始化栈
while(* exp!='\0')//当表达式未扫描完时循环
{
switch(* exp)
{
case '+':
case '-':
while(!StackEmpty(Optr))//栈不为空时循环
{
GetTop(Optr,e);
if(e!='(')
{
postexp[i++]=e;
Pop(Optr,e);//出栈
}
else
break;
}
Push(Optr,* exp);//进栈
exp++;
break;
case '*':
case '/':
while(!StackEmpty(Optr))
{
GetTop(Optr,e);
if(e=='*'||e=='/')
{
postexp[i++]=e;
Pop(Optr,e);
}
else
break;
}
Push(Optr,* exp);
exp++;
break;
case '(':
Push(Optr,* exp);
exp++;
break;
case ')':
Pop(Optr,e);//出栈元素e
while(e!='(')
{
postexp[i++]=e;
Pop(Optr,e);
exp++;
break;
default://处理数字字符
{
while(* exp>='0'&&* exp<='9')
{
postexp[i++]=* exp;
exp++;
}
postexp[i++]='#';//用#标识一个字符串的结束
}
}
while(!StackEmpty(Optr))
{
Pop(Optr,e);//出栈
postexp[i++]=e;//存入后缀表达式postexp中
}
postexp[i]='\0';
DestroyStack(Optr);
}
计算后缀表达式的值
求解后缀表达式的值时,需要设置一个操作数栈Opnd,然后从左往右扫描后缀表达式postexp,若读取的是一个操作数,则将它进操作数栈;若读取的是一个运算符,则从操作数栈中连续出栈两个操作数,进行运算,并将结果进操作数栈。将整个后缀表达式扫描结束后,栈中的栈顶元素就是表达式的计算结果。
这部分代码如下:
double compvalue(char * postexp)//计算后缀表达式的值
{
double a,b,c,d,e;
SqStack1 * Opnd;//定义一个操作数栈
InitStack1(Opnd);//初始化操作数栈
while(* postexp!='\0')//postexp字符串未扫描完时循环
{
switch(* postexp)
{
case '+':
Pop1(Opnd,a);
Pop1(Opnd,b);
c=b+a;
Push1(Opnd,c);
break;
case '-':
Pop1(Opnd,a);
Pop1(Opnd,b);
c=b-a;
Push1(Opnd,c);
break;
case '*':
Pop1(Opnd,a);
Pop1(Opnd,b);
c=b*a;
Push1(Opnd,c);
break;
case '/':
Pop1(Opnd,a);
Pop1(Opnd,b);
if(a==0)
{
printf("分母为0,运算异常!\n");
exit(0);
}
else
{
c=b/a;
Push1(Opnd,c);
break;
}
break;
default://处理数字字符
d=0;
while(* postexp>='0'&&* postexp<='9')
{
d=10*d+* postexp-'0';
postexp++;
}
Push1(Opnd,d);
break;
}
postexp++;
}
GetTop1(Opnd,e);
DestroyStack1(Opnd);
return e;
}
完整代码
//简单表达式求值
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#define Maxsize 100
typedef struct//定义顺序栈类型
{
char data[Maxsize];
int top;
}SqStack;
typedef struct
{
double data[Maxsize];
int top;
}SqStack1;
void InitStack(SqStack * &s)//初始化栈
{
s=(SqStack *)malloc(sizeof(SqStack));
s->top=-1;
}
void InitStack1(SqStack1 * &s)
{
s=(SqStack1 *)malloc(sizeof(SqStack1));
s->top=-1;
}
bool StackEmpty(SqStack * &s)//判断栈是否为空
{
return(s->top==-1);
}
bool StackEmpty1(SqStack1 * &s)
{
return(s->top==-1);
}
void DestroyStack(SqStack * &s)//销毁栈
{
free(s);
}
void DestroyStack1(SqStack1 * &s)
{
free(s);
}
bool GetTop(SqStack *s,char &e)//取栈顶元素
{
if(s->top==-1)
return false;
e=s->data[s->top];
return true;
}
bool GetTop1(SqStack1 *s,double &e)
{
if(s->top==-1)
return false;
e=s->data[s->top];
return true;
}
bool Pop(SqStack * &s,char &e)//出栈
{
if(s->top==-1)
return false;
e=s->data[s->top];
s->top--;
return true;
}
bool Pop1(SqStack1 * &s,double &e)
{
if(s->top==-1)
return false;
e=s->data[s->top];
s->top--;
return true;
}
bool Push(SqStack * &s,char &e)//进栈
{
if(s->top==Maxsize-1)//栈上溢出
return false;
s->top++;
s->data[s->top]=e;
return true;
}
bool Push1(SqStack1 * &s,double &e)
{
if(s->top==Maxsize-1)
return false;
s->top++;
s->data[s->top]=e;
return true;
}
void trans(char * exp,char postexp[])//将算数表达式exp转换为后缀表达式postexp
{
int i=0;//用来记录postexp中的元素个数
char e;
SqStack * Optr;//定义一个顺序栈,用来存放运算符
InitStack(Optr);//初始化栈
while(* exp!='\0')//当表达式未扫描完时循环
{
switch(* exp)
{
case '+':
case '-':
while(!StackEmpty(Optr))//栈不为空时循环
{
GetTop(Optr,e);
if(e!='(')
{
postexp[i++]=e;
Pop(Optr,e);//出栈
}
else
break;
}
Push(Optr,* exp);//进栈
exp++;
break;
case '*':
case '/':
while(!StackEmpty(Optr))
{
GetTop(Optr,e);
if(e=='*'||e=='/')
{
postexp[i++]=e;
Pop(Optr,e);
}
else
break;
}
Push(Optr,* exp);
exp++;
break;
case '(':
Push(Optr,* exp);
exp++;
break;
case ')':
Pop(Optr,e);//出栈元素e
while(e!='(')
{
postexp[i++]=e;
Pop(Optr,e);
}
exp++;
break;
default://处理数字字符
while(* exp>='0'&&* exp<='9')
{
postexp[i++]=* exp;
exp++;
}
postexp[i++]='#';//用#标识一个字符串的结束
}
}
while(!StackEmpty(Optr))
{
Pop(Optr,e);//出栈
postexp[i++]=e;//存入后缀表达式postexp中
}
postexp[i]='\0';
DestroyStack(Optr);
}
double compvalue(char * postexp)//计算后缀表达式的值
{
double a,b,c,d,e;
SqStack1 * Opnd;//定义一个操作数栈
InitStack1(Opnd);//初始化操作数栈
while(* postexp!='\0')//postexp字符串未扫描完时循环
{
switch(* postexp)
{
case '+':
Pop1(Opnd,a);
Pop1(Opnd,b);
c=b+a;
Push1(Opnd,c);
break;
case '-':
Pop1(Opnd,a);
Pop1(Opnd,b);
c=b-a;
Push1(Opnd,c);
break;
case '*':
Pop1(Opnd,a);
Pop1(Opnd,b);
c=b*a;
Push1(Opnd,c);
break;
case '/':
Pop1(Opnd,a);
Pop1(Opnd,b);
if(a==0)
{
printf("分母为0,运算异常!\n");
exit(0);
}
else
{
c=b/a;
Push1(Opnd,c);
break;
}
break;
default://处理数字字符
d=0;
while(* postexp>='0'&&* postexp<='9')
{
d=10*d+* postexp-'0';
postexp++;
}
Push1(Opnd,d);
break;
}
postexp++;
}
GetTop1(Opnd,e);
DestroyStack1(Opnd);
return e;
}
int main()
{
char postexp[Maxsize];
char exp[Maxsize];
printf("请输入您要计算的表达式:\n");
gets(exp);
trans(exp,postexp);
printf("中缀表达式:%s\n",exp);
printf("后缀表达式:%s\n",postexp);
printf("表达式的值:%g\n",compvalue(postexp));//%g格式输出实数不会输出没有意义的0
return 1;
}
运行框图