通过一个视频,10分钟一遍学会LL(1)的First集和Follow集

本文详细讲解了上下文无关文法的First集和Follow集的概念,并通过实例解析了它们的计算规则。First集是文法规则起始符号可能的开头终结符集合,Follow集则是非终结符后续可能出现的符号集合。文中还给出了多个示例,帮助读者深入理解First集和Follow集的求解过程,并解答了读者在理解过程中可能遇到的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写这篇博客的原因,是因为考试前以为自己已经将这个问题弄清楚了,但是,考试的时候,发现自己还是不会,特别是求follow集合。虽然考试结束了,希望可以帮助屏幕前的你,可以真正理解这个问题。

码字和做视频都不容易,可以给个三连吗?嗷呜~

2020-09-12更新第一版

2021-04-13更新第二版

2021-06-22更新第三版

讲解视频

博客对应的视频教程地址(一定要看看):https://www.bilibili.com/video/BV17K4y1a72M#reply4409274160

视频中的一些问题

下面列举的问题可以先不看,等到视频看完,再拉到对应的时间点看视频中小小的错误

视频的05:12 剪辑视频的时候写错了,正确的应该是First(β) 
视频的07:11 忘记计算First(T)和Follow(T),笔记中补了
视频的09:09 利用规则2或3两边可以是一样的非终结符
视频的12:03 First(E')和First(T')应该用逗号分隔

image-20210413214242541


image-20210413214917997


image-20210413221359517
在这里插入图片描述

First集

官方定义

设G=(VT,VN,S,P)是上下文无关文法 ,则

image-20200911080521074

理解定义

不求信达雅,但求“说人话”。官方定义看不懂?下面的描述比较通俗易懂。

FIRST(A)是以A的开始符的集合,A的所有可能推导的开头终结符或者是ε

例子

通过例子来加强理解。

  1. 后面跟的不是非终结符
...
A->aB|ε
A->c
...
First(A)={a,ε,c}
  1. 后面跟非终结符(一)
...
A->Ba
B->b
...
First(A)={b}
  1. 后面跟非终结符(二)
...
A->Bc
B->b|ε
...
First(A)={b,c}
  1. 后面跟非终结符(三)
...
A->BC
B->b|ε
C->c|ε
...
First(A)={b,c,ε}

Follow集

相对于First集,Follow集的理解会稍微难一点,但是认真听,还是简单的。

官方定义

这个是信达雅版本的定义

假定S是文法G的开始符号,对于G的任何非终结符A,我们定义

image-20200911080541959

理解定义

这个是“说人话”版本的定义

Follow(A)为非终结符A后跟符号的集合,Follow(A)是所有句型中出现在紧接A之后的终结符或’#’

求解规则

(1)对于文法的开始符号S,置#于Follow(S)中;

(2)若A->αBβ是一个产生式,则把First(β) \ {ε} 加入到Follow(B)中

(3)若A->αB是一个产生式,或A->αBβ是一个产生式且β=>ε,则把Follow(A)加入到Follow(B)中

理解求解规则

(1)对于开始符号,首先将#放入Follow集中

(2)形如A->αBβ

(α可以是终结符或者非终结符或者直接为空,β可以是终结符或者非终结符,
注意β不能为空,B后面要有东西
注意β不能为空,B后面要有东西
注意β不能为空,B后面要有东西

比如

A->aBC
A->aBd
A->BC
A->Bd

将First(β) \ {ε}(即First(β)除去ε) 加入到Follow(B)中

(3)形如A->αB(α可以是终结符或者非终结符或者直接为空)或者A->αBβ是一个产生式且β=>ε

比如

A->B
A->cB

A->dBC
C->ε

将Follow(A)加入到Follow(B)中

综合例子

让我们通过例子来对上面的知识点进行梳理和再次的理解。

综合例子一

注意:[if] 是一个终结符,同理[b] [other] [else] [then]

G(S):S->IETSP|O
I->if
E->b
O->other
L->else
T->then
P->LS|ε
FirstFollow
First(S)={if,other}Follow(S)={#,else}
First(I)={if}Follow(I)={b}
First(E)={b}Follow(E)={then}
First(O)={other}Follow(O)={else,#}
First(L)={else}Follow(L)={if,other}
First( P )={else,ε}Follow( P )={else,#}
First(T)={then}Follow(T)={if,other}

综合例子一 中反馈的问题:

  • S->IETSP|O 中求Follow(S)能运用规则2和3来求解S吗?

解答:

可以的,虽然规则中给出的是A和B两个不同的非终结符,但是
A和B在实际运用的过程中是可以相等的,即B=A
A和B在实际运用的过程中是可以相等的,即B=A
A和B在实际运用的过程中是可以相等的,即B=A
S->IETSP|O S后面有P,利用规则3,将First(P)加入Follow(S)中
但是,发现P可以等于ε,即S->IETS,利用规则二,将Follow(S)加入Follow(S)中
  • 在求Follow(S)发现P->LS|ε也是存在的,那么follow(s)={#,else}+follow( p ),而算到follow( p )发现follow( p )=follow(s) 就不知道怎么算了

解答:(很重要,认认真真地看)

我们需要同时满足
follow(s)={#,else}+follow(p)
follow(p)=follow(s)
将第二个式子带入一式得到
follow(s)={#,else}+follow(s)
注意:不能将follow(s)约掉,而是要想怎么样上面的等式仍然成立
那么,我们就会发现follow(s)只能等于{#,else}
因为 {#,else}={#,else}+{#,else}是成立的

综合例子二

G(E):E->TE'
E'->+TE'|ε
T->FT'
T'->*FT'|ε
F->(E)|i
FirstFollow
First(E)={(,i}Follow(E)={#,)}
First(E’)={+,ε}Follow(E’)={#,)}
First(T)={(,i}Follow(T)={+,#,)}
First(T’)={*,ε}Follow(T’)={+,#,)}
First(F)={(,i}Follow(F)={*,+,#,)}

综合例子二 中反馈的问题:

  • 为什么求E的FOLLOW集看到F->(E)|I直接将)右括号直接加入Follow中

解答:

这个问题需要回到最初的定义来理解
follow集的意思是紧跟后面的符号集,E后面有),即E后面有终结符。
属于规则二,但是终结符没有first和follow,所以直接添加即可。

综合例子三

G[S]: S→aH
H→aMd
H→d
M→Ab
M→ε
A→aM
A→e
FirstFollow
First(S)={a}Follow(S)={#}
First(H)={a,d}Follow(H)={#}
First(M)={a,e,ε}Follow(M)={d,b}
First(A)={a,e}Follow(A)={b}

综合例子四

G(E):E->TE'
E'->+E|ε
T->FT'
T'->T|ε
F->PF'
F'->*F'|ε
P->(E)|a|b|^
FirstFollow
First(E)={(,a,b,^}Follow(E)={#,)}
First(E’)={+,ε}Follow(E’)={#,)}
First(T)={(,a,b,^}Follow(T)={+,#,)}
First(T’)={(,a,b,^,ε}Follow(T’)={+,#,)}
First(F)={(,a,b,^}Follow(F)={(,a,b,^,+,#,)}
First(F’)={*,ε}Follow(F’)={(,a,b,^,+,#,)}
First( P )={(,a,b,^}Follow( P )={*,(,a,b,^,+,#)}

综合例子四中反馈的问题:

怎么求follow(E)和follow(E‘)?

根据G(E)和规则一,#加入follow(E)
根据P->(E)|a|b|^和规则二,)加入follow(E)
根据E'->+E|ε和规则三,将follow(E')到follow(E)里面

根据E->TE'和规则三得到将follow(E)到follow(E‘)里面 

=>
Follow(E)={#,)}+Follow(E')
Follow(E')=Follow(E)
根据综合例子一中一样的分析方法
Follow(E)={#,)}+Follow(E)=>Follow(E)={#,)}
<think>好的,我现在需要解决用户的问题,使用C++实现求取文法产生式的SELECT,并判断是否是LL(1)文法。用户之前已经询问过关于修改文法解析的问题,现在这个任务可能涉及到之前的解析部分,但需要更多的步骤,比如计算FIRSTFOLLOW,然后生成SELECT,最后判断是否冲突。 首先,我需要理解用户的需求。输入是多个产生式,输出每个产生式的SELECT,并按顺序输出,最后判断是否为LL(1)文法。关键点在于正确计算FIRSTFOLLOW,以及SELECT的交判断。 首先,我需要确定如何表示文法。可能需要一个结构体或类来存储产生式,左边是非终结符,右边是符号的向量。例如,每个产生式可以表示为左部非终结符的列表。 接下来,计算FIRSTFIRST的规则是,对于每个符号X,FIRST(X)是所有可能出现在推导中的第一个终结符。如果X是终结符,FIRST(X)就是{X}。如果是非终结符,则需要考虑所有产生式,递归计算。同时需要考虑ε的情况,比如当某个产生式可以推导出ε时,需要继续看后面的符号。 然后,计算FOLLOWFOLLOW(A)是所有可能紧跟在A后面的终结符。这需要遍历所有产生式,找到A出现的位置,然后查看其后的符号的FIRST,如果后面的符号可以推导出ε,则需要将FOLLOW(A)加入。 接下来,对于每个产生式A→α,需要判断α是否可以推导出ε。这涉及到检查α中每个符号是否都能推导出ε,或者是否α本身是ε。 然后,根据α是否可推导出ε,计算SELECT。如果α不能推导出ε,SELECTFIRST(α)。如果可以,则是(FIRST(α))FOLLOW(A)。 在实现过程中,需要注意数据结构的设计。可能需要一个map来存储每个非终结符的FIRSTFOLLOW,以及每个产生式的信息。 接下来,如何判断是否是LL(1)文法。对于每个非终结符A,检查它的所有产生式的SELECT是否两两不相交。如果有任何交,则不是LL(1)文法。 此外,输入输出的格式需要注意。输入的第一行是行数n,然后是n行产生式。输出每个产生式的SELECT,按照输入顺序,合元素按ASCII顺序排列,最后输出Yes或No。 现在,考虑可能的挑战: 1. 如何高效计算FIRSTFOLLOW,尤其是处理ε的情况。 2. 处理产生式右部的符号分解,尤其是可能有多个字符的情况,比如终结符可能包含多个字符,如“id”或“num”。但根据用户之前的例子,可能假设每个符号是单个字符?或者需要更复杂的符号拆分。 3. 输入的产生式可能需要解析,分解左部右部。例如,“S->aA”分解为左部S,右部aA。这里需要处理符号的拆分,可能需要将右部分解为符号列表,比如将“aA”拆分为['a','A']。 假设符号之间是连续的,没有空格,每个符号可以是单个字符或者用特殊符号(比如大写字母是非终结符,小写是终结符,或者用<>区分,但用户之前的例子中的输入样例是类似“S->aA”,所以可能非终结符是单个大写字母,终结符是单个小写字母或字符)。但用户可能希望更通用的处理,比如允许多字符符号,但需要明确分割方式。但根据输入样例中的情况,例如“A->bAS”,右部可能是b、A、S三个符号。所以需要将右部分解为各个符号,每个符号可能是一个字符,或者多字符?比如,如果有一个符号是“ab”,那如何拆分? 这可能是一个问题。用户给出的输入样例中的产生式右部似乎用单个字符作为符号,例如aA被拆分成aA。因此,假设每个符号是一个字符,所以在解析产生式右部时,需要将每个字符作为一个符号处理。例如,“aA”分解为['a','A']。但如果有类似“id”这样的终结符,可能需要不同的处理。因此,可能需要用户澄清符号的拆分方式,但根据样例,可能每个字符视为一个符号。 因此,在解析产生式时,右部的每个字符将被视为一个符号。例如,“bAS”分解为['b','A','S']。而终结符可以是像d、a这样的单个字符,非终结符可能如S、A等。 接下来,代码的结构: 1. 读取输入,解析每个产生式,存储为结构体,包括左部、右部的符号列表,以及原始顺序。 2. 计算所有符号的FIRST。 3. 计算所有非终结符的FOLLOW。 4. 对于每个产生式,计算是否可以推导出ε。 5. 计算每个产生式的SELECT。 6. 检查所有同一非终结符的产生式的SELECT是否有交。 7. 输出结果。 现在,关于FIRST的计算: 对于每个符号X: - 如果X是终结符,FIRST(X) = {X} - 如果X是非终结符: - 遍历所有产生式X→Y1Y2...Yn - 对每个产生式,检查Y1FIRST,如果Y1可以推导出ε,则继续看Y2,依此类推。 - 将所有这些终结符加入FIRST(X),直到某个Yk不能推导出ε,或者所有Y都可以推导出ε,此时如果整个产生式可以推导出ε,则加入ε。 这可能需要递归计算,或者用迭代的方式直到没有变化。 同样,对于判断一个符号串α是否可以推导出ε: 如果α为空(即产生式右部为ε),则可以推导出ε。 否则,对于每个符号Y_i in α,检查Y_i是否可以推导出ε。如果所有Y_i都可以推导出ε,则整个α可以推导出ε。 关于FOLLOW的计算: 初始化每个非终结符的FOLLOW为空,除了开始符号的FOLLOW可能包含$(假设用户可能需要处理这个,但根据样例输出中没有#,所以可能输入中的#代表结束符号?样例中的输入样例输出可能包含#,但用户给出的例子中的输出并没有。例如输入样例的输出中的SELECT没有#,但问题描述中提到SELECT合中可能含有#。所以需要注意,FOLLOW可能包含#,比如开始符号的FOLLOW可能初始化为{#}? FOLLOW的计算规则: 对于每个产生式A→αBβ: - B是非终结符,将FIRST(β)(除去ε)加入FOLLOW(B) - 如果β可以推导出ε,则将FOLLOW(A)加入FOLLOW(B) 此外,对于产生式A→αB,则将FOLLOW(A)加入FOLLOW(B) 这需要多次迭代直到所有FOLLOW不再变化。 现在,如何将这些步骤转化为C++代码? 可能需要的数据结构: - 一个产生式的结构体或类,存储左部字符串,右部vector<string>,原始顺序的索引。 - 全局的map<string, set<string>> first_sets; // 符号到FIRST的映射 - 全局的map<string, set<string>> follow_sets; // 非终结符到FOLLOW的映射 - 全局的map<string, vector<Production>> productions; // 非终结符到其所有产生式的映射 - 所有非终结符的合,终结符的合,以及所有符号的合。 处理步骤: 1. 输入处理: 读取n,然后读取n行产生式。每行解析为左部右部。例如,处理“S->aA”为左部S,右部['a','A']。对于空产生式,如“A->ε”,右部是空列表。 假设ε用特定的符号表示,比如“ε”或者用户可能使用“#”或者其他符号?根据问题描述中的输入样例,样例中的空产生式可能用ε表示,但用户给出的输入样例中没有空产生式。在问题描述中的SELECT定义中提到,可能包含#,所以需要明确ε的表示。例如,在输入中,空产生式可能写成A->ε,此时右部为空。 因此,在解析时,如果右部是ε,则处理为空列表。需要将输入中的“ε”视为空字符串。 例如,对于输入行“A->ε”,左部是A,右部为空列表。 这一步需要注意如何拆分右部为符号列表。例如,如果右部是“bAS”,则拆分为['b','A','S']。如果右部是“abc”,则拆分为['a','b','c']。或者可能有符号是多字符的?比如存在符号是“id”这样的。但根据用户的输入样例,可能假设每个符号是单个字符。 因此,在解析右部时,每个字符视为一个符号,除了处理ε的情况。例如,当右部是“ε”时,符号列表为空。否则,将每个字符拆分为单独的符号。 例如,输入行“S->aA”的右部拆分为['a','A']。 2. 计算FIRST: 需要为每个符号(终结符非终结符)计算FIRST。 对于终结符,FIRST就是自身。 对于非终结符,需要遍历其所有产生式,并递归计算。 实现方法可以是迭代直到没有变化: 初始化每个非终结符的FIRST为空。 然后,重复以下步骤直到所有FIRST不再变化: 对于每个产生式A→α: 计算该产生式的右部符号串的FIRST合,将其加入A的FIRST中。例如,对于右部Y1Y2...Yn: 初始时,将FIRST(Y1)中的非ε元素加入A的FIRST。如果Y1可以推导出ε,则继续处理Y2,将FIRST(Y2)的非ε元素加入,直到某个Yi不能推导出ε。如果所有Yi都能推导出ε,则将ε加入A的FIRST。 因此,对于非终结符A,遍历其所有产生式,计算每个产生式右部的FIRST,并将这些元素加入A的FIRST。 但这种方法可能需要多次迭代,因为FIRST的依赖关系可能比较复杂。 或者,可以采用递归的方式,但需要注意循环的情况。 但更可靠的方式是使用迭代的方式,直到收敛。 3. 计算是否可以推导出ε: 对于符号串α,可以推导出ε的条件是每个符号都能推导出ε。例如,空符号串可以推导出ε。或者,对于非空符号串Y1Y2...Yn,当每个Yi都能推导出ε时,整个符号串可以推导出ε。 因此,需要为每个符号X维护一个是否可以推导出ε的标志,比如epsilonable[X]。对于终结符,只有当X是ε时才能推导出ε。对于非终结符,如果存在某个产生式,其右部可以推导出ε,则该非终结符可以推导出ε。 计算这个标志可能需要多次迭代: 初始化所有符号的epsilonable为false,除了ε本身。 对于每个非终结符A: 如果存在一个产生式A→α,其中α可以推导出ε,则epsilonable[A] = true。 计算α能否推导出ε的条件是,对于α中的每个符号,都能推导出ε。例如,如果α是空,或者每个符号的epsilonable为true。 这个过程可能需要多次遍历产生式,直到没有变化。 4. 计算FOLLOW: 初始化FOLLOW,对于开始符号S,假设FOLLOW(S)初始化为包含#。但需要明确哪个是开始符号。通常,开始符号是第一个产生式的左部。例如,在样例输入中,第一个产生式是S->aA,所以开始符号是S。因此,在程序中,需要记录开始符号,并初始化其FOLLOW为{#}(如果存在)。 然后,对于每个产生式A→αBβ,其中B是非终结符: 将FIRST(β)中的非ε元素加入FOLLOW(B)。如果β可以推导出ε,则将FOLLOW(A)加入FOLLOW(B)。 这需要遍历每个产生式,并检查每个位置的非终结符B。 例如,对于产生式A→XYZ,其中X、Y、Z是符号: 检查每个位置是否是B,即非终结符。例如,如果Y是一个非终结符B,那么β是Z,所以需要处理Y的位置后的符号。 具体来说,对于产生式A→Y1Y2...Yn: 遍历每个符号Yi(i从0到n-1): 如果Yi是一个非终结符: 查看后面的符号Yi+1...Yn。计算它们的FIRST,如果这个合中包含ε,则需要将A的FOLLOW也加入到Yi的FOLLOW中。 例如: - 对于每个Yi(非终结符): beta = Yi+1 ... Yn first_beta = FIRST(beta) add all elements of first_beta except ε to FOLLOW[Yi] if beta can derive ε, then add all elements of FOLLOW[A] to FOLLOW[Yi] 这个过程需要重复多次,直到所有FOLLOW不再变化。 5. 计算SELECT: 对于每个产生式A→α: 如果α可以推导出ε,则SELECT(A→α) = (FIRST(α) - {ε})FOLLOW(A) 否则,SELECT(A→α) = FIRST(α) 但需要注意,FIRST(α)的计算方式。FIRST(α)是所有可能的第一个终结符的合,包括当α可以推导出ε时的情况。例如,对于α=Y1Y2...Yn,FIRST(α)包括所有可能的第一个终结符,如果Y1可以推导出ε,则包括Y2的FIRST,依此类推。如果所有Yi可以推导出ε,则包括ε。 但根据SELECT的定义,无论α是否推导出ε,SELECT都需要处理两种情况。所以,对于SELECT的计算,首先需要判断α是否可推导出ε,然后根据情况合并FIRST(α)(去掉ε)FOLLOW(A)。 因此,对于每个产生式,需要做以下步骤: a. 计算α是否可以推导出ε。 b. 计算FIRST(α),即该符号串的FIRST。 c. 如果α不能推导出ε,SELECTFIRST(α)。 d. 如果α可以推导出ε,则SELECT(FIRST(α) - {ε})FOLLOW(A)。 注意,ε在SELECT中不会被包含,所以需要确保SELECT合中没有ε。 6. 判断是否为LL(1)文法: 对于每个非终结符A,检查其所有产生式的SELECT是否两两不相交。如果有任何交,则不是LL(1)文法。 需要遍历所有非终结符的产生式,检查每一对产生式的SELECT是否有交。 现在,将这些步骤转化为C++代码: 首先,数据结构: struct Production { string left; vector<string> right; int index; // 用于记录输入顺序,方便输出时按顺序处理SELECT }; vector<Production> all_productions; // 按输入顺序存储所有产生式 map<string, vector<Production>> nt_productions; // 非终结符到其产生式的映射 set<string> non_terminals; // 所有非终结符 set<string> terminals; // 所有终结符 map<string, set<string>> first; // FIRST map<string, set<string>> follow; // FOLLOW map<string, bool> epsilonable; // 是否可以推导出ε 接下来,解析输入: 读取n,然后读取n行。每行的处理: 例如,输入行是“S->aA”,拆分为左部S,右部aA分解为['a','A']。 解析函数: 对于每行,分割为左部右部,用“->”分割。注意可能有空格?根据问题描述的输入样例,用户可能输入中没有空格,但需要处理可能的分割问题。例如,分割左边右边。 在C++中,可以使用string的find函数找到“->”的位置,然后提取左部,右部字符串。 处理右部字符串: 将右部分解为符号列表。如果右部是ε,则符号列表为空。否则,每个字符作为一个符号。例如,“aA”分解为['a','A']。 但需要考虑非终结符可能用大写字母表示,终结符用小写字母或其他符号。例如,在输入样例中,非终结符是S、A,终结符是a、d、b、c。 因此,在分解右部时,每个字符作为一个符号。当字符是大写字母时,视为非终结符;其他视为终结符?或者根据是否存在于非终结符合中? 这可能需要在解析时记录所有非终结符,即所有产生式的左部都是非终结符。因此,在解析左部时,将左部加入non_terminals合。右部中的符号如果是非终结符(即存在于non_terminals中),则视为非终结符,否则视为终结符。 或者,假设左部都是非终结符,右部中的符号如果是非终结符的话,必须出现在左部的某个产生式中。例如,可能产生式中的符号如果是大写字母,则视为非终结符,其他视为终结符。或者,这可能不成立,比如终结符可能包含大写字母?例如,用户可能定义某个非终结符为小写字母,但根据常规习惯,非终结符通常用大写字母或尖括号括起来,但问题中的输入样例显示非终结符是单个大写字母,如S、A,而终结符是单个小写字母,如a、d、b、c。 因此,假设非终结符是大写字母,终结符是其他字符。或者,可能产生式中的非终结符是任意字符串,而终结符是其他符号?例如,在输入样例中,非终结符是S、A,右部的符号如a、d、b、c是终结符,而AS在右部是非终结符。 因此,在分解右部时,每个字符作为一个符号。左部是非终结符(如S、A),而右部的符号如果是非终结符的话,必须出现在左部的合中。否则,视为终结符。例如,在输入样例的第二个产生式“S->d”中,右部是d,这是终结符。在第三个产生式“A->bAS”中,右部是b、A、S,其中AS是非终结符,b是终结符。 因此,在分解右部时,每个字符作为一个符号。对于每个符号: 如果是非终结符(存在于non_terminals合中),则视为非终结符;否则视为终结符。 因此,在解析右部时,需要将每个字符作为符号,并判断是否为非终结符。但是,在初始解析时,non_terminals合可能还未完全填充,因为可能先处理某个产生式右部的符号,而该符号的非终结符还未出现。例如,如果有一个产生式B->C,而C的产生式在后面才出现。因此,正确的方法应该是,在第一次遍历时收所有左部的非终结符,然后在分解右部符号时,将符号属于左部的非终结符的视为非终结符,否则视为终结符。 这可能比较复杂,或者可能需要两遍处理:第一遍所有左部的非终结符,第二遍分解右部符号。 或者,在分解右部符号时,所有大写字母视为非终结符,其他视为终结符。例如,根据输入样例中的情况,这可能成立。但用户可能没有明确说明,因此可能存在假设风险。 或者,将左部作为非终结符,右部中的每个符号如果是非终结符的话,必须出现在左部的合中。否则视为终结符。这可能需要动态判断。例如,在解析右部符号时,如果该符号是某个产生式的左部,则视为非终结符,否则视为终结符。这可能更准确,但需要处理循环依赖。 例如,在解析右部符号时,如果该符号存在于non_terminals合中,则是非终结符,否则是终结符。non_terminals合初始包含所有产生式的左部。因此,在分解右部时,对于每个字符,如果该字符是一个非终结符(存在于non_terminals中),则视为非终结符,否则视为终结符。 因此,在代码中,处理步骤: 首先,读取所有产生式的左部,并加入non_terminals合。 然后,处理右部符号,每个字符作为一个符号,判断是否在non_terminals中,如果是,则为非终结符,否则为终结符,并加入terminals合。 这可能更合理。 例如,处理输入: 4 S->aA S->d A->bAS A->c 左部是S、S、A、A,所以non_terminals包含SA。 处理右部: aA → 'a''A'。因为'A'在non_terminals中,所以是非终结符。'a'不在,所以是终结符。 d → 'd'是终结符。 bAS → 'b'(终结符),'A'(非终结符),'S'(非终结符)。 c → 'c'(终结符)。 因此,terminals合包含a、d、b、c。 因此,在代码中,分解右部符号的步骤: 对于每个字符: if (non_terminals.count(string(1, c))) → 视为非终结符 else → 视为终结符 注意,这里的c是字符,需要转换为string,或者将non_terminals存储为char的合?或者,假设左部都是单个字符?根据输入样例中的情况,左部是单个字符,如S、A。因此,可能假设非终结符是单个字符的大写字母,而终结符是其他字符。这可能简化处理,但用户可能没有明确说明。 但问题描述中并没有明确说明符号的表示方式,所以需要根据输入样例推断。输入样例中的非终结符是单个大写字母,终结符是单个小写字母或其他符号(如d,c等)。因此,可能假设: - 非终结符是单个大写字母。 - 终结符是其他单个字符(包括小写字母、数字、符号等)。 因此,在分解右部时,每个字符单独处理: 如果字符是大写字母 → 非终结符 否则 → 终结符 这可能更简单,不需要依赖non_terminals合的判断,但需要确保左部是大写字母。根据输入样例,左部是S、A,都是大写字母。 因此,在代码中: 左部必须是大写字母,产生式左部只能是单个字符的非终结符。右部的符号如果是大写字母则为非终结符,否则为终结符。 这可能更简单,但可能存在局限性。例如,无法处理多字符的非终结符。但根据输入样例问题描述,可能这是可以接受的。 因此,在解析时: 对于每个产生式行: 分割为左部右部。左部是产生式箭头左边的部分,例如“S”或“A”。 右部分解为各个符号,每个字符作为符号: 例如,“aA”分解为['a', 'A'] → 终结符a,非终结符A。 “ε”分解为空的符号列表。 因此,在代码中,解析右部的函数可能如下: vector<string> split_right(const string& right_str) { vector<string> symbols; if (right_str == "ε") { // 处理空产生式 return symbols; } for (char c : right_str) { string s(1, c); if (isupper(c)) { // 非终结符 symbols.push_back(s); non_terminals.insert(s); } else { // 终结符 symbols.push_back(s); terminals.insert(s); } } return symbols; } 但需要注意,ε的处理。例如,如果右部是“ε”,则符号列表为空。否则,每个字符作为符号。 现在,问题中的输入样例中的产生式没有空产生式,但测试时需要处理这种情况。例如,输入可能包含A->ε。 接下来,计算epsilonable: 初始化所有符号的epsilonable为false。 对于终结符,只有当符号是ε时,epsilonable为true。但根据我们的处理,空产生式被表示为右部为空,所以终结符中的ε可能不存在。例如,假设ε在输入中表示为右部是“ε”,此时在解析时,右部会被处理为空列表。因此,在epsilonable的初始化中,终结符的epsilonable只有符号为ε时才为true,但在我们的解析中,可能不存在这样的符号。例如,假设用户输入中ε表示为“ε”字符串,但处理时会被分解为空列表。所以可能需要在解析右部时,将“ε”转换为空列表。 这可能需要特别处理。 例如,当右部字符串是“ε”时,符号列表为空。否则,每个字符作为符号。 因此,在split_right函数中: if (right_str == "ε") { return symbols; // empty vector } 这样,在产生式右部为空的情况下,表示该产生式是A→ε。 现在,计算epsilonable: 对于每个符号X: - 如果是终结符,epsilonable[X] = (X == ε的符号?或者,只有当产生式右部为空时,表示可以推导出ε) 这可能有点混淆。例如,终结符的epsilonable只有ε符号本身为true。但根据我们的处理,终结符中没有ε符号,因为ε在输入中被处理为空产生式的右部。因此,在解析时,终结符只能是具体的字符,如a、d等,而ε的处理是右部为空。 因此,非终结符的epsilonable的计算是:存在至少一个产生式,其右部的所有符号都可以推导出ε。 例如,对于非终结符A: 如果A有一个产生式右部为空(即A→ε),则该产生式右部的符号列表为空,可以推导出ε。所以epsilonable[A] = true。 或者,如果A有一个产生式右部的符号列表中的每个符号都可以推导出ε,那么该产生式可以推导出ε,从而A可以推导出ε。 因此,计算epsilonable的步骤: 初始化epsilonable为false。 对于每个非终结符A: 检查是否至少有一个产生式A→α,其中α可以推导出ε。如果存在这样的产生式,则epsilonable[A] = true。 对于每个产生式右部α: α可以推导出ε当且仅当α为空,或者每个符号X in α的epsilonable[X]为true。 因此,计算epsilonable需要迭代直到稳定: repeat: changed = false for each non-terminal A: for each production A→α: can_derive_epsilon = true for each symbol X in α: if X is terminal: can_derive_epsilon = false (因为终结符只有ε才能推导出epsilon,而这里的X是具体的符号,如a、b等,无法推导出ε) break else: if not epsilonable[X]: can_derive_epsilon = false break if can_derive_epsilon: if epsilonable[A] was false: epsilonable[A] = true changed = true until no changes 此外,终结符的epsilonable只能在符号是ε时成立,但根据我们的解析方式,输入中的ε被处理为右部为空,而终结符中没有ε符号。所以,在代码中,终结符的epsilonable总是false。 这可能是一个问题,比如,如果用户输入的产生式右部是ε,则符号列表为空,此时该产生式可以推导出ε。例如,A→ε被解析为右部为空列表。在计算该产生式是否可推导出ε时,右部为空,所以返回true。因此,A的epsilonable会被设置为true。 所以,正确的处理是,当产生式的右部为空时,该产生式可以推导出ε,从而A的epsilonable可能为true。 现在,实现epsilonable的计算: 初始化epsilonable为false。 然后,迭代: do { changed = false; for 每个非终结符A: for 每个产生式A→α: bool can_derive = true; for (string X : α) { if (X是终结符) { can_derive = false; break; } else { if (!epsilonable[X]) { can_derive = false; break; } } } if (can_derive) { if (!epsilonable[A]) { epsilonable[A] = true; changed = true; } break; // 只要有一个产生式可以推导出ε,就设置A的epsilonable为true } } while (changed); 这可能需要多次迭代,直到所有可能的epsilonable被正确计算。 例如,考虑产生式A→BC,B→ε, C→ε。第一次迭代时,BC的epsilonable可能还未被计算为true。所以需要多次迭代。 现在,计算FIRST: 对于每个符号X: 如果是终结符,FIRST[X] = {X} 如果是非终结符,FIRST[X] = ∪ { FIRST(α) | X→α } 其中,FIRST(α)是符号串α的FIRST,计算方式为: 初始化first_alpha = empty set has_epsilon = true for each symbol Y in α: add FIRST[Y] - {ε} to first_alpha if ε not in FIRST[Y]: has_epsilon = false break if has_epsilon: add ε to first_alpha 所以,对于非终结符X的FIRST的更新,需要收所有产生式右部α的FIRST(alpha)合。 这可能也需要迭代的方式: 初始化每个非终结符的FIRST为空。 重复: changed = false for 每个非终结符A: old_size = FIRST[A].size() for 每个产生式A→α: compute first_alpha as described add all symbols in first_alpha to FIRST[A] if FIRST[A] has new elements: changed = true if FIRST[A].size() > old_size: changed = true 直到没有变化。 这可能效率较低,但对于小型文法是可行的。 现在,计算FOLLOW: 初始化FOLLOW: 对于开始符号S,FOLLOW[S] = { # } 其他非终结符的FOLLOW初始化为空。 然后,迭代处理: 重复: changed = false for 每个产生式A→α: 遍历α中的每个符号,位置i,符号B(非终结符): beta = α[i+1 ... end] first_beta = compute_FIRST(beta) if first_beta contains ε: follow_B = FOLLOW[B] add all elements of FOLLOW[A] to follow_B if FOLLOW[B] changes: changed = true remove ε from first_beta add all elements of first_beta to FOLLOW[B] 如果beta可以推导出ε(即compute_epsilonable(beta)为true): add all elements of FOLLOW[A] to FOLLOW[B] if FOLLOW[B] changes: changed = true 直到没有变化。 这可能比较复杂,需要正确计算每个符号的位置后的beta的FIRST,并处理是否可以推导出ε。 因此,需要两个辅助函数: 1. compute_FIRST_for_sequence(α) → 返回FIRST,包含ε if α can derive ε. 2. compute_epsilonable_for_sequence(α) → 返回是否α can derive ε. 现在,编写这些函数: compute_FIRST_for_sequence的伪代码: set<string> compute_first_for_sequence(const vector<string>& alpha) { set<string> result; bool can_derive_epsilon = true; for (const string& sym : alpha) { if (terminals.count(sym)) { // sym is terminal result.insert(sym); can_derive_epsilon = false; break; } else { // non-terminal for (const string& s : first[sym]) { if (s != "ε") { result.insert(s); } } if (epsilonable[sym]) { // continue to next symbol } else { can_derive_epsilon = false; break; } } } if (can_derive_epsilon) { result.insert("ε"); } return result; } compute_epsilonable_for_sequence: bool can_derive_epsilon(const vector<string>& alpha) { for (const string& sym : alpha) { if (terminals.count(sym)) { // terminal return false; // 除非该终结符是ε,但根据之前的处理,这里不存在 } else { if (!epsilonable[sym]) { return false; } } } return true; } 现在,回到FOLLOW的计算: 对于每个产生式A→α: 遍历α中的每个位置i,符号B = α[i] 如果B是非终结符: beta = α的子列表从i+1到末尾 first_beta = compute_first_for_sequence(beta)first_beta中的非ε元素加入FOLLOW[B] 如果beta可以推导出ε(即can_derive_epsilon(beta)为true),则将FOLLOW[A]的所有元素加入FOLLOW[B] 这需要多次迭代,直到没有变化。 现在,编写SELECT的代码: 对于每个产生式prod in all_productions: A→α 判断α是否可以推导出ε → can_derive_epsilon(alpha) 然后计算SELECT: if can_derive_epsilon(alpha): select_set = (first_alpha - ε)follow[A] else: select_set = first_alpha 其中,first_alpha是compute_first_for_sequence(alpha),可能包含ε。 但根据问题描述中的SELECT定义,SELECT合中不包含ε,但可能包含#。所以需要移除ε。 最后,判断所有同一非终结符的产生式的SELECT是否有交。 现在,将各部分整合到C++代码中: 步骤: 1. 解析输入,生成all_productions,并填充non_terminalsterminals合。 2. 计算epsilonable。 3. 计算FIRST。 4. 初始化FOLLOW,并计算。 5. 计算每个产生式的SELECT。 6. 输出SELECT。 7. 判断是否为LL(1)文法。 现在,编写代码: 首先,定义结构体: #include <iostream> #include <vector> #include <map> #include <set> #include <algorithm> #include <sstream> using namespace std; struct Production { string left; vector<string> right; int index; // 输入的顺序 }; vector<Production> all_productions; map<string, vector<Production>> nt_productions; set<string> non_terminals; set<string> terminals; map<string, set<string>> first; map<string, set<string>> follow; map<string, bool> epsilonable; // 计算epsilonable void compute_epsilonable() { bool changed; do { changed = false; for (const auto& nt : non_terminals) { for (const auto& prod : nt_productions[nt]) { bool can_derive = true; for (const string& sym : prod.right) { if (terminals.count(sym)) { // 终结符无法推导出ε can_derive = false; break; } else { if (!epsilonable[sym]) { can_derive = false; break; } } } if (can_derive) { if (!epsilonable[nt]) { epsilonable[nt] = true; changed = true; } } } } } while (changed); } // 计算FIRST void compute_first() { // 终结符的FIRST for (const auto& t : terminals) { first[t].insert(t); } // 非终结符的FIRST bool changed; do { changed = false; for (const auto& nt : non_terminals) { for (const auto& prod : nt_productions[nt]) { set<string> first_alpha; bool has_epsilon = true; for (const string& sym : prod.right) { if (terminals.count(sym)) { // 终结符 first_alpha.insert(sym); has_epsilon = false; break; } else { // 非终结符 for (const string& s : first[sym]) { if (s != "ε") { first_alpha.insert(s); } } if (!epsilonable[sym]) { has_epsilon = false; break; } } } if (has_epsilon) { first_alpha.insert("ε"); } // 将first_alpha加入first[nt] size_t old_size = first[nt].size(); for (const string& s : first_alpha) { first[nt].insert(s); } if (first[nt].size() > old_size) { changed = true; } } } } while (changed); } // 计算产生式右部的FIRST set<string> compute_first_for_sequence(const vector<string>& alpha) { set<string> result; bool has_epsilon = true; for (const string& sym : alpha) { if (terminals.count(sym)) { result.insert(sym); has_epsilon = false; break; } else { for (const string& s : first[sym]) { if (s != "ε") { result.insert(s); } } if (!epsilonable[sym]) { has_epsilon = false; break; } } } if (has_epsilon) { result.insert("ε"); } return result; } // 计算FOLLOW void compute_follow(const string& start_symbol) { for (auto& nt : non_terminals) { follow[nt].clear(); } follow[start_symbol].insert("#"); // 假设#是结束符号 bool changed; do { changed = false; for (const auto& prod : all_productions) { const string& A = prod.left; const auto& alpha = prod.right; for (size_t i = 0; i < alpha.size(); ++i) { const string& B = alpha[i]; if (non_terminals.count(B)) { // B是非终结符 vector<string> beta(alpha.begin() + i + 1, alpha.end()); set<string> first_beta = compute_first_for_sequence(beta); // 添加FIRST(beta) - ε到FOLLOW[B] size_t old_size = follow[B].size(); for (const string& s : first_beta) { if (s != "ε") { follow[B].insert(s); } } if (follow[B].size() > old_size) { changed = true; } // 如果beta可以推导出ε,添加FOLLOW[A]到FOLLOW[B] bool beta_derives_epsilon = true; for (const string& sym : beta) { if (terminals.count(sym)) { beta_derives_epsilon = false; break; } else if (!epsilonable[sym]) { beta_derives_epsilon = false; break; } } if (beta_derives_epsilon || beta.empty()) { old_size = follow[B].size(); for (const string& s : follow[A]) { follow[B].insert(s); } if (follow[B].size() > old_size) { changed = true; } } } } } } while (changed); } // 判断产生式右部是否可以推导出ε bool can_derive_epsilon(const vector<string>& alpha) { for (const string& sym : alpha) { if (terminals.count(sym)) { return false; } else if (!epsilonable[sym]) { return false; } } return true; } int main() { int n; cin >> n; cin.ignore(); // 忽略换行符 string start_symbol; for (int i = 0; i < n; ++i) { string line; getline(cin, line); // 解析产生式 size_t arrow_pos = line.find("->"); if (arrow_pos == string::npos) { cerr << "Invalid production: " << line << endl; return 1; } string left = line.substr(0, arrow_pos); string right_str = line.substr(arrow_pos + 2); // 处理左部 left.erase(remove(left.begin(), left.end(), ' '), left.end()); if (left.empty()) { cerr << "Empty left part in production: " << line << endl; return 1; } non_terminals.insert(left); // 处理右部 right_str.erase(remove(right_str.begin(), right_str.end(), ' '), right_str.end()); vector<string> right; if (right_str == "ε") { // 空产生式,right为空 } else { for (char c : right_str) { string sym(1, c); right.push_back(sym); if (isupper(c)) { non_terminals.insert(sym); } else { terminals.insert(sym); } } } Production prod{left, right, i}; all_productions.push_back(prod); nt_productions[left].push_back(prod); } if (all_productions.empty()) { cerr << "No productions provided." << endl; return 1; } start_symbol = all_productions[0].left; // 初始化epsilonable for (const auto& nt : non_terminals) { epsilonable[nt] = false; } compute_epsilonable(); // 计算FIRST compute_first(); // 计算FOLLOW compute_follow(start_symbol); // 计算每个产生式的SELECT,并存储结果 vector<set<string>> select_sets; for (const auto& prod : all_productions) { set<string> select_set; bool alpha_derives_epsilon = can_derive_epsilon(prod.right); set<string> first_alpha = compute_first_for_sequence(prod.right); if (alpha_derives_epsilon) { // SELECT = (FIRST(alpha) - ε)FOLLOW[A] for (const string& s : first_alpha) { if (s != "ε") { select_set.insert(s); } } for (const string& s : follow[prod.left]) { select_set.insert(s); } } else { // SELECT = FIRST(alpha) for (const string& s : first_alpha) { if (s != "ε") { select_set.insert(s); } } } select_sets.push_back(select_set); } // 输出SELECT for (size_t i = 0; i < all_productions.size(); ++i) { const auto& prod = all_productions[i]; const auto& select = select_sets[i]; cout << "SELECT(" << prod.left << "->"; for (size_t j = 0; j < prod.right.size(); ++j) { cout << prod.right[j]; } cout << ")={ "; vector<string> sorted_select(select.begin(), select.end()); sort(sorted_select.begin(), sorted_select.end()); for (size_t j = 0; j < sorted_select.size(); ++j) { if (j > 0) cout << " "; cout << sorted_select[j]; } cout << " }" << endl; } // 判断是否为LL(1)文法 bool is_ll1 = true; map<string, vector<set<string>>> nt_select_map; for (size_t i = 0; i < all_productions.size(); ++i) { const auto& prod = all_productions[i]; nt_select_map[prod.left].push_back(select_sets[i]); } for (const auto& entry : nt_select_map) { const auto& select_sets_list = entry.second; for (size_t i = 0; i < select_sets_list.size(); ++i) { for (size_t j = i + 1; j < select_sets_list.size(); ++j) { set<string> intersection; set_intersection( select_sets_list[i].begin(), select_sets_list[i].end(), select_sets_list[j].begin(), select_sets_list[j].end(), inserter(intersection, intersection.begin()) ); if (!intersection.empty()) { is_ll1 = false; break; } } if (!is_ll1) break; } if (!is_ll1) break; } cout << (is_ll1 ? "Yes" : "No") << endl; return 0; } 现在,测试这个代码是否符合输入样例: 输入: 4 S->aA S->d A->bAS A->c 输出: SELECT(S->aA)={ a } SELECT(S->d)={ d } SELECT(A->bAS)={ b } SELECT(A->c)={ c } Yes 代码中的计算步骤: 首先,解析产生式: S→aA → right是['a','A'] S→d → ['d'] A→bAS → ['b','A','S'] A→c → ['c'] non_terminals包括S、A。 terminals包括a、d、b、c. 计算epsilonable: 对于S: 产生式S→aA:右部包含终结符a,所以不能推导出ε。 S→d:终结符d,不能推导出ε。所以S的epsilonable为false。 对于A: 产生式A→bAS:包含终结符b,不能推导出ε。 A→c:包含c,不能推导出ε。所以A的epsilonable为false。 所以,epsilonable都为false。 计算FIRST: 对于终结符a、d、b、c,它们的FIRST就是自身。 对于S: 产生式S→aA: 右部的FIRST是a. 产生式S→d: 右部的FIRST是d. 所以,S的FIRST是{a, d}. 对于A: 产生式A→bAS: 右部的FIRST是b. 产生式A→c: 右部的FIRST是c. 所以,A的FIRST是{b, c}. 计算FOLLOW: 开始符号是S,FOLLOW(S)初始化为{#}。 遍历所有产生式: 对于产生式S→aA: 处理每个符号: a是终结符,忽略。 A是非终结符: beta是后面的符号(空),所以beta的FIRST是空,可以推导出ε。所以将FOLLOW(S)加入到FOLLOW(A). 此时,FOLLOW(A) += FOLLOW(S) → {#} 对于产生式S→d:没有非终结符。 对于产生式A→bAS: 处理每个符号: b是终结符,忽略. A是非终结符: beta是S. FIRST(S)是{a, d}, 所以将{a, d}加入FOLLOW(A). beta S无法推导出ε(因为S的epsilonable为false),所以不需要加入 FOLLOW(A→S的FOLLOW?原产生式是A→bAS,所以当处理到A的位置i=1(符号A)时,beta是S,FIRST(S)是{a, d},所以 FOLLOW[A] += {a, d}。 此外,beta是S,无法推导出ε,所以不需要将FOLLOW(A)(当前为{#})加入 FOLLOW[S]。 处理符号S: S是非终结符,后面没有符号。所以将FOLLOW(A)加入 FOLLOW(S). 此时,FOLLOW(S) = {#} ∪ FOLLOW(A) → 在后续迭代中,如果 FOLLOW(A) 包含其他符号,会更新。 在第一次迭代中: 初始FOLLOW(S) = {#}, FOLLOW(A) = {#} (来自S→aA) {a, d} (来自A→bAS). 所以,FOLLOW(A)在第一次处理产生式A→bAS时,添加了ad。然后,在处理产生式A→bAS的符号A时,beta是S,将FIRST(S)中的a、d加入 FOLLOW[A]。因此,FOLLOW[A]现在是 {#, a, d}。 当处理产生式A→bAS的符号S时,beta为空,所以将FOLLOW(A)加入 FOLLOW(S)。此时,FOLLOW(S)变成 {#, a, d}。 然后,在后续迭代中,这些FOLLOW可能被进一步更新。 例如,当处理产生式S→aA时,A的FOLLOW被加入 FOLLOW[A](可能没有变化)。 这可能经过多次迭代,最终FOLLOW稳定下来。 对于产生式A→c:没有非终结符,不影响FOLLOW。 因此,最终的FOLLOW可能如下: FOLLOW(S) = {#, a, d} FOLLOW(A) = {#, a, d} 现在,计算每个产生式的SELECT: 对于S→aA: alpha是['a','A'],无法推导出ε。所以SELECT = FIRST(aA) = {a}. 对于S→d:SELECT = {d}. 对于A→bAS:alpha无法推导出ε,SELECT = {b}. 对于A→c:SELECT = {c}. 所有SELECT之间没有交,因此是LL(1)文法,输出Yes。 因此,代码的输出应与样例一致。 现在,测试代码是否存在问题: 可能的错误点: 1. 在解析产生式右部时,是否正确处理了ε? 是的,当右部字符串是“ε”时,符号列表为空。 2. FOLLOW的初始化是否正确? 开始符号的FOLLOW初始化为{#}。 3. 在计算FOLLOW时,是否正确地处理了产生式中的各个符号? 例如,在产生式A→bAS中,处理A的位置时,beta是S,所以将FIRST(S)的{a, d}加入FOLLOW[A],并且由于S不能推导出ε(epsilonable[S]为false),所以不将FOLLOW[A]加入 FOLLOW[S]。 但在代码中的处理,是否在beta可以推导出ε时才会添加FOLLOW[A]? 是的,代码中的beta_derives_epsilon变量用于判断是否beta可以推导出ε。在这种情况下,beta是S,无法推导出ε,所以不会将FOLLOW[A]加入 FOLLOW[S]。 但是,当处理产生式A→bAS中的最后一个符号S时,后面没有符号,所以 beta为空。此时,beta可以推导出ε(空串),所以将FOLLOW[A]加入 FOLLOW[S]。 因此,在第一次迭代中,FOLLOW[A]包含#,所以在处理产生式A→bAS的最后一个符号S时,将FOLLOW[A](#)加入 FOLLOW[S]。 此时,FOLLOW[S]变为 {#, #} → 也就是 {#},没有变化? 或者,当产生式A→bAS中的S后面没有符号,那么根据规则,对于产生式A→αBβ,其中β是空,那么将 FOLLOW[A] 加入 FOLLOW[B]。在这个例子中,B是S,所以 FOLLOW[S] 需要加入 FOLLOW[A]吗?或者,当处理产生式A→bAS中的S时,S是最后一个符号,此时 beta是空,所以 将 FOLLOW[A] 加入 FOLLOW[S]。 因此,在产生式A→bAS中,当处理符号S时,beta是空,所以 beta可以推导出ε,所以将 FOLLOW[A] 加入 FOLLOW[S]. 假设初始 FOLLOW[A] 是 {#},那么 FOLLOW[S] 初始为 {#},在第一次处理时, FOLLOW[S] 加入 FOLLOW[A] → FOLLOW[S] 变为 {#} ∪ {#} → 没变化? 可能需要更多的迭代才能正确计算 FOLLOW。 例如,可能最终 FOLLOW[A] FOLLOW[S] 都会包含更多符号。 这可能导致代码中的 FOLLOW计算不够准确,从而影响 SELECT的结果。 例如,在样例输入中,如果 FOLLOW[A] 包含a、d,那么产生式A→bAS的SELECT可能包含这些符号,但根据问题描述的样例输出,SELECT仅为 {b},说明在计算时该产生式的α不能推导出ε,因此SELECT仅为 FIRST(α) = {b}。 因此,在代码中,计算正确。 综上,代码应该可以正确处理样例输入,并输出正确结果。</think>以下是使用C++实现求取SELECT并判断LL(1)文法的代码: ```cpp #include <iostream> #include <vector> #include <map> #include <set> #include <algorithm> #include <sstream> using namespace std; struct Production { string left; vector<string> right; int index; }; vector<Production> all_productions; map<string, vector<Production>> nt_productions; set<string> non_terminals; set<string> terminals; map<string, set<string>> first; map<string, set<string>> follow; map<string, bool> epsilonable; void compute_epsilonable() { bool changed; do { changed = false; for (const auto& nt : non_terminals) { for (const auto& prod : nt_productions[nt]) { bool can_derive = true; for (const string& sym : prod.right) { if (terminals.count(sym) || !epsilonable[sym]) { can_derive = false; break; } } if (can_derive && !epsilonable[nt]) { epsilonable[nt] = true; changed = true; } } } } while (changed); } void compute_first() { for (const auto& t : terminals) first[t].insert(t); bool changed; do { changed = false; for (const auto& nt : non_terminals) { for (const auto& prod : nt_productions[nt]) { set<string> first_alpha; bool has_epsilon = true; for (const string& sym : prod.right) { if (terminals.count(sym)) { first_alpha.insert(sym); has_epsilon = false; break; } else { for (const string& s : first[sym]) { if (s != "ε") first_alpha.insert(s); } if (!epsilonable[sym]) { has_epsilon = false; break; } } } if (has_epsilon) first_alpha.insert("ε"); size_t old_size = first[nt].size(); for (const string& s : first_alpha) first[nt].insert(s); if (first[nt].size() > old_size) changed = true; } } } while (changed); } set<string> compute_first_for_sequence(const vector<string>& alpha) { set<string> result; bool has_epsilon = true; for (const string& sym : alpha) { if (terminals.count(sym)) { result.insert(sym); has_epsilon = false; break; } else { for (const string& s : first[sym]) { if (s != "ε") result.insert(s); } if (!epsilonable[sym]) { has_epsilon = false; break; } } } if (has_epsilon) result.insert("ε"); return result; } void compute_follow(const string& start_symbol) { for (auto& nt : non_terminals) follow[nt].clear(); follow[start_symbol].insert("#"); bool changed; do { changed = false; for (const auto& prod : all_productions) { const string& A = prod.left; const auto& alpha = prod.right; for (size_t i = 0; i < alpha.size(); ++i) { const string& B = alpha[i]; if (!non_terminals.count(B)) continue; vector<string> beta(alpha.begin()+i+1, alpha.end()); set<string> first_beta = compute_first_for_sequence(beta); size_t old_size = follow[B].size(); for (const string& s : first_beta) { if (s != "ε") follow[B].insert(s); } if (follow[B].size() > old_size) changed = true; bool beta_epsilon = all_of(beta.begin(), beta.end(), [](const string& s) { return non_terminals.count(s) && epsilonable[s]; }); if (beta_epsilon || beta.empty()) { old_size = follow[B].size(); for (const string& s : follow[A]) follow[B].insert(s); if (follow[B].size() > old_size) changed = true; } } } } while (changed); } bool can_derive_epsilon(const vector<string>& alpha) { for (const string& sym : alpha) { if (terminals.count(sym) || !epsilonable[sym]) return false; } return true; } int main() { int n; cin >> n; cin.ignore(); string start_symbol; for (int i = 0; i < n; ++i) { string line; getline(cin, line); size_t pos = line.find("->"); string left = line.substr(0, pos); string right_str = line.substr(pos+2); left.erase(remove(left.begin(), left.end(), ' '), left.end()); non_terminals.insert(left); vector<string> right; if (right_str != "ε") { for (char c : right_str) { string sym(1, c); right.push_back(sym); if (isupper(c)) non_terminals.insert(sym); else terminals.insert(sym); } } all_productions.push_back({left, right, i}); nt_productions[left].push_back(all_productions.back()); } start_symbol = all_productions[0].left; for (const auto& nt : non_terminals) epsilonable[nt] = false; compute_epsilonable(); compute_first(); compute_follow(start_symbol); vector<set<string>> select_sets; for (const auto& prod : all_productions) { set<string> select; bool alpha_epsilon = can_derive_epsilon(prod.right); set<string> first_alpha = compute_first_for_sequence(prod.right); if (alpha_epsilon) { for (const string& s : first_alpha) if (s != "ε") select.insert(s); for (const string& s : follow[prod.left]) select.insert(s); } else { for (const string& s : first_alpha) if (s != "ε") select.insert(s); } select_sets.push_back(select); } for (size_t i = 0; i < all_productions.size(); ++i) { const auto& prod = all_productions[i]; cout << "SELECT(" << prod.left << "->"; for (const auto& s : prod.right) cout << s; cout << ")={ "; vector<string> sorted(select_sets[i].begin(), select_sets[i].end()); sort(sorted.begin(), sorted.end()); for (size_t j = 0; j < sorted.size(); ++j) { if (j > 0) cout << " "; cout << sorted[j]; } cout << " }" << endl; } bool is_ll1 = true; map<string, vector<set<string>>> nt_select; for (size_t i = 0; i < all_productions.size(); ++i) { nt_select[all_productions[i].left].push_back(select_sets[i]); } for (const auto& entry : nt_select) { const auto& sets = entry.second; for (size_t i = 0; i < sets.size(); ++i) { for (size_t j = i+1; j < sets.size(); ++j) { vector<string> inter; set_intersection(sets[i].begin(), sets[i].end(), sets[j].begin(), sets[j].end(), back_inserter(inter)); if (!inter.empty()) is_ll1 = false; } } } cout << (is_ll1 ? "Yes" : "No") << endl; return 0; } ``` ### 代码说明 1. **输入处理**:解析产生式,分离左部右部符号,处理空产生式(ε) 2. **ε推导判断**:通过迭代计算每个非终结符能否推导出ε 3. **FIRST计算**:包含终结符的直接取值非终结符的递归推导 4. **FOLLOW计算**:基于产生式右部符号的位置关系进行迭代推导 5. **SELECT计算**:根据FIRSTFOLLOW的组合逻辑生成 6. **LL(1)判断**:检查同一非终结符的不同产生式SELECT是否相交 该实现严格遵循LL(1)文法的判定规则,能够正确处理空产生式、复杂符号顺序等文法特性。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值