[小专题]栈与表达式求值

划水&摸鱼了好多天…解决了高中一直没搞清楚的表达式求值的内容…

写篇博客记录一下


 

 

1.栈(Stack)


 

 

 

直观理解 如图所示,类似于厨房用来放调羹的那个槽…先放进去的会被压在底下,一般叫做栈底(bottom)元素,最后进去的则作为栈顶(top)元素,我们把一个调羹放到槽里面就叫做把这个元素压入(push)栈里,把顶部的调羹拿出来就叫做把一个元素从栈中弹出(pop),栈相关的操作基本就是这些,C++的STL也自带了stack类型,声明形式为 stack<type>s; ,type是要插入栈的元素类型。使用时只要include头文件stack就可以调用啦

当然我们自己也容易用数组实现一个确定空间上界的栈:

struct Stack
{
    ll s[N],cnt;
    Stack()
    {
        cnt=0;
        memset(s,0,sizeof(s));
    }
    inline ll top(){return s[cnt];}
    inline ll pop(){return s[cnt--];}
    inline void push(ll x){s[++cnt]=x;}
    inline bool empty(){return (cnt==0);}
};

C++自带的栈当然方便啦,不过在算法竞赛中可能为了常数小一些经常会选择手写栈

 

2.概念补充:表达式、运算符、表达式树


 

不出意外的话屏幕前的你是一个人类(?),人类们似乎能比较容易地判断类似于$2+3*5^2-3*(5+2)$这样的表达式(notation)要先计算谁后计算谁,但是现在我们想让计算机帮我们做运算,自然要设计一套算法让计算机能够知道先计算谁后计算谁。也就是我们希望能建立一套成系统的方法,把一个记录表达式的字符串拆成若干个元素,运算对象(operand)归操作对象,运算符(operator)归运算符,最好再把运算符的优先级处理好。

首先明确一下优先级:遇到括号应该先算括号内的,括号内的优先级最高,然后是幂运算>乘除>加减。

需要注意的是通常情况下优先级相同的从左到右左边的先算(比如6*3/2一般会认为先算乘法后算除法)

但是常用的运算符有个比较特殊的:幂运算,比如2^3^4我们通常会理解为是2^(3^4)而不是(2^3)^4。

于是对于+-*/这类运算符我们就说他是左结合的(Left-Associativity),而像幂运算^这类运算符我们就说它是右结合的(Right-Associativity),这些概念会在下面用到。

表达式树同样不陌生,对于每个表达式我们选择一个优先级最低的运算符作为树根,运算符的左右两边作为左右子树,左右子树再进行同样的操作,例如上面的表达式可以构建如下的表达式树:

 

 

 对这颗二叉树进行前种后序遍历可以分别得到原表达式的前缀(prefix)、中缀(infix)、后缀(postfix)表达式

而后缀表达式则又被成为逆波兰表达式(reverse Polish notation),前缀表达式也被成为波兰表达式。

 

3.调度场算法(shunting-yard algorithm)


 

例子中的2+3*5^2-这部分,先读到运算对象2和运算符+,接着是3,这里我们不能急着做加法,原因是不知道后面有什么,所以这里正确的操作应该是先把“这里有个加法运算”这件事存起来,同时最好运算对象也被按顺序存好。接着往下我们发现一个乘法,同样不知道后面会不会有优先级更高的运算,同样存起来。好的我们读到了“^”,它比乘法优先级更高,继续往下读读到一个减法,优先级比当前的幂运算低,这意味着前面的部分可以开始算了。

现在开始倒着进行运算:因为我们都是保留优先级低的操作,所以到此为止的运算符优先级都是单调递增的,也就是要对靠后的运算符先进行操作,同样对于运算对象也是,基于这点我们联想到用来做这件事。

栈S1储存数字,栈S2储存运算符,到这里S1的元素分别是:2,3,5,2,而S2的元素分别是:+,*,^,取出S2的一个幂运算和S1的2和5,注意顺序是倒着来的,所以应该是以5为底,求得5^2,

再把计算结果25压入S1,接下来的操作类似,S1有2,3,25,S2有+,*,取出乘法和25和3,计算结果3*25=75压入S1,继续取出S2的+和S1的75和2,计算结果2+75压入S1,直到S2变空,这时候S1剩下的一个元素就是运算结果

得到一个简单的伪代码:

while there are tokens to be read:
    read a token.
    if(the token is an operator)
    	while((S2 not empty)and(precedence of the top of S2>=precedence of this operator))
    		calculate
		push the operator onto S2
   else
	//the token is an operand
	push it onto S1
while(S2 not empty)calculate
print the top of S1

 

括号怎么处理?

括号优先级最高,遇到左括号先存起来,进行calculate则需要额外加一个S2的top不是左括号

如果读到左括号直接压到运算符栈S2,如果读到右括号则一直进行计算直到读到左括号,最后再把左括号扔掉。

(因为括号内能先算的其实都已经算了,所以只会剩下优先级单调不减的运算符,直接算就行)

 

左结合和右结合?

如果S2栈顶元素和当前运算符优先级相同且都是右结合的(比如^),那也不能进行运算,也就是把

precedence of the top of S2>=precedence of this operator

稍微修改一下变成

(precedence of the top of S2>precedence of this operator)or((precedence of the top of S2==precedence of this operator)and(this operator is not Right associativity))

 

到此为止代码就差不多成形了:

//n is the length of string str
//pre[x] records the precedence of operator x
//calc() is the function calculating
for(register int i=1;i<=n;i++)
{
    if(oper(str[i]))
    {
        while(!S2.empty()&&oper(S.top())&&((pre[S2.top()]>pre[str[i]])||(pre[S2.top()]==pre[str[i]]&&str[i]!='^'))&&S.top()!='(')
            calc();
        S2.push(str[i]);
    }else if(str[i]=='(')
    {
        S2.push(str[i]);
    }else if(str[i]==')')
    {
        while(S2.top()!='(')calc();
        if(S2.top()=='(')S2.pop();
    }else if(num(str[i]))
    {
        ll sum=0,j=0;
        for(;num(str[i+j]);j++)sum=sum*10+str[i+j]-'0';
        i+=j-1;
        S1.push(sum);
    }
}
while(!S.empty())calc();

 

上面这个算法就是标题的调度场算法shunting-yard algorithm啦,是Dijkstra大佬提出的~算法的起名大概是因为这个原理很像下图的结构:

↑图转自Wikipedia

 

4.和逆波兰表达式的关系


 

不难发现如果把计算改成输出运算符,把压入数字改成输出数字,就得到了原来表达式的逆波兰表达式啦。

而逆波兰表达式的计算也很简单,省去了对优先级的考虑——读到运算符就对前两个数字进行运算,就能够给出答案

 

5.来道题!


Luogu2379:给若干个单项式或者多项式,包含+、-、*、和^,字母只包括小写字母,输出运算结果

 

话说这题我没A,因为我写了个用set维护多项式字典序的东西…后面发现只要保证每个项按字典序排序,多项式直接按照输入顺序输出就行了…然后我懒得改(

表达式计算的框架和上面一样,对于每个项我选择用一个27个元素的数组s[]记录,s[0]记录前面的系数,s[1]到s[26]分别表示a到z的次数,然后再用一个set储存每个项,手写一个比较函数来维护多项式字典序。

加减乘法再手写一下~就得到一个挺长的代码啦…

  1 #include<cstdio>
  2 #include<set>
  3 #include<stack>
  4 #include<cstring>
  5 using namespace std;
  6 #define rep(i,a,b) for(register int i=(a);i<=(b);i++)
  7 
  8 typedef long long ll;
  9 
 10 const int N=300;
 11 
 12 //term
 13 struct term
 14 {
 15     ll s[27];
 16     term(){memset(s,0,sizeof(s));}
 17     inline void print()const
 18     {
 19         if(s[0]!=1)printf("%d",s[0]);
 20         rep(i,1,26)if(s[i])
 21         {
 22             printf("%c",'a'+i-1);
 23             if(s[i]>1)printf("^%d",s[i]);
 24         }
 25     }
 26 };
 27 
 28 struct cmp
 29 {
 30     inline bool operator () (const term &A,const term &B)
 31     {
 32         rep(i,1,26)if(A.s[i]!=B.s[i]){return A.s[i]>B.s[i];}
 33         return 0;
 34     }
 35 };
 36 
 37 
 38 inline term operator * (const term &A,const term &B)
 39 {
 40     term C;
 41     C.s[0]=A.s[0]*B.s[0];
 42     rep(i,1,26)C.s[i]=A.s[i]+B.s[i];
 43     return C;
 44 }
 45 
 46 inline bool check(term A,term B)
 47 {
 48     rep(i,1,26)if(A.s[i]!=B.s[i])return 0;
 49     return 1;
 50 }
 51 
 52 inline term operator +(term A,term B)
 53 {
 54     term C;
 55     C.s[0]=A.s[0]+B.s[0];
 56     if(C.s[0]==0)return C;
 57     rep(i,1,26)C.s[i]=A.s[i];
 58     return C;
 59 }
 60 
 61 inline term operator -(term A,term B)
 62 {
 63     term C;
 64     C.s[0]=A.s[0]+B.s[0];
 65     if(C.s[0]==0)return C;
 66     rep(i,1,26)C.s[i]=A.s[i];
 67     return C;
 68 }
 69 
 70 //polynomial 
 71 
 72 struct P
 73 {
 74     set<term,cmp>p;
 75     inline void insert(const term &A)
 76     {
 77         set<term,cmp>::iterator itr=p.begin();
 78         bool b=1;
 79         for(;itr!=p.end();itr++)
 80         {
 81             if(check(*itr,A))
 82             {
 83                 term temp=*itr;
 84                 temp=temp+A;
 85                 p.erase(itr);
 86                 p.insert(temp);
 87                 b=0;
 88                 break;
 89             }
 90         }
 91         if(b)p.insert(A);
 92     }
 93 };
 94 
 95 inline P operator +(const P &A,const P &B)
 96 {
 97     P res=A;
 98     
 99     for(set<term,cmp>::iterator itr=B.p.begin();itr!=B.p.end();itr++)res.insert(*itr);
100     
101     return res;
102 }
103 
104 inline P operator -(const P &A,const P &B)
105 {
106     P res=A;
107     P t;
108     for(set<term,cmp>::iterator itr=B.p.begin();itr!=B.p.end();itr++)
109     {
110         term temp=*itr;
111         temp.s[0]*=-1;
112         t.insert(temp);
113     }
114     for(set<term,cmp>::iterator itr=t.p.begin();itr!=t.p.end();itr++)res.insert(*itr);
115     
116     return res;
117 }
118 
119 inline P operator *(const P &A,const P &B)
120 {
121     P res;
122     set<term,cmp>::iterator ita,itb;
123     ita=A.p.begin();itb=B.p.begin();
124     
125     for(;ita!=A.p.end();ita++)for(itb=B.p.begin();itb!=B.p.end();itb++)res.insert((*ita)*(*itb));
126     return res;
127 }
128 
129 struct deal
130 {
131     stack<char>S1;//oper
132     stack<P>S2;//poly
133     char str[N];
134     int pre[300];
135     int n;
136     
137     inline bool op(char c)
138     {
139         return (c=='+'||c=='-'||c=='*');
140     }
141     
142     inline void calc()
143     {
144         char t=S1.top();S1.pop();
145         P p1,p2,res;
146         p2=S2.top();S2.pop();
147         p1=S2.top();S2.pop();
148         if(t=='+')res=p1+p2;
149         else if(t=='-')res=p1-p2;
150         else res=p1*p2;
151         S2.push(res);
152     }
153     
154     inline void work()
155     {
156         scanf("%s",str+1);n=strlen(str+1);
157         pre['+']=pre['-']=1;pre['*']=2;
158         rep(i,1,n)
159         {
160             if(str[i]=='[')str[i]='(';
161             if(str[i]==']')str[i]=')';
162         }
163         rep(i,1,n)
164         {
165             if(op(str[i]))
166             {
167                 while(!S1.empty()&&pre[S1.top()]>=pre[str[i]]&&S1.top()!='(')
168                     calc();
169                 S1.push(str[i]);
170             }else if(str[i]=='(')
171             {
172                 S1.push(str[i]);
173             }else if(str[i]==')')
174             {
175                 while(!S1.empty()&&S1.top()!='(')calc();
176                 if(S1.top()=='(')S1.pop();
177             }else
178             {
179                 int j=0,sum=0;
180                 P poly;
181                 term res=term();
182                 for(;str[i+j]>='0'&&str[i+j]<='9';j++)sum=sum*10+str[i+j]-'0';
183                 for(;str[i+j]>='a'&&str[i+j]<='z';j++)
184                 {
185                     if(str[i+j+1]=='^')
186                     {
187                         res.s[str[i+j]-'a'+1]=str[i+j+2]-'0';
188                         j+=2;
189                     }
190                     else res.s[str[i+j]-'a'+1]=1;
191                     
192                 }
193                 
194                 if(sum==0)sum=1;
195                 res.s[0]=sum;
196                 
197                 poly.insert(res); 
198                 S2.push(poly);
199                 i+=j-1; 
200             }
201         }
202         while(!S1.empty())calc();
203         
204         set<term,cmp> res=S2.top().p;
205         set<term,cmp>::iterator itr=res.begin();//*itr->term
206         
207         for(int i=0;itr!=res.end();itr++,i++)
208         {
209             if((*itr).s[0]==0)continue; 
210             if((*itr).s[0]>0&&i!=0)printf("+");
211             
212             if((*itr).s[0]!=1)
213             {
214                 if((*itr).s[0]==-1)printf("-");
215                 else printf("%d",(*itr).s[0]);
216             }
217             
218             rep(j,1,26)if((*itr).s[j])
219             {
220                 printf("%c",'a'+j-1);
221                 if((*itr).s[j]>1)printf("^%d",(*itr).s[j]);
222             }
223         }
224     }
225     
226 };
227 int main()
228 {
229     deal D;
230     D.work();
231     return 0; 
232 }
View Code

 

以上~

posted @ 2020-09-15 01:02  yoshinow2001  阅读(373)  评论(0编辑  收藏  举报