一、前言
二、题目
7-20 表达式转换(25 分)
算术表达式有前缀表示法、中缀表示法和后缀表示法等形式。日常使用的算术表达式是采用中缀表示法,即二元运算符位于两个运算数中间。请设计程序将中缀表达式转换为后缀表达式。
输入格式:
输入在一行中给出不含空格的中缀表达式,可包含+
、-
、*
、\
以及左右括号()
,表达式不超过20个字符。(编者注:最后一个应该是'/',原题就写错了)
输出格式:
在一行中输出转换后的后缀表达式,要求不同对象(运算数、运算符号)之间以空格分隔,但结尾不得有多余空格。
输入样例:
2+3*(7-4)+8/4
输出样例:
2 3 7 4 - * + 8 4 / +
三、测试样例
序号 | 输入 | 输出 | 说明 |
0 | 2+3*(7-4)+8/4 | 2 3 7 4 - * + 8 4 / + | 正常测试6种运算符 |
1 | ((2+3)*4-(8+2))/5 | 2 3 + 4 * 8 2 + - 5 / | 嵌套括号 |
2 | 1314+25.5*12 | 1314 25.5 12 * + | 运算数超过1位整数且有非整数出现 |
3 | -2*(+3) | -2 3 * | 运算数前有正负号 |
4 | 123 | 123 | 只有一个数字 |
注意:上一篇文章中我们测试得出结论,其实每个样例都是多位整数。
四、算法逻辑分析
1、构造二叉树
2、后序遍历
后序遍历二叉树并将结果存到一个字符串中。
3.决定成败的细节
重点:上边这些分析是个正常人都知道,问题的关键不在于此,而在于一些细节,这些细节有没有考虑才是决定能不能PASS的关键。
(1).何时创建节点申请内存,最后如何释放内存?
答:在定位出最低优先级运算符后,创建节点并赋值该运算符。
(2).如何定位最低优先级的运算符?
答:对于给定表达式,初始最低优先级为一个非常大的正数,基础优先级是0,遇到左括号基础优先级增加一个正数a(大于最高优先级);遇到右括号基础优先级减少一个正数a;遇到运算符比较基础优先级+运算符优先级是否小于等于当前记录的最低优先级,如果是则更新最低优先级算符位置和最低优先级大小。随后,创建子树。最后,如果最低优先级没被更新过,则说明没有运算符,只需要该子树的左右子树赋值为空,将给定表达式去除左右括号后赋值为节点字符串,否则,将最低优先级运算符赋值该节点字符串,递归创建左右子树。
(3).本题隐藏的测试点,正号需要去除,怎么去除?
读取建立二叉树时:
正负号的唯一标志是该符号是第一个字符或者该符号前一个字符是‘(’。在定位最低优先级运算符时,如果遇到正负号,直接跳过。
后序遍历时:
遇到叶子带正号的直接去除正号。
(4)如何去除最后一个空格?
最后用str.erase(len-1,1)函数去除最后一个空格就好了,这里不可以直接赋值为'\0',因为我使用的是string类型。
五、正确代码
#include <iostream>
#include <string>
#include <stdio.h>
#define STEP 3
#define MAX 9999999
using namespace std;
string str="";
struct Tree{
string s;
Tree *l,*r;
Tree(){
l=r=NULL;s="";
}
~Tree(){
delete l;
delete r;
}
};
int GetPri(char c){
if(c=='+'||c=='-')return 1;
if(c=='*'||c=='/')return 2;
return -1;
}
void In2T(string s,Tree* &T){
int i,id=-1;int len=s.length();
int d=0;int p=-1;int mi=MAX;
for(i=0;i<len;i++){///记录最后计算的那个运算符位置
if(s[i]=='('){
d+=STEP;
}else if(s[i]==')'){
d-=STEP;
}else if(GetPri(s[i]==1)&&(i==0||s[i-1]=='(')){///正负号不是运算符
continue;
}else if((p=GetPri(s[i]))>0){
if(p+d<=mi){
mi=p+d;id=i;
}
}
}
T=new Tree;///创建子树
if(id==-1){///去除左右括号
int id1,id2;
for(id1=0;id1<len&&s[id1]=='(';id1++);
for(id2=len-1;id2>=0&&s[id2]==')';id2--);
T->s=s.substr(id1,id2-id1+1);return;
}
T->s=s[id];///
In2T(s.substr(0,id),T->l);///左子树
In2T(s.substr(id+1,len-id-1),T->r);///右子树
}
void Post(Tree* T){
if(!T)return;
Post(T->l);
Post(T->r);
if(T->s[0]=='+'&&T->s.length()>1){///去除正号
///判断条件还可以为T->s[0]=='+'&&T->left==NULL&&T->right==NULL
str=str+T->s.substr(1,T->s.length()-1)+" ";return;
}
str=str+T->s+" ";
}
int main()
{
string ss;
Tree* T=NULL;
getline(cin,ss);
In2T(ss,T);
Post(T);
int len=str.length();
str.erase(len-1,1);///去除末尾空格
cout<<str;
return 0;
}
六、总结
同样的一个题目,不过这次我们不是机械的使用栈去完成任务,而是从编译原理的算符优先分析法出发,构造一棵树,这样不仅可以得到后序,前序也可以轻易得到。