将混合运算表达式,转化为后缀表达式

后缀式


后缀式即逆波兰式,是波兰逻辑学家卢卡西维奇(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;
}

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值