LL(1)算法
编译原理的语法分析中一个入门的算法就是LL(1)算法了,这里做一个总结。首先比较重要的是FIRST集和FOLLOW集合的生成。
先上个例子吧:

首先说一下FIRST集的生成,这个就要看产生式右部对应的首字母的“终结符”的个数的表现了,例如:A-> +TA|-TA|k 所以 A的FIRST集为+-k ; 同理B->*FB|/FB|k,所以B的FIRST集是*/k; D的FIRST集是xyz; 接着我们再分析F,对F分析可以得FIRST集是 ( 和 D的FIRST的并集,即(xyz,同理可以得到T和E的FIRST都是(xyz。
我们接着分析FOLLOW集。首先要说一点,可以观察按照FOLLOW集按照“非终结符顺序”从上到下是递增的(这是什么规律,但是几乎所有的LL1(1)分析表都有这个现象),从上至下依次分析,对于E,观察所有的产生式的右部:有F-> (E)|D 这个式,则我们可以得知E的FOLLOW集为#). 接下来我们分析A的FOLLOW集,观察所有的产生式的右部,发现没有跟着A的终结符,哎,没办法,照抄上面的吧:即A得FOLLOW集和E的一样,都是#)。 然后我们分析T,发现T后面总是跟着A,则A的FIRST集应该包含于T的FOLLOW集中(当然,有空k的要去掉k), 再加上上面“遗传”下来的,所以T的FOLLOW集是+-#). 同理分析下去就会得到全部的非终结符的FOLLOW集。
接着是如何运用FIRST和FOLLOW集生成LL(1)分析表。首先对所有的非终结符的FIRST进行填充,并留下这些FIRST的空k符号。这一遍刷完之后,再特别处理空k,对有空k的非终结符,将它的FOLLOW集中的所有的元素,都写成空k的式输入到LL(1)分析表中去。这样就大功告成了。
不妨再贴一个例子:

题目1:
输入开始符号,非终结符,终结符,产生式,LL(1)分析表
输出LL(1)分析表
G[E]:E →E+T | E-T | T
T →T*F | T/F | F
F →(E) | D
D →x | y | z
消除左递归G1[E]:
E →TA
A →+TA | -TA | e
T →FB
B →*FB | /FB | e
F →(E) | D
D →x | y | z
输入开始符号;
非终结符个数,非终结符,空格符分隔;
终结符个数,终结符,空格符分隔;
产生式的个数,各产生式的序号,产生式的左边和右边符号,空格符分隔;
LL(1)分析表中的产生式个数,序号,行符号,列符号,产生式编号,空格符分隔;
第一行:空,安终结符循序输出终结符,结束符‘#’,每个符号占5格;
其余行:非终结符符号,各对应终结符的产生式的右边,每个符号占5格;
E
6 E A T B F D
9 + - * / ( ) x y z
13
1 E TA
2 A +TA
3 A -TA
4 A k
5 T FB
6 B *FB
7 B /FB
8 B k
9 F (E)
10 F D
11 D x
12 D y
13 D z
25
1 E ( 1
2 E x 1
3 E y 1
4 E z 1
5 A + 2
6 A - 3
7 A ) 4
8 A # 4
9 T ( 5
10 T x 5
11 T y 5
12 T z 5
13 B + 8
14 B - 8
15 B * 6
16 B / 7
17 B ) 8
18 B # 8
19 F ( 9
20 F x 10
21 F y 10
22 F z 10
23 D x 11
24 D y 12
25 D z 13
+ - * / ( ) x y z #
E TA TA TA TA
A +TA -TA k k
T FB FB FB FB
B k k *FB /FB k k
F (E) D D D
D x y z AC代码:
#include <iostream>
using namespace std; string S=""; //开始符号
struct { int number;string sign[]; string res_LL[][]; } not_endsign={}; //非终结符
struct { int number;string sign[]; } end_sign={}; //终结符
struct { int number;int order[]; string left[],right[]; } production={}; //产生式
struct { int number;int order[]; string rank[],col[];int production_num[]; } LL={}; //LL(1)分析表 void input();
void print(string a); int main(){
input();
end_sign.sign[end_sign.number] = "#";
end_sign.number++; //刷整个的分析表,将分析表中的数据填入到not_endsign中去
for(int i=;i<LL.number;i++){
//得到LL一条数据的“行”对应的“非终结符”
int j;
for(j=;j<not_endsign.number&& not_endsign.sign[j]!=LL.rank[i];j++ ); //得到LL一条数据的“列”对应的“终结符”
int z;
for(z=;z<end_sign.number&&end_sign.sign[z]!=LL.col[i];z++ ); //得到LL一条数据的要赋予的值
not_endsign.res_LL[j][z] = production.right[LL.production_num[i]- ];
} //单独处理“#” cout<<" ";
for(int i=;i<end_sign.number;i++){
cout<<" "<<end_sign.sign[i];
}
cout<<endl; for(int i=;i<not_endsign.number;i++){
print(not_endsign.sign[i]);
cout<<not_endsign.sign[i];
for(int j=;j<end_sign.number ;j++){
print(not_endsign.res_LL[i][j] );
cout<<not_endsign.res_LL[i][j];
}
cout<<endl;
} return ;
} void print(string a){
for(int i=;i<-a.length();i++){
cout<<" ";
}
return ;
}
void input(){
cin>>S;
cin>>not_endsign.number;
for(int i=;i<not_endsign.number;i++){
cin>>not_endsign.sign[i];
} cin>>end_sign.number;
for(int i=;i<end_sign.number;i++){
cin>>end_sign.sign[i];
} cin>>production.number;
for(int i=;i<production.number;i++){
cin>>production.order[i]>>production.left[i]>>production.right[i];
} cin>>LL.number;
for(int i=;i<LL.number;i++){
cin>>LL.order[i]>>LL.rank[i]>>LL.col[i]>>LL.production_num[i];
}
return ;
}
题目2:
输入开始符号,非终结符,终结符,产生式,LL(1)分析表
输出LL(1)分析表
输入开始符号;
非终结符个数,非终结符,空格符分隔;
终结符个数,终结符,空格符分隔;
产生式的个数,各产生式的序号,产生式的左边和右边符号,空格符分隔;
LL(1)分析表中的产生式个数,序号,行符号,列符号,产生式编号,空格符分隔;
输入一个算术式符号串,用#结束
输出推导过程,每一步一行,中间“ & ”前是已经识别的子串,后是栈中信息。
E
6 E A T B F D
9 + - * / ( ) x y z
13
1 E TA
2 A +TA
3 A -TA
4 A k
5 T FB
6 B *FB
7 B /FB
8 B k
9 F (E)
10 F D
11 D x
12 D y
13 D z
25
1 E ( 1
2 E x 1
3 E y 1
4 E z 1
5 A + 2
6 A - 3
7 A ) 4
8 A # 4
9 T ( 5
10 T x 5
11 T y 5
12 T z 5
13 B + 8
14 B - 8
15 B * 6
16 B / 7
17 B ) 8
18 B # 8
19 F ( 9
20 F x 10
21 F y 10
22 F z 10
23 D x 11
24 D y 12
25 D z 13
(x+(y-x*z)*(y+x*z))+x/z#
# & E#
# & TA#
# & FBA#
# & (E)BA#
#( & E)BA#
#( & TA)BA#
#( & FBA)BA#
#( & DBA)BA#
#( & xBA)BA#
#(x & BA)BA#
#(x & A)BA#
#(x & +TA)BA#
#(x+ & TA)BA#
#(x+ & FBA)BA#
#(x+ & (E)BA)BA#
#(x+( & E)BA)BA#
#(x+( & TA)BA)BA#
#(x+( & FBA)BA)BA#
#(x+( & DBA)BA)BA#
#(x+( & yBA)BA)BA#
#(x+(y & BA)BA)BA#
#(x+(y & A)BA)BA#
#(x+(y & -TA)BA)BA#
#(x+(y- & TA)BA)BA#
#(x+(y- & FBA)BA)BA#
#(x+(y- & DBA)BA)BA#
#(x+(y- & xBA)BA)BA#
#(x+(y-x & BA)BA)BA#
#(x+(y-x & *FBA)BA)BA#
#(x+(y-x* & FBA)BA)BA#
#(x+(y-x* & DBA)BA)BA#
#(x+(y-x* & zBA)BA)BA#
#(x+(y-x*z & BA)BA)BA#
#(x+(y-x*z & A)BA)BA#
#(x+(y-x*z & )BA)BA#
#(x+(y-x*z) & BA)BA#
#(x+(y-x*z) & *FBA)BA#
#(x+(y-x*z)* & FBA)BA#
#(x+(y-x*z)* & (E)BA)BA#
#(x+(y-x*z)*( & E)BA)BA#
#(x+(y-x*z)*( & TA)BA)BA#
#(x+(y-x*z)*( & FBA)BA)BA#
#(x+(y-x*z)*( & DBA)BA)BA#
#(x+(y-x*z)*( & yBA)BA)BA#
#(x+(y-x*z)*(y & BA)BA)BA#
#(x+(y-x*z)*(y & A)BA)BA#
#(x+(y-x*z)*(y & +TA)BA)BA#
#(x+(y-x*z)*(y+ & TA)BA)BA#
#(x+(y-x*z)*(y+ & FBA)BA)BA#
#(x+(y-x*z)*(y+ & DBA)BA)BA#
#(x+(y-x*z)*(y+ & xBA)BA)BA#
#(x+(y-x*z)*(y+x & BA)BA)BA#
#(x+(y-x*z)*(y+x & *FBA)BA)BA#
#(x+(y-x*z)*(y+x* & FBA)BA)BA#
#(x+(y-x*z)*(y+x* & DBA)BA)BA#
#(x+(y-x*z)*(y+x* & zBA)BA)BA#
#(x+(y-x*z)*(y+x*z & BA)BA)BA#
#(x+(y-x*z)*(y+x*z & A)BA)BA#
#(x+(y-x*z)*(y+x*z & )BA)BA#
#(x+(y-x*z)*(y+x*z) & BA)BA#
#(x+(y-x*z)*(y+x*z) & A)BA#
#(x+(y-x*z)*(y+x*z) & )BA#
#(x+(y-x*z)*(y+x*z)) & BA#
#(x+(y-x*z)*(y+x*z)) & A#
#(x+(y-x*z)*(y+x*z)) & +TA#
#(x+(y-x*z)*(y+x*z))+ & TA#
#(x+(y-x*z)*(y+x*z))+ & FBA#
#(x+(y-x*z)*(y+x*z))+ & DBA#
#(x+(y-x*z)*(y+x*z))+ & xBA#
#(x+(y-x*z)*(y+x*z))+x & BA#
#(x+(y-x*z)*(y+x*z))+x & /FBA#
#(x+(y-x*z)*(y+x*z))+x/ & FBA#
#(x+(y-x*z)*(y+x*z))+x/ & DBA#
#(x+(y-x*z)*(y+x*z))+x/ & zBA#
#(x+(y-x*z)*(y+x*z))+x/z & BA#
#(x+(y-x*z)*(y+x*z))+x/z & A#
#(x+(y-x*z)*(y+x*z))+x/z & # AC代码:
#include <iostream>
#include <stack>
using namespace std; string S=""; //开始符号
struct { int number;string sign[]; string res_LL[][]; } not_endsign={}; //非终结符
struct { int number;string sign[]; } end_sign={}; //终结符
struct { int number;int order[]; string left[],right[]; } production={}; //产生式
struct { int number;int order[]; string rank[],col[];int production_num[]; } LL={}; //LL(1)分析表
string test; void input();
void print(string left,stack<string > right); int main(){
input(); //定义输出结果
string left;
stack<string > right; right.push(S) ;
print(left,right); while(!right.empty()){
string top = right.top();
string firstletter = test.substr(,);
if(top==firstletter){
left += top;
test = test.substr(,test.length()- );
right.pop();
print(left,right); continue;
}
else {
//替换掉 top
for(int i=;i<LL.number;i++){
if(LL.rank[i]==top &&LL.col[i]==firstletter ){
right.pop();
string temp = production.right[LL.production_num[i]- ];
if(temp=="k") continue;
while(temp.length()!=){
string temp0 = temp.substr( temp.length()-,);
right.push(temp0);
temp = temp.substr(,temp.length()-);
} }
} } print(left,right);
} return ;
} void print(string left,stack<string > right){
cout<<"#"<<left<<" & ";
string temp="";
while(!right.empty()){
cout<<right.top();
temp+=right.top();
right.pop();
}
cout<<"#"<<endl; while(temp.length()!=){
string temp0 = temp.substr( temp.length()-,);
right.push(temp0);
temp = temp.substr(,temp.length()-);
} return ;
} void input(){
cin>>S;
cin>>not_endsign.number;
for(int i=;i<not_endsign.number;i++){
cin>>not_endsign.sign[i];
} cin>>end_sign.number;
for(int i=;i<end_sign.number;i++){
cin>>end_sign.sign[i];
} cin>>production.number;
for(int i=;i<production.number;i++){
cin>>production.order[i]>>production.left[i]>>production.right[i];
} cin>>LL.number;
for(int i=;i<LL.number;i++){
cin>>LL.order[i]>>LL.rank[i]>>LL.col[i]>>LL.production_num[i];
} cin>>test;
return ;
}
LL(1)算法的更多相关文章
- B树——算法导论(25)
B树 1. 简介 在之前我们学习了红黑树,今天再学习一种树--B树.它与红黑树有许多类似的地方,比如都是平衡搜索树,但它们在功能和结构上却有较大的差别. 从功能上看,B树是为磁盘或其他存储设备设计的, ...
- 分布式系列文章——Paxos算法原理与推导
Paxos算法在分布式领域具有非常重要的地位.但是Paxos算法有两个比较明显的缺点:1.难以理解 2.工程实现更难. 网上有很多讲解Paxos算法的文章,但是质量参差不齐.看了很多关于Paxos的资 ...
- 【Machine Learning】KNN算法虹膜图片识别
K-近邻算法虹膜图片识别实战 作者:白宁超 2017年1月3日18:26:33 摘要:随着机器学习和深度学习的热潮,各种图书层出不穷.然而多数是基础理论知识介绍,缺乏实现的深入理解.本系列文章是作者结 ...
- 红黑树——算法导论(15)
1. 什么是红黑树 (1) 简介 上一篇我们介绍了基本动态集合操作时间复杂度均为O(h)的二叉搜索树.但遗憾的是,只有当二叉搜索树高度较低时,这些集合操作才会较快:即当树的高度较高(甚至一种极 ...
- 散列表(hash table)——算法导论(13)
1. 引言 许多应用都需要动态集合结构,它至少需要支持Insert,search和delete字典操作.散列表(hash table)是实现字典操作的一种有效的数据结构. 2. 直接寻址表 在介绍散列 ...
- 虚拟dom与diff算法 分析
好文集合: 深入浅出React(四):虚拟DOM Diff算法解析 全面理解虚拟DOM,实现虚拟DOM
- 简单有效的kmp算法
以前看过kmp算法,当时接触后总感觉好深奥啊,抱着数据结构的数啃了一中午,最终才大致看懂,后来提起kmp也只剩下“奥,它是做模式匹配的”这点干货.最近有空,翻出来算法导论看看,原来就是这么简单(先不说 ...
- 神经网络、logistic回归等分类算法简单实现
最近在github上看到一个很有趣的项目,通过文本训练可以让计算机写出特定风格的文章,有人就专门写了一个小项目生成汪峰风格的歌词.看完后有一些自己的小想法,也想做一个玩儿一玩儿.用到的原理是深度学习里 ...
- 46张PPT讲述JVM体系结构、GC算法和调优
本PPT从JVM体系结构概述.GC算法.Hotspot内存管理.Hotspot垃圾回收器.调优和监控工具六大方面进行讲述.(内嵌iframe,建议使用电脑浏览) 好东西当然要分享,PPT已上传可供下载 ...
- 【C#代码实战】群蚁算法理论与实践全攻略——旅行商等路径优化问题的新方法
若干年前读研的时候,学院有一个教授,专门做群蚁算法的,很厉害,偶尔了解了一点点.感觉也是生物智能的一个体现,和遗传算法.神经网络有异曲同工之妙.只不过当时没有实际需求学习,所以没去研究.最近有一个这样 ...
随机推荐
- 50个新的汉化Demo!纯前端 Wijmo 放大招
Wijmo 是为企业应用程序开发而推出的一系列包含 HTML5 和 JavaScript 的开发控件,包含 Wijmo 5 . Wijmo 3 及面向企业级应用的控件金融图表.FlexSheet 和 ...
- 编程模式之模板方法模式(Template Method)
模板方法模式由两个角色组成:父类角色,子类角色. 父类角色:提供模板. 子类角色:为父类模板提供实现. 类图: JAVA代码: AbstractClass.java package com.templ ...
- Windows 10 下mysql 安装后无法启动问题
安装过程: 1. 官网下载5.15.7, http://dev.mysql.com/downloads/, 选择开源社区版:MySQL Community Server (GPL) 2. 我解压后放在 ...
- file_get_contents()/file_put_contents()
PHP file_get_contents() 函数 定义和用法 file_get_contents() 把整个文件读入一个字符串中. 该函数是用于把文件的内容读入到一个字符串中的首选方法.如果服务器 ...
- ABAP程序互调用:SUBMIT、CALL TRANSACTION、LEAVE TO TRANSACTION
声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...
- iOS UIRefreshControl-刷新tableView
override func viewDidLoad() { super.viewDidLoad() refreshControl = UIRefreshControl.in ...
- AutoLayout(自动布局)
1. iOS两种自适应布局方式:(修正说明:) -AutoLayout(自动布局) + SizeClasses(尺寸类别) -Autoresizing (自动调整尺寸/弹簧式调整尺寸) 前者 Auto ...
- OC 协议
OC 协议 概念:定义了一个接口,其他类负责来实现这些接口.如果你的类实现了一个协议的方法时,则说该类遵循此协议. 非正式协议:非正式协议虽名为协议,但实际上是挂于NSObject上的未实现分类(Un ...
- Linux sendmail发送邮件失败诊断案例(一)
在新服务器上测试sendmail发送邮件时,发现邮件发送不成功,检查日志文件发现如下错误(Notice:由于涉及公司服务器,邮箱等,故下面hostname.邮箱地址等信息使用xxx代替) tail - ...
- Linux system log avahi-daemon[3733]: Invalid query packet
在检查Linux的日志文件时,发现大量 avahi-daemon[3733]: Invalid query packet错误(不同服务器对应的数字有所不同) Aug 3 07:00:01 hostn ...