前言
表达式求值是程序设计语言编译中的一个最基本问题,其实现是栈应用的又一个典型例子。“算符优先法”,是一种简单直观、广为使用的表达式求值算法。
要把一个表达式翻译成正确求值的一个机器指令序列,或者直接对表达式求值,首先要能够正确解释表达式。算符优先法就是根据算术四则运算规则确定的运算优先关系,实现对表达式的编译或解释执行的。
在表达式计算中先出现的运算符不一定先运算,具体运算顺序是需要通过运算符优先关系的比较,确定合适的运算时机,而运算时机的确定是可以借助栈来完成的。将扫描到的不能进行运算的运算数和运算符先分别压入运算数栈和运算符栈中,在条件满足时再分别从栈中弹出进行运算。
案例分析
表达式分析
任何一个表达式都是由**操作数(operand)、运算符(operator)和界限符(delimiter)**组成的,统称它们为单词。
一般地,操作数既可以是常数,也可以是被说明为变量或常量的标识符;运算符由运算对象上来说由单目,双目和三目运算符;基本界限符有左右括号和表达式结束符等。
为了叙述的简洁,在此仅讨论简单算术表达式的求值问题,这种表达式只含加、减、乘、除4种双目运算符,并且操作数只有一位。
读者不难将它推广到更一般的表达式上。
下面把运算符和界限符统称为算符。
在表达式求值时,表达式一般有三种表达方式:
后缀表达:<操作数><操作数><运算符>
中缀表达:<操作数><运算符><操作数>
后缀表达:<运算符><操作数><操作数>
日常生活我们用的都是中缀表达式:1+2*(8-5)-4/2。
后缀表达式解释
由于中级表示中有算符的优先级问题,有时还采用括号改变运算顺序,因此一般在 表达式求值中,较少采用中缀表示,在编译系统中更常见的是采用后缀表示。
1+2*(8-5)-4/2的计算顺序和用后缀表示的计算顺序如图所示。
1)后缀表达式(也称逆波兰式)求值 由于后缀表达式的操作数总在运算符之前,并且表达式中即无括号又无优先级的约 束,算法比较简单。
具体做法是,只使用一个操作数栈,当从左向右扫描表达式时,每遇 到一个操作数就送人栈中保存,每遇到一个运算符就从栈中取出两个操作数进行当前的 计算,然后把结果再人栈,直到整个表达式结束,这时送人栈顶的值就是结果。 下面是后缀表达式求值的算法,在下面的算法中假设,每个表达式是合乎语法的,并且假设后缀表达式已被存人一个足够大的字符数组A中,且以#为结束字符。
表达式求值算法实现(后缀表达式)
编译环境
头文件在博主博客中:
https://blog.csdn.net/Liu1_1_/article/details/133855448
/*表达式求值*/
#include<stdio.h>
#include"Seqstack.h"
算法代码
typedef char DataType; //更改栈元素
//判断字符是否为操作数
int IsNum(char c)
{
/*判断字符是否为操作数 若是返回1 否则返回0*/
if (c >= '0' && c <= '9')
return 1;
else
return 0;
}
double postfix_exp(char* A)
{
/*本函数返回由后缀表达式A表达的表达式运算结果*/
PSeqStack S;
double Result, a, b, c;
char ch;
ch = *A++;
S = Init_SeqStack(); /*初始化栈*/
while (ch != '#')
{
if (IsNum(ch)) Push_SeqStack(S, ch - '0');
else
{
Pop_SeqStack(S, &b); //取出两个运算量
Pop_SeqStack(S, &a);
switch (ch)
{
case '+':c = a + b; break;
case '-':c = a - b; break;
case '*':c = a * b; break;
case '/':c = a / b; break;
case '%':c = (int)a % (int)b; break;
}
Push_SeqStack(S, c);
}
ch = *A++;
}
GetTop_SeqStack(S, &Result); //取得答案
Destroy_Seqstack(&S);
return Result;
}
中缀表达式与后缀表达式转换
转换分析
根据中缀表达式中算术运算规则:
式子:<操作数>θ₁<操作数>θ₂<操作数>中θ₁θ₂运算优先级如图所示。为简单起见算符仅限图 所表示的几种,操作数仅限个位数。
详解步骤
表达式作为一个满足表达式语法规则的串存储,转换过程:
初始化一个算符栈,并将结束符’#放人栈中,然后自左向右扫描表达式,直到扫描到#并且当前栈顶也是#时结束。
当扫描到的是操作数时直接输出,扫描到算符时不能马上输出,因为后面可能还有更高优先级的运算,要对下列几种情况分别处理。
(1)算符栈栈顶算符是(如果当前扫描到的算符是则算法出不作任何处理同时扫描下个字符,此过程称为脱括号;如果当前扫描到的算符不是》,则当前算符进栈。
2)算符栈栈顶算符不是并且算符顶算符优先级比当前扫到的算符优先级低,则人栈;若算符栈栈顶算符优先级比当前扫描到的算符优先级高(或相等),则从算符栈出栈并输出,当前算符继续与新的栈顶算符比较。
如表达式“1+2*(8-5)-4/2#”的转换过程如图所示。
为简单起见,先在栈中放人一个结束符#
转换步骤分析
上述操作的算法步骤如下。
(1)初始化算符栈 s将结符#加算符栈s中
(2)读表达式字符=>w。
(3)当栈顶为#并且w也是# 时结束;否则循环做下列步骤
(3-1)如果w 是操作数,直接输出,读下一个字符=>w;转步骤(3)
(3-2)w 若是算符,则:
(3-2-1)如果顶为(并且w为则出栈不输出,读下一个字符=>w;
转步骤(3)。
(3-2-2)如果栈顶为(或者栈顶优先级小于 w优先级,则w人读下一个字符->w;转步骤(3)。否则:从算符栈中出栈并输出,转步骤(3)
为实现上述算法,要能够判断表达式字符是否是操作数(只考虑一位操作数),要能够比较出算符的优先级,因此用下面的两个函数实现。
//(1)判断字符是否位操作数。若是返回 1,否则返回0,实现方法如下:
int IsNum(charc)
{
if(c>='0'&& c<='g') return(1);
else return(0);
}
//(2)求算符优先级
int priority(char op)
{
switch(op)
case'#:return(1);
case')';return(2);
casei+':
case'-'; return(3);
case1*1: /*按照表格给每个算符定优先级*/
case';return(4);
casel(;return(5)
default: return(0);
}
将中缀表达式转换成后缀表达式的具体算法如下所示。
代码实现
/*求运算优先级别*/
int priority(char op)
{
switch (op)
{
case '#':return 1;
case ')':return 2;
case'+':
case'-':return 3;
case'*':
case '/':return 4;
case'(':return 5;
default:return 0;
}
}
/*中缀表达式变成后缀表达式*/
int infix_exp_value(char* infixexp, char* postifixexp)
{
PSeqStack S;
char c, w, topelement;
S = Init_SeqStack(); //初始化栈
if (!S)
{
printf("初始化失败");
return 0;
}
Push_SeqStack(S, '#'); //运算符栈放入#
w = *infixexp;
while ((GetTop_SeqStack(S, &c),c) != '#' || w != '#') //栈顶元素不是#或w不是),脱括号
{
if (IsNum(w))
{
*postifixexp = w;
postifixexp++;
w = *(++infixexp);
}
else
{
if ((GetTop_SeqStack(S, &c), c) == '(' && w == ')') //栈顶是‘(’且栈外是‘)’脱括号
{
Pop_SeqStack(S, &topelement); //出栈
w = *(++infixexp);
}
else
{
if ((GetTop_SeqStack(S, &c), c) == '(' || priority(GetTop_SeqStack(S, &c)) < priority(w))
{
Push_SeqStack(S,w);
w=*(++infixexp);
}
else
{
Pop_SeqStack(S, &topelement);
*postifixexp = topelement;
postifixexp++;
}
}
}
}
*postifixexp = '#';
*(++postifixexp) = '\0'; //添加字符串结束符号
Destroy_Seqstack(&S); //销毁栈
return 1;
}
算法如表格:
主函数实现
int main_duilie()
{
char str[100] = "1285-*+42/-#";
printf("result=%f", postfix_exp(str));
}
如有疑惑请联系博主!