中缀转化后缀表达式-编译原理-算符优先分析算法

这篇博客涉及的编译原理的内容只能帮助复习,不能帮助掌握编译原理部分的知识,要深入理解还是要系统的学习。
博客最后会给出中缀转后缀的通用步骤。
非终结符用VN表示,终结符用VT表示。
优先级之间的比较用<,>,=

一、算符优先文法的介绍

1.编译原理网课推荐

  • 慕课上国防科大王挺教授的编译原理
  • 教材是《程序设计语言 编译原理》(第3版),作者是陈火旺院士。
  • 这篇博客的内容基于第五章的算符优先算法。

2.什么是算符文法

  • 一个文法任何一个产生式右部没有连续两个挨着的非终结符(大写字母)。也就是说每两个字母之间都至少有一个非终结符(小写字母)相连,就说这是一个算符文法
- 【正例】 E->...AaBbcD...
- 【反例】E->...AB...

3.什么是算符优先文法

教材内容。
在这里插入图片描述

引用一段书上P90的定义:(这里的=,<,>表示的是算符优先级的大小,书上=,<,>中间还带点,键盘不会打打不出来,下面都会这样表示优先级。)
【图片】
(1)a=b,文法中有P->…ab…或P->…aQb…

  • 表示…ab…同时归约为P,或者,…aQb…同时归约为P。

(2)a<b,文法中有P->…aR…,而R=>b…或R=>Qb…。(这里的R是正推导)

  • 表示Qb…先归约成R,…aR…再归约为P。b先归约,所以b的优先级大于a。

(3)a>b,文法中有P->…Rb…,而R=>…a或R=>…aQ。(这里的R是正推导)

  • 表示…aQ先归约成R,…Rb…再归约为P。a先归约,所以a的优先级大于b。

如果一个算符文法G中的终结符对(a,b)至多存在一种优先关系,或者不存在,那么G是一个算符优先文法

二、算符优先分析算法的准备

1.给出一个通用的 算术表达式 文法

  • 可以先看书上P90页的5.4的例子,理解一下过程。(如果手边没有书也没有关系,下面的内容跟书上的例子差不多)
  • 给出文法:
文法G(E):
E-> T | E+T | E-T
T-> F | T*F | T/F
F-> (E) | i
--------------------------
[解释]:这是一个常见且用途广泛的算数表达式文法,里面有+,-,*,/,(,)和数字i。
[例子]:1 + 2 * ( (3+4) / 5 + 6) - 7 。(这里的1234567就是i)

2.三个前提

  1. 先把文法的每个产生式分开写
E->T
E->E+T
E->E-T
T->F
T->T*F
T->T/F
F->(E)
F->i

2.1 构建FirstVT和LastVT集合

  • 介绍一下FirstVT和LastVT集合,引用书上P91的内容。
    在这里插入图片描述

(1)FirstVT(P)是P的产生式右部的第一个终结符。

  • [定义] FirstVT(P) = { a | P=>a…或P=>Qa… 。这里a是VT,Q是VN,P都是证推导,即一步或多步推导}
  • 针对(优先级)a < b

(2)LastVT(P)是P的产生式右部的最后一个终结符。

  • [定义] FirstVT(P) = { a | P=>…a或P=>…aQ 。这里a是VT,Q是VN,P都是证推导,即一步或多步推导}
  • 针对(优先级)a > b
  • 那么为什么要构建这两个集和?下面的2.2步就会用到。比如对产生式E->E+T+的优先级就小于所有FirstVT(T)的终结符。这样可以避免用观察法构造优先关系时出现的一些失误。
  1. 构造两个集合的方法
  • 这里因用书上P91上的内容,上一张图上有,中间黑笔圈出来的部分。

(1)FirstVT(P)的构造:

  • 若有产生式P->a…或P->Qa…,则把a添加到FirstVT(P)中。
  • 若P有产生式P->Q…,且b在FirstVT(Q)中,则把b也添加到FirstVT(P)中。
  • [例]: 有产生式P-> a | Q | RcQ-> bkkkkFirstVT(P)={a,c,b}。

(2)LastVT(P)的构造:(书上没有这个构造,这个对比FirstVT自己推出来的)

  • 若有产生式P->…a或P->…aQ,则把a添加到FirstVT(P)中。
  • 若P有产生式P->…Q,且b在LastVT(Q)中,则把b也添加到LastVT(P)中。
  • [例]: 有产生式P-> a | Q | cRQ-> nnnbLastVT(P)={a,c,b}。
  1. 基于上面给出的G(E)构造两个集合
FirstVT(E) = {+, -, *, /, (, i}
FirstVT(T) = {*, /, (, i}
FirstVT(F) = {(, i}
LastVT(E) = {+, -, *, /, ), i}
LastVT(T) = {*, /, ), i}
LastVT(F) = {), i}

2.2 构建非终结符之间的优先关系表

  1. 根据上面两个集合构建算符优先表
  • 这里不考虑#的优先级,因为它总是低于所有的VT。
+-*/i
+>><<<><
->><<<><
*>>>><><
/>>>><><
(<<<<<=<
)>>>>>
i>>>>>

2.3 构建优先函数(简化优先关系表)

  1. 这个例子中的VT比较少,如果一个文法中有几百几千个VT那么维护这么一个表是很费空间的一件事。所以用优先函数来化简这张表。
  • 优先函数是什么,这里引用概括书上P94的内容。

【定义】:优先函数

  • 节省存储空间。
  • f称为入栈优先函数,g称为比较优先函数。通俗的说,f表示的是左边符号的优先级大小,g表示的是右边符号优先级的大小。

【构造方法】:

  • 通过有向图构造优先函数。
  1. 如果a>b或者a=b就画一条fa到gb的有向边。a<b或a=b就画一条gb到fa的有向边。
  2. 从一个节点出发,所能到达的节点数(包括自身),赋值给函数,比如说从fa出发一共能到达3个节点,fa=3。

根据文法G(E)
在这里插入图片描述

  • 通过数从一个节点出发,所能到达的节点数(包括自身),构建优先函数的关系。
下面给出从一个节点出发,所能到达节点的集合。
src{ f+ } = dst { f+, g+, f(, g), g- } = 5
src{ f- } = dst { f-, g+, f(, g), g- } = 5
src{ f* } = dst { f*, g+, f(, g), g-, g*, g/, f+, f- } = 9
src{ f/ } = dst { f/, g+, f(, g), g-, g*, g/, f+, f- } = 9
src{ f( } = dst { f(, g) } = 2
src{ f) } = dst { f), g+, f(, g), g-, g*, g/, f+, f- } = 9
src{ fi } = dst { fi, g+, f(, g), g-, g*, g/, f+, f- } = 9
=======================================================
src{ g+ } = dst { g+, f(, g) } = 3
src{ g- } = dst { g-, f(, g) } = 3
src{ g* } = dst { g*, f(, g), f+, f-, g+, g- } = 7
src{ g/ } = dst { g/, f(, g), f+, f-, g+, g- } = 7
src{ g( } = dst { g(, f(, g), f+, f-, g+, g-, f*, f/, g*, g/ } = 11
src{ g) } = dst { g), f( } = 2
src{ gi } = dst { gi, f(, g), f+, f-, g+, g-, f*, f/, g*, g/ } = 11

下面这张表可以记住,适用于很多情况。

优先函数+-*/i
F5599299
G337711211
  1. 概括的介绍一下,整个算法分析归约的流程
  • 此处介绍的是书上P92的伪代码。
    在这里插入图片描述

(1)这里先介绍一下,两头尖的结构,也就是书上说的最左素短语。这个名称时我们编译原理老师上课时说的,非常形象。

  • 假设有如下的优先级关系(VT之间):h < k < a = b = c > r
  • 那么两头尖的结构就像这样:HhAabBCcrR
  • 在归约的过程中就把AabBCc先归约(归约最左素短语)。

(2)有了两头尖这个概念,那么整个算法的分析归约过程就变得十分清楚。整个过程就是在输入串中不断找“两头尖”,一直到所有输入串都归约到文法的开始符。

  1. 还是用上面(1)给出的优先关系:做下面的分析(只是举例子,不是很严谨,因为输入串中不可能有VN,这里只是为了理解该步骤)
步骤符号栈输入串动作
1#HhkQaAbBcrR#例子最开栈里的内容
2#HhkQaAbBcrR#栈顶 h < 当前输入符号k。k移进
3#HhkQaAbBcrR#栈顶 k = 当前输入符号a。a移进。(这里Q移进是因为在步骤2、3之间发生了归约,如果不理解可以忽略,接下来的步骤6会说明)
4#HhkQaAbBcrR#a = b。b移进
5#HhkQaAbBcrR#b = c。c移进
6#HhkPrR#因为 c > r,出现了两头尖的结构,即k < a = b = c > r,所以把kr之间的内容根据P->QaAbBc归约为P
  1. 不断循环上面表格里的步骤2到6,直到符号栈内是#E,输入串是#,则扫描成功。(这里的E是文法的开始符)
  2. 结合下面408的真题可能会理解的更深。

3.小结

小结:在算符优先算法的分析指导的归约过程中,有以下特点:

1. 每次归约都是严格按照优先函数的指导进行归约,永远是高优先级的先归约。
2. 当且仅当符号栈内出现“两头尖”的结构时进行归约。
3. 每次归约的都是最左素短语。

三、以 408【2012 统考真题】为例子套用上述方法分析

1.题目

【2012 统考真题】已知操作符包括 +、 -、 *、 /、(、 )。将中缀表达式a+b-a*((c+d)/e-f)+g转换为等价的后缀表达式ab+acd+e/f-*-g+时,用栈来存放暂时还不能确定运算次序的操作符。若栈初始时为空,则转换过程中同时保存在栈中的操作符的最大个数是(A)。

  • A. 5
  • B. 7
  • C. 8
  • D. 11

2.用一个简单的观察法把中缀转换为后缀

  • 给出的运算符有这样的优先级:括号>乘除>加减。那么就根据优先级从小到大的顺序对中缀表达式分组。有如下步骤:
分组:Ⅰ={a+b},={a*((c+d)/e-f)},={g}。
那么有,原式=-+Ⅲ。
由观察法得后缀表达式:原式(后缀)=ⅠⅡ-+。
同理对Ⅰ,,Ⅲ里面按照这种方法再分组,用观察法得出每个小组得后缀表达式,再接到一起。
Ⅰ变为后缀:ab+
Ⅱ再分组:Ⅱ①={a}, Ⅱ②={((c+d)/e-f)},=Ⅱ①*Ⅱ②
Ⅱ转换成后缀:Ⅱ①Ⅱ②*
原式(后缀)=ⅠⅡ①Ⅱ②*-+
...
...
...
最后得,原式= ab+acd+e/f-*-g+

3.用算符优先分析算法把中缀转换为后缀

  • 根据上面第6条优先算法的分析流程:
    在这里插入图片描述

  • 这里有一点需要注意,就是根据在归约的时候,只要产生式形状相近就可以归约,不用VN之间一一对应,比如:产生式E->E-T那么当栈内出现F-T时,可以直接把F-T归约为E

  1. 对于该题目的详细分析步骤

分析步骤:如下,

在这里插入图片描述

3.1小结

  1. 那么问题来了,这个过程和后缀表达式有什么关系呢?
    不难发现:
    1. 每次归约的符号连起来正好是后缀表达式 ,即上图最右边一列方括号里的符号。(方括号里标红的两项,是因为有括号需要把之前的运算结果归约,因为在后缀表达式里没有括号,所以这里不要带括号的项。最后结果是对的。)
    2. 连起来写就是ab+acd+e/f-*-g+,到这里,整个中缀转后缀表达式的原理就大致搞清楚了。

4.给出王道的答案(图片)

在这里插入图片描述

参考资料

  • 《程序设计语言 编译原理》(第3版)
  • 王道论坛2022年数据结构考研复习指导
  • 国防科大王挺教授的慕课
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值