数据结构之栈----PTA题目7-20表达式转换(中缀转后缀)

一、前言

考研中,最近在复习数据结构。在PTA的数据结构与算法题目集(中文)上练习一下表达式转换。在将整体答对率从从0.16刷低到0.14,提交53次,历时6个半小时,从2017/9/13 17:26:08刷到2017/9/13 23:42:54,并询问同学无果后终于成功地解决此题。

原题如下。

----------------------------------------------------------------------------------------------------------------------------

二、题目

7-20 表达式转换(25 分)

算术表达式有前缀表示法、中缀表示法和后缀表示法等形式。日常使用的算术表达式是采用中缀表示法,即二元运算符位于两个运算数中间。请设计程序将中缀表达式转换为后缀表达式。

输入格式:

输入在一行中给出不含空格的中缀表达式,可包含+-*\以及左右括号(),表达式不超过20个字符。(编者注:最后一个应该是'/',原题就写错了)

输出格式:

在一行中输出转换后的后缀表达式,要求不同对象(运算数、运算符号)之间以空格分隔,但结尾不得有多余空格。

输入样例:

2+3*(7-4)+8/4

输出样例:

2 3 7 4 - * + 8 4 / +
----------------------------------------------------------------------------------------------------------------------------

三、测试样例分析

1.测试样例

本人找到的官方给出的测试样例大笑(根据陈越等的高教社版红皮《数据结构学习与实验指导》)

表达式转换测试用例
序号输入输出说明
02+3*(7-4)+8/42 3 7 4 - * + 8 4 / +正常测试6种运算符
1((2+3)*4-(8+2))/52 3 + 4 * 8 2 + - 5 /嵌套括号
21314+25.5*121314 25.5 12 * +运算数超过1位整数且有非整数出现
3-2*(+3)-2 3 *运算数前有正负号
4123123只有一个数字










但是实际上的测试样例根本不是像上面这样的。这也是为什么我明明样例0,1,4和所给输出完全一致(包括空格等)却一直提示格式错误得0分。

样例3和5的错误是意料之中,我本来就是只想做对样例0,1,4,测试一下自己的栈的使用来着。快哭了


2.某次提交结果

提交时间状态分数题目编译器耗时用户
2017/9/13 19:06:57多种错误07-20C++ (g++)2 ms 
测试点提示结果耗时内存
0sample 6种运算符格式错误2 ms244KB
1嵌套括号格式错误2 ms244KB
2运算数超过1位整数且有非整数出现答案错误2 ms256KB
3运算数前有正负号答案错误2 ms240KB
4只有1个数字格式错误2 ms244KB

错误代码(只考虑1位整数的情况)如下:

#include <stdio.h>
#include <stack>
using namespace std;
int Prior(char ch){
    int pri = 0;
    switch(ch){
    case '(':
        pri=1;break;
    case '+':
    case '-':
        pri=2;break;
    case '*':
    case '/':
        pri=3;break;
    }
    return pri;
}
void Zhong2Hou(char* src,char* str){
    stack<char> stc;
    int i,j;
    i=j=0;
    for(i=0;src[i];i++){//读取字符
        if(src[i]>='0'&&src[i]<='9'){//是操作数直接输出
            str[j++]=src[i];///printf("%c\n",str[j-1]);
        }else if(src[i]=='('){//是左括号直接入栈
            stc.push(src[i]);
        }else if(src[i]==')'){//是右括号则一直出栈到左括号并输出,左括号不输出
            while(stc.top()!='('){
                str[j++]=stc.top();///printf("%c\n",str[j-1]);
                stc.pop();
            }
            stc.pop();
        }else{//是其他操作符
            if(stc.empty()){//栈空直接入栈
                stc.push(src[i]);
            }else if(Prior(src[i])>Prior(stc.top())){//优先级比栈顶的大直接入栈
                stc.push(src[i]);
            }else{//优先级比栈顶的小或等则一直出栈到空或大并输出,大时不输出
                while(Prior(src[i])<=Prior(stc.top())){
                    str[j++]=stc.top();///printf("%c\n",str[j-1]);
                    stc.pop();
                    if(stc.empty()){
                        break;
                    }
                }
                stc.push(src[i]);//入栈该操作符
            }
        }
    }
    while(!stc.empty()){//栈不空时全输出
        str[j++]=stc.top();///printf("%c\n",str[j-1]);
        stc.pop();
    }
    str[j++]=0;//补0
}
int main(void)
{
    char src[100],str[100];
    scanf("%s",src);
    Zhong2Hou(src,str);
    int i;
    for(i=0;str[i+1];i++){
        putchar(str[i]);putchar(' ');
    }
    putchar(str[i]);
    return 0;
}

3.错误分析

实际上,所有测试样例都是有多位整数的情况的。这就很坑了,要么彻底对,要么一点不对。再见

那么,为什么会提示格式错误而不是答案错误呢?

这是因为假设输入是

123

如果要求输出

       123

        而你输出

        1 2 3

那么,这种情况会提示 “格式错误” 而不是 “答案错误” 。

按上面的错误代码输入123的确会产生如上所示的 “格式错误” 的输出。而测试样例0,1的格式错误同理。(仿佛看到了PAT和PTA系统背后的一点点判题代码(定义格式错误)的运行逻辑偷笑。)

四、正确代码

下面这个是正确代码,刚好99行:

#include <stdio.h>
#include <stack>
using namespace std;
///是否是数字
int IsNumber(char a){
    return a>='0'&&a<='9';
}
///从字符串start处开始提取操作数或操作符,并返回下一次提取的开端和提取的结果
int getop(char* src,int start,int &nextstart,char* token){
    int i,j;
    if(IsNumber(src[start])){///是一个无符号数
        for(i=0;IsNumber(src[start+i])||src[start+i]=='.';i++){///复制给token
            token[i]=src[start+i];
        }token[i]=0;
        nextstart=start+i;///下一次提取的开端
        return 1;
    }else if((src[start]=='+'||src[start]=='-')&&(start==0||src[start-1]=='(')){///是一个有符号数
        if(src[start]=='-'){
            token[0]=src[start];j=1;
        }else{///去掉正号
            j=0;
        }
        for(i=1;IsNumber(src[start+i])||src[start+i]=='.';i++){///复制给token
            token[j++]=src[start+i];
        }token[j]=0;
        nextstart=start+i;///下一次提取的开端
        return 1;
    }else{///不是数
        token[0]=src[start];///复制操作符
        token[1]=0;
        nextstart=start+1;///下一次提取的开端
    }
    return 0;
}
///定义优先级。括号最低
int Prior(char ch){
    int pri=0;
    switch(ch){
    case '(':
        pri=1;break;
    case '+':
    case '-':
        pri=2;break;
    case '*':
    case '/':
        pri=3;break;
    }
    return pri;
}
///表达式转换
void Zhong2Hou(char* src,char* str){
    stack<char> stc;
    int i,j,nexti;char token[100];
    for(i=0,j=0;src[i];i=nexti){
        int isnumber=getop(src,i,nexti,token);///提取操作对象
        if(isnumber){///是个数
            int k=0;///复制到str后
            for(k=0;token[k];k++){
                str[j++]=token[k];
            }
            str[j++]=' ';///用空格分隔
        }else if(src[i]=='('){///左括号直接入栈
            stc.push(src[i]);
        }else if(src[i]==')'){///右括号则弹出栈中全部操作符并复制到str后,直到左括号为止
            while(stc.top()!='('){
                str[j++]=stc.top();stc.pop();
                str[j++]=' ';///用空格分隔
            }
            stc.pop();///弹出左括号
        }else{///是个一般的四则运算符
            if(stc.empty()){///空栈直接入栈
                stc.push(src[i]);
            }else if(Prior(src[i])>Prior(stc.top())){///优先级高于栈顶则入栈
                stc.push(src[i]);
            }else{///否则弹出栈中元素直到优先级比栈顶高或空栈为止
                while(!stc.empty()&&Prior(src[i])<=Prior(stc.top())){
                    str[j++]=stc.top();
                    stc.pop();
                    str[j++]=' ';
                }
                stc.push(src[i]);///将该操作符入栈
            }
        }
    }
    while(!stc.empty()){///弹出栈中全部元素
        str[j++]=stc.top();
        stc.pop();
        str[j++]=' ';
    }
    str[j-1]=0;///最后一个空格赋值为0
}
int main(void)
{
    char src[100]="",str[100]="";
    scanf("%s",src);///输入
    Zhong2Hou(src,str);///转换
    printf(str);///打印
    return 0;
}

五、算法逻辑分析

一、提取操作对象(操作数或操作符):

起始点:

指向中缀串指针所指字符是数字,则是无符号数,

指向中缀串指针所指字符是正负号所指字符是第一个或者所指字符的前一个字符是 ‘(’,则是有符号数。

否则所指为操作符。

结束点:

当是操作数时,直到下一个既不是数字也不是小数点的符号为止。

当是操作符时,直接加1(这里操作符都是1位)。

二、定义算符优先级

加减号是2,乘除号是3。为了在接下来遇到‘(’时省去判断栈顶是否是‘(’,将‘)’优先级定为1。优先级初始化为0。

三、中缀转后缀(后缀又称为逆波兰式)

中缀指针所指是操作数,则直接将之复制到后缀串尾,加空格。

中缀指针所指是左括号 ‘(’ ,则直接将之入栈。

中缀指针所指是右括号 ‘)’ ,则一直弹栈(并且复制到后缀串尾,加空格)到左括号 ‘(’ 为止,再将左括号弹栈。

中缀指针所指是一般的操作符‘+’、‘-’、‘*’、‘/’,则

若栈为空,则直接入栈;

否则,若其优先级比栈顶元素高,则将之入栈

   否则,则一直弹栈(并且复制到后缀串尾,加空格)直到栈空或者其优先级比栈顶元素高为止(注意若此时遇到左括号,因为左括号我们设置的优先级很低,所以其优先级必比左括号高,可以停止)。然后将该操作符入栈

遍历中缀表达式后,将栈中所剩操作符全部弹栈(并且复制到后缀串尾,加空格)

最后根据题目要求,将最后一个空格赋值为结束符 ‘\0’。

----------------------------------------------------------------------------------------------------------------------------------------------------------

附录:对于人类而言,如何将中缀表达式转换为后缀表达式比较准而且快?

比如:2+3*(7-4)+8/4

第一步,把每两个数之间的运算都加上括号(涉及视觉上并行地进行局部运算次序的判定)。((2+(3*(7-4)))+(8/4))

第二步,把中间的运算符放在所在的那一层括号右边(符号移动)。((2(3 (7 4)-)*)+(84)/)+

第三步,把括号全部去掉即为后缀表达式(符号消除或代替)。2 3 7 4 - * + 8 4 / +

转换为前缀表达式同理。

----------------------------------------------------------------------------------------------------------------------------------------------------------

六、总结

其实这题的算法思想就跟栈没有什么关系,只是在解题过程中借用了一下栈这种数据结构来表达而已。大多数实际问题和算法题也都是这样的,本身解法与数据结构并无关系,只是在借助该类数据结构以一种简洁的方式来表示算法的思想,有没有这种类型的数据结构并不影响问题的解决,因为会在解决问题过程中自然而然的去发现和创造出这种数据结构(非常慢......)。不可以舍本逐末,本末倒置,只关心工具本身,要能明白问题的起源和终结问题的方法,这样才能掌握其本质。

----------------------------------------------------------------------------------------------------------------------------------------------------------

下一篇文章我们将使用创建二叉树+遍历二叉树的方法解决此题。

  • 33
    点赞
  • 86
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
### 回答1: 表达式 b'7-1' 的中文意思是“7减1”。在Python中,b开头的字符串表示为bytes类型,需要使用decode()方法将其转换为字符串类型。在将中缀表达式转换后缀表达式时,需要使用来存储运算符和括号,并按照一定的规则进行转换。最终,将后缀表达式求值即可得到结果。对于表达式“7减1”,其对应的后缀表达式为“7 1 -”,结果为6。 ### 回答2: 中缀表达式是人们常用的一种表达式形式,但对计算机来说,这种表示方法并不方便。因此,在处理中缀表达式时,需要将其化为后缀表达式。接下来,我们将通过一个例子来讲解如何将中缀表达式转换后缀表达式并求值。 例子:将中缀表达式“7-2*5+(6+3)/2”转换后缀表达式,并求值。 1. 创建两个,分别为操作符和结果。从左到右遍历中缀表达式的每一个字符。 2. 如果是操作数,将其压入结果。 3. 如果是左括号,“(”,将其压入操作符。 4. 如果是操作符,比较优先级。如果操作符为空或顶是左括号,则将该操作符压入。否则,将操作符顶的操作符弹出并压入结果,直到操作符为空或遇到左括号或优先级低于当前操作符的操作符。将当前操作符压入操作符。 5. 如果是右括号,“)”,则连续弹出操作符顶的操作符,并将其压入结果,直到遇到左括号为止。左右括号都不输出结果。 6. 遍历完表达式后,操作符中仍有操作符,将其逐个弹出并压入结果。 7. 最后结果只剩一个元素,即为结果。 按照上述方法,将中缀表达式“7-2*5+(6+3)/2”转换后缀表达式如下: 7 2 5 * - 6 3 + 2 / + 对后缀表达式进行求值: 1. 读入7,将其压入中。 2. 读入2,将其压入中。 3. 读入5,将2弹出,将其与5做乘法得到10,将10压入中。 4. 读入“*”,将其压入操作符。 5. 读入“-”, 将“-”与7做减法,得到“-”,将其压入中。 6. 读入6,将其压入中。 7. 读入3,将其压入中。 8. 读入“+”,将“+”与6做加法,得到9,将其压入中。 9. 读入“/”,将其压入操作符。 10. 读入2,将其压入中。 11. 读入“+”,弹出除号,将9和2做除法,得到4.5,将其压入中。 12. 由于后缀表达式遍历完毕,将剩余的操作符全部弹出,得到4.5和“+”,将其做加法,最终结果为13.5。 因此,中缀表达式“7-2*5+(6+3)/2”的后缀表达式为“7 2 5 * - 6 3 + 2 / +”,其结果为13.5。 ### 回答3: 中缀表达式是指运算符位于两个操作数之间的表达式,例如:1 + 2 * 3。而后缀表达式则是将运算符位于两个操作数之后的表达式,例如:1 2 3 * +。本题的任务就是将中缀表达式转换后缀表达式,并求出后缀表达式的值。 中缀表达式转换后缀表达式的过程可以使用来实现。首先,定义两个Operand Stack和Operator Stack。遍历中缀表达式,对于每个元素: 1. 如果该元素是数字,则将其压入Operand Stack中。 2. 如果该元素是运算符,则进行以下操作: a. 如果Operator Stack为空,则将该运算符压入Operator Stack中。 b. 如果Operator Stack不为空,则判断顶元素与该运算符的优先级,若顶元素优先级大于等于该运算符,则将顶运算符弹出,并将其压入Operand Stack中,然后重复步骤2直到Operator Stack为空或顶元素优先级小于该运算符。最后将该运算符压入Operator Stack中。 3. 如果该元素是左括号,则将其压入Operator Stack中。 4. 如果该元素是右括号,则进行以下操作: a. 重复步骤2将Operator Stack中的运算符弹出并压入Operand Stack中,直到遇到左括号。 b. 将左括号弹出Operator Stack并丢弃。 当遍历完中缀表达式后,将Operator Stack中的运算符弹出并压入Operand Stack中。此时,Operand Stack中的元素就是后缀表达式的结果。 接下来,可以使用另外一个Result Stack来计算后缀表达式的值。遍历后缀表达式,对于每个元素: 1. 如果该元素是数字,则将其压入Result Stack中。 2. 如果该元素是运算符,则从Result Stack中弹出两个数字,并根据该运算符进行计算,并将结果压入Result Stack中。 最终Result Stack中只剩下一个元素,即为后缀表达式的值。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值