表达式总结
感觉以前从来没有用过,突然在白书上面看见。。。。
概要 中缀表达式 后缀表达式 表达式树
一.相关概念
1.中缀表达式:
就是熟知的表达式形式例如a+b*(c-d)-e/f,按照一定的优先级运算,不同的语言优先级不同,C语言应该是下表:
(注pascal的运算符and和*/统计,or,xor和+-同级)
2.后缀表达式:
后缀表达式,指的是不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则);一般我们只考虑常用二目运算符和(),例如上边的a+b*(c-d)-e/f的后缀表达式为:abcd-*+ef/-
后缀表达式的计算:
对人脑来说不如中缀表达式好看,但是对计算机来说很有用。计算时拿一个栈来存放结果,从左往右一次扫描后缀式如果为数字(a,b,c,d,e,f)就直接加入栈中,如果为运算符(-*/-)就取出栈顶的两个元素进行运算,注意这样的运算是有顺序的,即 次栈顶 (运算) 栈顶 ,因为类似减号这样的东西是没有交换律的;
中缀表达式转后缀表达式:
用两个栈ans,S,一个栈存后缀表达式,另一个栈维护表达式运算符优先级严格单调上升;
扫描字符 | S | ans |
a | NULL | a |
+ | + | a |
b | + | a b |
* | + * | a b |
( | + * ( | a b |
c | + * ( | a b c |
- | + * ( - | a b c |
d | + * ( - | a b c d |
) | + * ( – ) --> + * | a b c d - |
- | + *- --> - | a b c d - * + |
e | - | a b c d - * + e |
/ | - / | a b c d - * + e |
f | - / | a b c d - * + e f |
NULL | - / ---> NULL | a b c d - * + e f / - |
我们从左到右扫描中缀表达式,遇到一个数字就直接加入ans,如果是运算符,先考虑运算符和S栈顶运算符的优先级,如果当前优先级小于或者等于栈顶的优先级,则弹出栈顶运算符并将其加入ans,最后再将当前的运算符加入s;特别的,当遇到’(‘时就直接加入S,遇到’)’是就将S中的最后一个’(’之后的元素依次弹出并加入ans;最后将S里面所有剩下的元素弹出并加入ans;
3.表达式树:
表达式树就是一颗叶子结点是数,非叶子结点是运算符的二叉树(二目运算符),对于一个非叶子结点,它的左子树就代表了这个运算符左边的表达式,右子树为右边的表达式;
可以发现这样的树的左右儿子也是有顺序的,不能随意交换。
表达式树的计算:
对左右子树递归,再将递归结果用当前节点的运算符计算即可;所以表达式树用树形结构来替代了表达式的结构;
和中缀,后缀表达式的关系:
中缀表达式是表达式树的中序遍历再加上某些必要的括号,后缀表达式就是表达式树的后序遍历;
后缀表达式转表达式树,理解了表达式树的计算之后,我们只需要按照后缀表达式求值的方式建树,依旧用一个栈存字符代表的编号,每次运算符进入时退出栈顶两个元素,并接到当前运算符的左右儿子再把当前运算符标号加进去;
中缀表达式转表达式树:
一种出现在白书上比较简单的方法是:找整个中缀里面最后一个运算的运算符,左儿子就递归其左边的表达式,右儿子就递归其右边的表达式,但是我感觉这样子的复杂度是O(n*dep)dep为表达式树的深度。
O(n)的我能想到的方法就是参照中缀转后缀的思想在运算符进入ans数组时直接把左右儿子接到当前运算符并弹出再加入(估计没什么用,不会卡);
二.一些应用
1. UVA – 12219 Common Subexpression Elimination
题意:给出一个二目表达式,中缀的形式,现在可以允许用一个数字替换第二次出现的相同表达式,求最少可以用多少个字符表示出这个表达式;
题解:建立表达式树会直观一点,建好树之后,问题即你可以用一个数字替代前面出现的相同子树,并且会保留第一次出现的子树,这样只要贪心就好,用三元(u,l,r)表示当前节点,左子树,右子树;将每个子树都哈希一下,比较当前节点和左子树和右子树是否相同,用一个map标记是否出现;
(直接hash一个子树的话应该也是没问题)
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<map> 5 using namespace std; 6 const int N=500010; 7 int T,vis[N],idx; 8 char s[N],*p; 9 struct node{ 10 string s; 11 int l,r,h; 12 bool operator <(const node&A)const{ 13 if(A.h!=h)return h<A.h; 14 if(A.l!=l)return l<A.l; 15 return r<A.r; 16 } 17 }v[N]; 18 map<node,int>mp; 19 int solve(){ 20 int now=++idx; 21 vis[now]=0; 22 node &u=v[now]; 23 u.s="";u.l=u.r=u.h=0; 24 while(isalpha(*p)){ 25 u.h=u.h*27+*p-'a'+1;// 26 u.s=u.s+*p; 27 p++; 28 } 29 if(*p=='('){ 30 p++;u.l=solve(); 31 p++;u.r=solve(); 32 p++; 33 } // 34 if(mp[u]){ 35 idx--; 36 return mp[u]; 37 } 38 else return mp[u]=now; 39 }/// 40 void print(int x){ 41 if(vis[x]){printf("%d",x);return;} 42 vis[x]=1; 43 printf("%s",v[x].s.c_str()); 44 if(v[x].l&&v[x].r){ 45 printf("("); 46 print(v[x].l); 47 printf(","); 48 print(v[x].r); 49 printf(")"); 50 } 51 }// 52 int main() 53 { freopen("A.in","r",stdin); 54 freopen("A.out","w",stdout); 55 scanf("%d",&T); 56 while(T--){ 57 mp.clear(); 58 scanf("%s",s+1); 59 p=s+1;idx=0; 60 int tmp=solve(); 61 print(tmp); 62 printf("\n"); 63 } 64 return 0; 65 }//by tkys_Austin;
2.UVA – 1661 Equation
题意:输入一个最多有一个x,x最多出现一次的方程,求解,如果有且仅有一个解,输出分数形式;
题解:首先定义一个分数类用来存储答案,后缀表达式转表达式树,对于一个节点,将没有x的子树的值直接计算,再根据当前节点的结果和这个已知子树算出未知子树的值,递归求解直到叶子x;
但是代码写起来好像很不友好,特别是无解和多解的讨论:
左子树已知: a/x=b; a!=0,b!=0一解; a!=0,b=0,无解; a=0,b=0多解; a=0,b!=0无解;
a*x=b; a!=0,一解; a=0,b=0,多解; a=0,b!=0无解;
右子树已知: x*a=b; a!=0,一解; a=0,b=0,多解; a=0,b!=0无解;
x/a=b(a!=0); 一解;
WA了好多发的。。。。( 初中数学。。。。。。。。。)
1 #include<cstdio> 2 #include<iostream> 3 #include<cmath> 4 #include<cstring> 5 #define Run(i,l,r) for(ll i=l;i<=r;i++) 6 #define Don(i,l,r) for(ll i=l;i>=r;i--) 7 using namespace std; 8 typedef long long ll; 9 const int N=1100000; 10 char ch; 11 ll cnt,ls[N],rs[N],vis[N],top,id[N],fg1,fg2,st[N]; 12 ll gcd(ll a,ll b){return(!b)?a:gcd(b,a%b);}// 13 struct Fra{ 14 ll a,b; 15 Fra(ll A=0,ll B=1):a(A),b(B){};// 16 void sim(){ 17 if(b<0)a=-a,b=-b; 18 if(a==0){b=1;return;} 19 ll g=gcd(abs(a),b); 20 if(g==1||g==0)return; 21 a/=g;b/=g; 22 return; 23 }// 24 Fra operator +(const Fra&A)const{ 25 Fra ret=Fra(a*A.b+A.a*b,b*A.b); 26 ret.sim(); 27 return ret; 28 }// 29 Fra operator -(const Fra&A)const{ 30 Fra ret=Fra(a*A.b-A.a*b,b*A.b); 31 ret.sim(); 32 return ret; 33 }// 34 Fra operator *(const Fra&A)const{ 35 Fra ret=Fra(a*A.a,b*A.b); 36 ret.sim(); 37 return ret; 38 }// 39 Fra operator /(const Fra&A)const{ 40 Fra ret=Fra(a*A.b,b*A.a); 41 ret.sim(); 42 return ret; 43 }// 44 }A[N],ans; 45 Fra cal(Fra a,Fra b,ll op){ 46 if(op==0)return a+b; 47 if(op==1)return a-b; 48 if(op==2)return a*b; 49 return a/b; 50 }// 51 Fra get(ll u){ 52 if(!ls[u]&&!rs[u])return A[u]; 53 return cal(get(ls[u]),get(rs[u]),A[u].a); 54 }// 55 void dfs(ll u,Fra now){ 56 if(!ls[u]&&!rs[u]){ 57 if(!vis[u]){ 58 Fra tmp=now-A[u]; 59 if(!tmp.a)fg2=1;else fg1=1;// 60 }else ans=now;// 61 return;// 62 }// 63 if(vis[rs[u]]){ 64 Fra tmp=get(ls[u]); 65 if(A[u].a>=2&&!tmp.a){ 66 if(!now.a)fg2=1;else fg1=1;//0*x=0,1 ; 0/x=0,1; 67 return; 68 }// 69 if(A[u].a==3&&!now.a){fg1=1;return;}//1/x=0; 70 if(A[u].a==1)now.a=-now.a; 71 if(A[u].a==3)swap(now.a,now.b); 72 now=cal(now,tmp,A[u].a^1);//1*x=1,0 ; 1/x=1 ; 73 dfs(rs[u],now); 74 }else { 75 Fra tmp=get(rs[u]); 76 if(A[u].a>=2&&!tmp.a){ 77 if(!now.a)fg2=1;else fg1=1; //x*0=0,1 ; x/0=0,1; 78 return; 79 } 80 now=cal(now,tmp,A[u].a^1); //x*1=0,1; x/1=0,1; 81 dfs(ls[u],now); 82 } 83 } 84 void solve(){ 85 top=0; 86 for(ll i=1;i<=cnt;i++){ 87 if(A[i].b || (!A[i].b&&!(~A[i].a)) )st[++top]=i; 88 else { 89 rs[i]=st[top--]; 90 ls[i]=st[top--]; 91 st[++top]=i; 92 vis[i]=vis[ls[i]]||vis[rs[i]]; 93 } 94 }// 95 fg1=fg2=0; 96 dfs(st[top],Fra(0,1));// 97 if(fg1)puts("NONE"); 98 else if(fg2)puts("MULTIPLE"); 99 else printf("X = %lld/%lld\n",ans.a,ans.b);// 100 }// 101 int main() 102 { freopen("E.in","r",stdin); 103 freopen("E.out","w",stdout); 104 while((ch=getchar())!=-1){ 105 cnt=0; 106 memset(ls,0,sizeof(ls)); 107 memset(rs,0,sizeof(rs)); 108 memset(vis,0,sizeof(vis)); 109 while(1){ 110 if(ch=='X')A[++cnt]=Fra(-1,0),vis[cnt]=1,ch=getchar(); 111 else if(ch=='+')A[++cnt]=Fra(0,0),ch=getchar(); 112 else if(ch=='-')A[++cnt]=Fra(1,0),ch=getchar(); 113 else if(ch=='*')A[++cnt]=Fra(2,0),ch=getchar(); 114 else if(ch=='/')A[++cnt]=Fra(3,0),ch=getchar(); 115 else A[++cnt]=Fra(ch-'0',1),ch=getchar(); 116 while(ch==' ')ch=getchar(); 117 if(ch=='\n'||ch==-1)break; 118 } 119 solve(); 120 } 121 return 0; 122 }//by tkys_Austin;
3.UVA – 1662 Brackets Removal
题意:给出一个中缀表达式,去掉不必要的括号再输出;
题解:应该有其它的写法,我的写法是转表达式树,再中序递归地加上必要地括号,如果不加括号的话需要变号(括号前面是-或/),分类讨论:
左右儿子的运算符优先级比当前节点运算符大,直接不加括号,比当前节点小,直接加括号;
否则同级的运算符
对于左子树,如果当前节点被改过,那么它也应该改;
对于右子树,如果当前节点是-或/就改,实现时标记每个点是否被改就可以了;
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #define Run(i,l,r) for(int i=l;i<=r;i++) 5 #define Don(i,l,r) for(int i=l;i>=r;i--) 6 using namespace std; 7 const int N=1010; 8 int n,ls[N],rs[N],rt,sz; 9 char op[N],s[N]; 10 void build(int&k,int l,int r){ 11 if(l==r){ 12 k=++sz;ls[k]=rs[k]=0;op[k]=s[l]; 13 return; 14 } // 15 int p1=0,p2=0,cnt=0; 16 for(int i=l;i<=r;i++){ 17 switch(s[i]){ 18 case'(':cnt++;break; 19 case')':cnt--;break; 20 case'+':case'-':if(!cnt)p1=i;break; 21 case'*':case'/':if(!cnt)p2=i;break; 22 } 23 }// 24 if(!p1)p1=p2; 25 if(!p1){build(k,l+1,r-1);return;} 26 op[k=++sz]=s[p1]; 27 build(ls[k],l,p1-1); 28 build(rs[k],p1+1,r); 29 }/// 30 int cmp(char C){ 31 if(C=='+'||C=='-')return 0; 32 if(C=='*'||C=='/')return 1; 33 return 2; 34 }// 35 void change(int u){ 36 char &tmp=op[u]; 37 switch(tmp){ 38 case '+':tmp='-';break; 39 case '-':tmp='+';break; 40 case '*':tmp='/';break; 41 case '/':tmp='*';break; 42 } 43 } 44 string dfs(int u,int fg){ 45 if(!ls[u]&&!rs[u]){return string("")+op[u];} 46 string ret="",le="",ri=""; 47 int k=cmp(op[u]),l=cmp(op[ls[u]]),r=cmp(op[rs[u]]); 48 if(l>k)le=dfs(ls[u],0); 49 else if (l<k)le='('+dfs(ls[u],0)+')'; 50 else { 51 if(fg)change(ls[u]);else fg=0; 52 le=dfs(ls[u],fg); 53 } 54 if(r>k)ri=dfs(rs[u],0); 55 else if(r<k)ri='('+dfs(rs[u],0)+')'; 56 else { 57 if(op[u]=='-'||op[u]=='/'){ 58 fg=1;change(rs[u]); 59 }else fg=0; 60 ri=dfs(rs[u],fg); 61 } 62 return le+op[u]+ri; 63 }// 64 int main() 65 { freopen("F.in","r",stdin); 66 freopen("F.out","w",stdout); 67 while(scanf("%s",s+1)!=EOF){ 68 rt=0;sz=0; 69 n=strlen(s+1); 70 build(rt,1,n); 71 printf("%s\n",dfs(rt,0).c_str());// 72 }// 73 return 0; 74 }//by tkys_Austin;
大米飘香的总结:
抓住三种形式之间的关系,灵活选取后缀或树解题,这类题实现的时候注意细节,一般和实际问题联系比较紧密,分类要全面,不然会很惨的。。。。。。。。。。。。。