后缀式
后缀式即逆波兰式,是波兰逻辑学家卢卡西维奇(Lukasiewicz)发明的一种表示表达式的方法。
在上一讲 , 我们已经了解了,混合运算表达式转化为后缀表达式后 , 可以程序化运算结果 ,
现在我们的问题是, 如何将一个混合运算表达式 转化为后缀表达式.
注:后续表达式包括,加减乘除 括号 等
我们现在用人脑来模拟 ,转化后缀表达式的过程
初始的时候 , 我们从混合表达式里面取出 a
发现是数字 , 就存入 postexp
接着遍历exp 第二个字符 ,
发现是 + 号
因为我们要把 + 号放到两个字符后面, 所以先把 + 入栈到运算符栈OP ,(因为要将遍历到的运算符和栈顶运算符比较,才能进行插入,所以规定 "=" 优先级最低,提前入栈)
下面遍历 exp 的第三个字符,b ,加入 postexp
接着遍历第四个字符
是 乘号 ,与栈顶运算符的优先级比较, 比 + 高, 所以 + 号不急着运算 ,先把 乘号入栈
遍历第五个字符 , c ,进入 postexp
遍历第六个字符 ,减号 , - 和 栈顶元素 乘号优先级比较,没有 乘高 ,所以 乘号就可以先运算了
弹出乘号 ,进入 postexp ,然后 - 号 再和 栈顶的加号比较 ,加号在前面 ,所以 + 号也可以出栈 ,
参与运算 ,进入 postexp ,最后 - 号比 = 优先级高 ,所以入栈
接着遍历第七个元素 d ,第八个元素 ,都是数字,相继入栈
进入 postexp
第九个元素 , 除号
和栈顶元素 负号 比较 ,比 减号优先级高 ,所以入栈
第十个元素 乘号 和 栈顶元素 除号 比较 , 优先级理论来说一样 ,但是 / 在前面 ,弹出 \ ,入 postexp ,然后乘号入栈
第十一个元素 f ,进入 postexp
第十二个元素 "\0" 遍历到字符串的结束标志元素 '\0'
就把栈内的符号 ,全部依次出栈并压入 postexp(等号不是运算符,表示连接字符)
这样 ,我们就把后缀式 用我们的常识运算出来了
现在我们从中提取一些思路 ,或者能够程序化的步骤
我们在判断 加减乘除, 等号 ,括号 优先级的时候 ,
如果是
●在我们常识中同等优先级的运算符 ,在前面的优先级高 ,
●优先级不同的运算符 ,不受先后顺序影响
●当我们把一个运算符入栈后 ,当有和其同级的运算符想要入栈时,因为先入栈,优先级会比后入栈的优先级高,所以会先将栈顶符号弹出 , 然后把栈顶运算符和 入栈运算符比较
●当要入栈的运算符 比栈顶的优先级高的时候 ,我们会直接把运算符压入栈中 ,因为插入前的栈顶元素优先级低 ,不急着运算
所以我们为了比较 运算符的优先级 ,最好的办法是为其标号 ,
靠前的同等优先级 比靠后 的同等优先级 高一点
现在我们开始设置遍历算术运算式设 ch 就是遍历的指针
当ch 遍历到运算符的时候, 对比栈顶运算符的优先级 从而判断是否进栈
我们设 ,已经进入栈的运算符 是左运算符
ch 遍历到的未进入栈的运算符,称作右运算符
所以 ,科学家就设计了如图所示的优先级对比图 ,通运算级的 ,左边的运算级比右边的同等优先级高一点 ,但是也没有 更高的优先级高 ,
这种区分 ,只是相对顺序的区分对比
我们看左括号 ,括号可能有多个嵌套 ,所以后进去的左括号优先级比先进去左括号高 .
然后 右括号 ,也是包裹类型 ,先进去的右括号 ,已经包裹了一定数量的运算符了 ,所以优先级更高一点 ,要进去的右括号 ,需要等所有元素处理完再进去 ,所以优先级低一点
我们的目的是 ,完成后缀式 ,后缀式是没有括号的,我们的目的,也是要消除括号
我们知道 , 括号里面的要先运算 ,我们把括号里看成一个整体 ,
当我们在把整个运算式子用括号括起来的时候 ,我们第一次遍历的符号就是左括号 ,然后最后一个符号是右括号 , 我们把 左括号和右括号里面的符号全部加入到了后缀式
类比一下 ,当我们把 左括号要入栈的时候 ,直接无条件入栈 ,因为其运算级很高 ,然后当我们运行一段时间后 , 遇到 右括号 ,此时左括号和右括号要做照应 ,我们要把两个括号中间的所有符号弹出栈 ,加入后缀式 ,以示其是一个整体
我们的理论和演示已经准备充分了 ,下面开始后缀式的程序化设计:
我们用人脑遍历混合运算的时候, 遇到字符我们就把他记录下来,遇到运算符,我们会把他放在
运算符栈里面 ,然后接着遍历 ,再次遇到运算符的话,就要和栈顶的运算符进行优先级比较,然后再进行下一步操作了
在不读取到字符串 '\0' 的时候 , 要么是 数字字符 ,要么是运算符字符 , 我们依次进行相应的操作:
while(ch != '\0')
{
if(ch不为运算符)
将后续的所有数字一次存放到 postexp中,并以字符 # 标志数字串结束;
因为中缀表达式是一串字符串,例如: " 44+55*66-9/3 "
运算数 都是通过运算符进行分隔的 ,
利用这个特性我们可以找到运算符夹到中间的就是一个运算数 ,然后我们得到数字,在遍历到运算符前,就可以把数字字符转化为数值 ,然后用 #分隔开,方便后续遍历
即 "44#55#66#*+9#3#/-"
else
// 我们遍历的字符 ch , 不是数字 就是运算符 ,我们要判断是 ch 运算符和 运算符栈的栈顶的运算符 的优先级 ,然后再进一步进行操作,完成后缀式
//我们先创建一个 Precede () 函数接口 ,调用传回优先级的大小关系, 后续再去具体实现
switch(Precede(op栈顶运算符,ch ))
{
无非有三种情况 ,①栈顶运算符优先级低 ,那就把 ch 里运算符 压入栈顶 ,然后 ch 再接着遍历下一个字符
case '<':
将ch进栈 ;从 exp 读取下一个字符 ch ;
break; //跳出,接着循环对比栈内运算符
②栈顶运算符优先级 高 ,那就需要先运算栈里面的运算了, 把栈顶运算符弹出,加入后缀式 ,然后跳出 ,接着让 ch 和栈顶元素比较 ,ch 里元素不变
case '>':
栈顶运算符出栈并存放到postexp中,
break; 跳出,接着循环对比 栈顶运算符 和 ch 运算符的优先级
③栈顶元素 和 ch 优先级相等 ,这时候只有一种可能 ,/只有栈顶为'(' , 要进栈的运算符为")"的情况下,左括号和右括号汇合 ,我们这里就是为了消除括号,我们只需要把括号删除就行了
case '=':
将运算符栈顶的' ( ' 弹出栈 ,删除就行了接着 ch 读取下一个字符
break;//跳出
}
}
直到 我们读取到 了 '\0 ' ,代表我们的后缀表达式也完成了 ,
但是我们刚才调用的接口 ,传入 栈顶运算符 和 遍历的 ch 运算符 ,返回 优先级比较的结果;进而我们才能进行上面我们的操作.
所以,我们现在要构建优先级的比较:
参考表:
我们以上规则 , 用优先级标号的形式展现了 ,
●同类型优先级运算符比较 ,靠前的运算级数值大一点
●不同类型的优先级运算符比较,优先级高的 比优先级低的运算符 数值大
●对于括号 ,我们是为了删除括号 ,所以要进去的右括号 ,优先级小,所以就出栈运算符栈里的符号 , 直到遇到左括号 ,将其弹出栈 ,就相当于删除了括号
定义运算符优先级结构
struct { char ch; //运算符 int pri; //优先级 } lpri[] = {{'=',0},{'(',1},{'*',5},{'/',5},{'+',3},{'-',3},{')',6}}, rpri[] = {{'=',0},{'(',6},{'*',4},{'/',4},{'+',2},{'-',2},{')',1}};
接下来就是返回 ch 和 栈顶运算符的比较结果
// 我们定义 op1 为栈顶运算符 , op2 为遍历到的 ch 运算符
int Precede(char op1,char op2) { if(leftpri(op1) == rightpri(op2)) //栈顶运算符是左括号 ,ch 是右括号 return 0; else if(leftpri(op1) < rightpri(op2)) //栈顶运算符优先级小于 ch 优先级 return -1; else //栈顶运算符优先级大于 ch 优先级 return 1; }
我们先构建了返回优先级的大致框架 ,那如何获得优先级呢?
我们创建
int rightpri(char op){} int leftpri(char op){ }
如何返回运算符的优先级呢?
那当然是,把这个运算符拿去遍历定义的数组结构了
//求 遍历指针 ch里运算符 的优先级
int rightpri(char op) { for(int i; i<MaxOp;i++) { if(rpri[i].ch == op) return rpri[i].pri; } }
//求栈顶运算符的优先级
int leftpri(char op) { for(int i; i<MaxOp;i++) { if(lpri[i].ch == op) return lpri[i].pri; } }
判断 ch 是否为运算符
bool Inop(char ch) { if(ch == '(' || ch == ')' || ch == '+' || ch == '-'|| ch == '*'|| ch == '/') return true; else return false; }
以上就是运算符优先级的处理模块
接下来 ,我们就利用上述模块, 将算术表达式 exp 转换成后缀表达式 postexp
思路:就是我们上述,遇到 栈顶运算符 和 遍历到 ch 里的运算符优先级不同情况的处理
传入 算术运算式 *exp , 存储后缀式的数组
void trans(char *exp ,char postexp[]) {
接下来定义存储运算符栈的数据结构
struct { char data[MaxSize]; //MaxSize最大字符串长度,是我们规定的,即输入的算术运算符字符串长度 int top; //定义栈顶指针 }op;
初始化栈
栈顶指针指向 -1
op.top = -1;
我们为了能够让栈顶运算符和 ch 比较 ,初始的时候 ,先向栈顶插入 优先级最低的 '=' ,便于后续的插入比较
//栈顶上移 op.top++; //栈顶指针插入 等号 op.data[op.top] = '=';
//接下来就开始出来 exp 中的每一个字符了
遵照我们之前的思路://在不读取到字符串 '\0' 的时候 , 要么是 数字字符 ,要么是运算符字符 , 我们依次进行相应的操作:
while(*exp !='\0') {
if(ch不为运算符)
将后续的所有数字一次存放到 postexp中,并以字符 # 标志数字串结束;if(!InOp(*exp)) //当不是"加减乘除括号等"时,就是数字字符了 { //因为算术表达式是字符串 ,所以我们用字符的Ascll码来区分自负 while(*exp >= '0' & *exp <= '9') { //此时,如果是数字就需要向 后缀式插入数字字符了,直到遇到分隔数字的运算符 //先插入把数字插入到后缀式数字,数组序号再自增 postexp[i++] = *exp; //插入后,需要遍历算术表达式exp的下一个字符 ,exp也是字符数组 //exp是数组初始地址,累加后,逐个遍历数组元素 exp++; } //当数字插入完后 ,插入 # 作为一个数值的结束 postexp[i++] = '#'; }
因为中缀表达式, " 44+55*66-9/3 " 多位数都是通过运算符进行分隔的 ,并且这一串是字符串
利用这个特性我们可以找到运算符夹到中间的就是一个运算数 ,然后我们得到数字,在遍历到运算符前,就可以把数字字符转化为数值 ,然后用 #分隔开,方便后续遍历
即 "44#55#66#*+9#3#/-"
//当 遍历到exp字符为运算符得时候,
else{
我们判断三种情况
//调用比较优先级的模块 ,传入 运算符栈的栈顶运算符 和 遍历到 算术表达式字符 里面的运算符 switch(Precede(op.data[op.top] , *exp )) {
无非有三种情况
①栈顶运算符优先级低 ,那就把 ch 里运算符 压入栈顶 ,然后 ch 再接着遍历下一个字符
case '<':
将ch进栈 ;从 exp 读取下一个字符 ch ;
break; //跳出,接着循环对比栈内运算符case -1 : //栈顶运算符的优先级低: 进栈 op.top++; op.data[op.top] = *exp; //继续扫描其他字符 exp++; //跳出,继续将 栈顶和扫描得字符进行对比 break;
②栈顶运算符优先级 高 ,那就需要先运算栈里面的运算了, 把栈顶运算符弹出,加入后缀式 ,然后跳出 ,接着让 ch 和栈顶元素比较 ,ch 里元素不变
case '>':
栈顶运算符出栈并存放到postexp中,
break; 跳出,接着循环对比 栈顶运算符 和 ch 运算符的优先级case 1: //将栈顶运算符输出到后缀式 postexp[i++] = op.data[op.top]; //栈顶指针下移 op.top--; //继续将算术表达式里遍历到的运算符和 栈顶指针比较 //跳出,继续循环比较 break;
③栈顶元素 和 ch 优先级相等 ,这时候只有一种可能 ,/只有栈顶为'(' , 要进栈的运算符为")"的情况下,左括号和右括号汇合 ,我们这里就是为了消除括号,我们只需要把括号删除就行了
case '=':
将运算符栈顶的' ( ' 弹出栈 ,删除就行了接着 ch 读取下一个字符
break;//跳出
}case 0: //只有括号,这种情况,所以我们只需要将栈顶元素出栈,然后继续遍历算术表达式里的元素就可以了 op.top--; exp++; break; } } }
这样我们就把中缀表达式转化为后缀表达式的算法设计好了:
我们下面看一下整体轮廓:
完整代码如下:
#include <stdio.h>
#include <stdlib.h>
#define MaxOp 100
#define MaxSize 100
struct //设定运算符优先级
{
char ch; //运算符
int pri; //优先级
}
lpri[]= {{'=',0},{'(',1},{'*',5},{'/',5},{'+',3},{'-',3},{')',6}},
rpri[]= {{'=',0},{'(',6},{'*',4},{'/',4},{'+',2},{'-',2},{')',1}};
int leftpri(char op) //求左运算符op的优先级
{
int i;
for (i=0; i<MaxOp; i++)
if (lpri[i].ch==op)
{
return lpri[i].pri;
}
return 0;
}
int rightpri(char op) //求右运算符op的优先级
{
int i;
for (i=0; i<MaxOp; i++)
if (rpri[i].ch==op)
return rpri[i].pri;
return 0;
}
bool InOp(char ch) //判断ch是否为运算符
{
if (ch=='(' || ch==')' || ch=='+' || ch=='-'
|| ch=='*' || ch=='/')
return true;
else
return false;
}
int Precede(char op1,char op2) //op1和op2运算符优先级的比较结果
{
if (leftpri(op1)==rightpri(op2))
return 0;
else if (leftpri(op1)<rightpri(op2))
return -1;
else
return 1;
}
void trans(char *exp,char postexp[])
//将算术表达式exp转换成后缀表达式postexp
{
struct
{
char data[MaxSize]; //存放运算符
int top; //栈指针
} op; //定义运算符栈
int i=0; //i作为postexp的下标
op.top=-1;
op.top++; //将'='进栈
op.data[op.top]='=';
while (*exp!='\0') //exp表达式未扫描完时循环
{
if (!InOp(*exp)) //为数字字符的情况
{
while (*exp>='0' && *exp<='9') //判定为数字
{
postexp[i++]=*exp;
exp++;
}
postexp[i++]='#'; //用#标识一个数值串结束
}
else //为运算符的情况
switch(Precede(op.data[op.top],*exp))
{
case -1: //栈顶运算符的优先级低:进栈
op.top++;
op.data[op.top]=*exp;
exp++; //继续扫描其他字符
break;
case 0: //只有括号满足这种情况
op.top--; //将(退栈
exp++; //继续扫描其他字符
break;
case 1: //退栈并输出到postexp中
postexp[i++]=op.data[op.top];
op.top--;
break;
}
} //while (*exp!='\0')
while (op.data[op.top]!='=')
//此时exp扫描完毕,退栈到'='为止
{
postexp[i++]=op.data[op.top];
op.top--;
}
postexp[i]='\0'; //给postexp表达式添加结束标识
}
float compvalue(char exp[]) //计算后缀表达式的值
{
struct
{
float data[MaxSize]; //存放数值
int top; //栈指针
} st; //定义数值栈
float d;
char ch;
int t=0; //t作为exp的下标
st.top=-1;
ch=exp[t];
while (ch!='\0') //exp字符串未扫描完时循环
{
switch (ch)
{
case'+':
st.data[st.top-1]=st.data[st.top-1]+st.data[st.top];
st.top--;
t++;
break;
case '-':
st.data[st.top-1]=st.data[st.top-1]-st.data[st.top];
st.top--;
t++;
break;
case '*':
st.data[st.top-1]=st.data[st.top-1]*st.data[st.top];
st.top--;
t++;
break;
case '/':
if (st.data[st.top]!=0)
{
st.data[st.top-1]=st.data[st.top-1]/st.data[st.top];
}
else
{
printf("\n\t除零错误!\n");
exit(0); //异常退出
}
st.top--;
t++;
break;
default:
d=0; //将数字字符转换成数值存放到d中
while (ch>='0' && ch<='9') //为数字字符
{
d=10*d+ch-'0';
t++;
ch=exp[t];
}
t++;
st.top++;
st.data[st.top]=d;
}
ch=exp[t];
}
return st.data[st.top];
}
int main()
{
char exp[]="(56-20)/(4+2)-33+9*32"; //可将exp改为键盘输入
char postexp[MaxSize];
trans(exp,postexp);
printf("中缀表达式:%s\n",exp);
printf("后缀表达式:%s\n",postexp);
printf("表达式的值:%g\n",compvalue(postexp));
return 0;
}