THOMPSON 算法的实现

  • 一、实验目的
  • 二、实验要求、内容
  • 三、实验设备
  • 四、实验原理(或程序框图)及步骤
  • 五、程序源代码
  • 六、实验数据、结果分析
  • 七、存在的问题与体会

一、实验目的

掌握将正规表达式转换为NFA的方法和过程。

二、实验要求、内容

1.输入一个正规表达式,输出一个接受同一语言的NFA;
2.采用任意语言,实现该算法;
3编制测试程序;
4.调试程序。

三、实验设备

计算机、Windows 7操作系统、Eclipse 2019-12 程序集成环境、JDK 12。

四、实验原理(或程序框图)及步骤

对于给定的以中缀表达式记录的正规表达式,需要将其转换为后缀表达式以便于程序处理,依次自左至右扫描整个中缀表达式,建立一个表用于存放转换后的后缀表达式,程序实现时为方便插入操作,可以使用链表。此外还需建立一个栈用于存储扫描时遇到的运算符。对于碰到的运算符,将其直接存入后缀表达式表。

在处理正规表达式时,对于所有的运算符,如果是左括号则压入栈操作符栈,如是右括号则依次在操作符栈中弹栈直到遇到左括号,将所有弹栈出的运算符依次加入后缀表达式表中,对于操作符,如果当前操作符的优先级低于操作服栈的栈顶元素,则弹栈并将栈顶元素加入结果列表,随后将当前操作符加入操作符栈。如果当前操作符的优先级高于栈顶元素,则压栈。扫描完成后如果操作符栈还有其他元素,则将所有元素依次弹栈后加入结果数组。

处理后得到的NFA可以用状态转换图和状态转换表描述,所以可以直接使用实验一中的State类。为方便构建State类,在扫描中缀表达式的时候,还需要构建字母表。即扫描中缀表达式前还需建立一个链表,如果扫描到的字符不在字母表中,则将其加入字母表。由于State类将ε也作为一个字符处理,所以如果给出的正规表达式中不包含ε,需要将其加入字母表中。

生成后缀表达式的流程图如图4-1所示,首先读入待扫描的正规表达式,然后判断是高优先级操作符还是操作数,存入对应的栈或链表中。读取完成后将操作符栈中的所有元素存入操作数链表。在得到后缀表达式后,即可以依据Thompson算法计算NFA。其中,由于Thompson算法中对ε和普通字符的处理相同,所以上述将ε视作字符的处理方式可行。分析Thompson算法可知,其所有运算符在处理时并非直接操作对应字符,而是根据对字符(包含ε)的规则将对应字符转换为一系列状态组后在进行操作。亦即所有运算符的操作对象都是基本字符转换得到的状态组,所以在处理得到后缀表达式后,并不能直接对后缀表达式中的字符进行计算,还需将其转换为相关的状态组后,再进行相关操作。所有状态组中的基本状态可复用实验一中的State类进行描述,因而可考虑设计基于State 的设计组类或数组对状态组加以描述。但相应方法在实现和操作过程中过于繁琐,故不采用上述方法,而依下述方法设置特殊的状态类用以区分不同的状态组。

图4-1 生成后缀表达式的流程图

分析Thompson算法的处理规则可知,所有运算在处理时只涉及状态组头尾的两个状态,处理后的结果是包含新的开始和结束状态的状态组,所以可以将后缀表达式中的所有字符转换为状态组,并将其按字符顺序存放在一个链表中,每个状态组的开始状态为start、结束状态为end,可以此区分不同字符串转换得到的状态组。则对于每一个字符串x转换得到的状态组X,所有X中均包含开始状态start、结束状态end和其他状态,如图4-1所示,其中白色部分是普通的状态组,灰色部分是用于标识不同状态组的开始和结束状态组。

图4-2 原始状态组示意图

在扫描原始后缀表达式时,如果遇到字符,则将一个状态组加入结果链表中。处理所有状态组时的顺序依据原始后缀表达式的顺序,生成一个空链表用于存放最终得到的结果,其链表头尾不可改变的START状态,用于标记最终得到的NFA的开始状态,其示意图如图4-2所示。

图4-3 包含两个状态组的结果链表示意图

对于生成和处理正确的后缀表达式,在遇到闭包运算时链表中应该仅能找到一组状态组,在遇到|运算时应仅有两组状态组。对于闭包运算和|运算,Thompson算法中的转换规则均在状态组的头尾加上新的开始状态start’和结束状态end’形成新的状态组X’,且在添加和处理时只涉及start、end、start’、end’几个状态。对此两种运算,均需要生成新的开始start’和结束状态end’分别加入链表的前端(START后一个位置)和尾端,找到原有的start和end修改其所记录的ε边信息,并改变其名称使其变成普通状态,即可完成操作。将字符状态组加入结果链表的示意图如图4-2所示,链表头为不可改变的最终开始状态START,其后的状态组以start和end区分。闭包和|运算的示意图如图4-3所示,加入新的开始和结束状态start’和end’后,找到待处理的状态组的头尾,将其修改并变为普通状态。

图4-4 闭包和|运算处理状态组的示意图

同样,在遇到连接运算时,结果链表中应仅包含两个状态组。对于连接运算,处理时只涉及其要连接的两个基本状态组X、Y中的开始和结束状态。修改第一个状态组的end状态和第二个状态组的start状态中记录的ε边信息并改变其名称,使其变成普通状态,即可完成操作。连接运算示意图如图4-4所示,不加入新的开始和结束状态,处理第一个状态组的end状态和第二个状态组的start状态,则第一个状态组的start状态和第二个状态组的end状态作为新的状态组的开始和结束状态。

图4-5 连接运算的示意图

上述相关处理的流程图如图4-6所示。

图4-6 后缀表达式计算流程图

五、程序源代码

生成后缀表达式的代码如下所示,其中,queue用于存放计算后得到的后缀表达式,stack用于存放过程中的操作符,list用于生成字母表。

     Queue<Character> queue = new LinkedList<>();Stack<Character> stack = new Stack<>();LinkedList<String> list = new LinkedList<>();for (int i = 0; i < normal.length(); i++) {char m = normal.charAt(i);if (m != '(' && m != ')' && m != '*' && m != '|' && m != '.' && m != '+') {if (!list.contains("" + m))list.add("" + normal.charAt(i));queue.add(m);} else if (m == ')') {char n = stack.pop();while (n != '(') {queue.add(n);n = stack.peek();}stack.pop();} else if (m == '*') {queue.add(m);} else {stack.push(m);}}while (!stack.empty()) {if (stack.peek() != '(') {queue.add(stack.pop());} else {stack.pop();}}

计算后缀表达式的代码如下所示,其中,result是存放最终结果的链表,name是生成新状态的名字,随着状态的生成不断增加,st和ed是生成的新状态,遍历后缀表达式时用target做扫描其中的每一个值,在状态类中用字母到状态集合的映射反映边关系,ε用$代替,在设置变关系是,只需把对应的状态名称添加到对应状态的映射关系中即可。

     char name = 'a';LinkedList<State> result = new LinkedList<>();result.addFirst(new State("START"));while (!queue.isEmpty()) {char target = queue.poll();if (target == '|' || target == '*') {State st = new State("" + (name++));State ed = new State("" + (name++));result.add(1, st);result.addLast(ed);LinkedList<String> start = new LinkedList<>();if (target == '|') {for (int i = 0; i < result.size(); i++) {if (result.get(i).name().equals("start")) {start.add(result.get(i + 1).name());}if (result.get(i).name().equals("end")) {State temp = result.get(i - 1);@SuppressWarnings("unchecked")LinkedList<String> t = (LinkedList<String>) temp.get("$");if (t == null)t = new LinkedList<String>();t.add(ed.name());temp.put("$", t);result.set(i - 1, temp);}}} else {int m = -1;for (int i = 0; i < result.size(); i++) {if (result.get(i).name().equals("start")) {start.add(result.get(i + 1).name());m = i + 1;}if (result.get(i).name().equals("end")) {State temp = result.get(i - 1);@SuppressWarnings("unchecked")LinkedList<String> t = (LinkedList<String>) temp.get("$");if (t == null)t = new LinkedList<String>();t.add(ed.name());if (m != -1)t.add(result.get(m).name());temp.put("$", t);result.set(m-1, temp);}}start.add(ed.name());}st.put("$", start);result.set(1, st);for (int i = 0; i < result.size(); i++) {if (result.get(i).name().equals("start") || result.get(i).name().equals("end")) {result.remove(i);i--;}}result.add(1, new State("start"));result.addLast(new State("end"));} else if (target == '.') {int i = 0;for (; i < result.size(); i++) {if (result.get(i).name().equals("end")) {break;}}int k = i;for (; k< result.size(); k++) {if (result.get(k).name().equals("start")) {break;}}State endData = result.get(i - 1);State end = result.get(i);State start = result.get(k);@SuppressWarnings("unchecked")LinkedList<String> t = (LinkedList<String>) endData.get("$");if (t == null)t = new LinkedList<String>();start.setName("" + (name++));t.add(start.name());endData.put("$", t);result.set(i - 1, endData);result.set(k, start);result.remove(end);} else {State temp = stateQueue.peek();while (!temp.name().contains("end")) {stateQueue.poll();result.add(temp);temp = stateQueue.peek();}result.add(temp);stateQueue.poll();}}return result;}

六、实验数据、结果分析

实验中以正规表达式“(a|b)*.a.b.b”作为输入,其后缀表达式和处理所有字符后得到的状态组如图6-1所示,结果正确。

图6-1 后缀表达式和初始状态组 在图6-1中,编号①为得到的后缀表达式,其中逗号为分隔符,②普通状态的状态名,③为该状态经某字母可达某状态集合的映射表,④⑤分别为开始状态标志和结束状态标志,其映射表为空,仅标记其前后的状态为当前状态集合的开始或结束状态。 计算后缀表达式后得到的最终结果如图6-2所示,其中编号编号①为NFA的开始状态,②⑤分别为最后一次计算后得到的开始状态合计数标记,③为处理过程中新生成的状态的状态名,④是其处理后添加的状态边,在处理过程中只有最开始处理状态时添加的边包含字母表中的字符,在后续的计算中均只添加ε边,所以在新生成的状态中只包含经ε可达到状态集合映射。 图6-2 程序处理结果

七、存在的问题与体会

1.程序中原始的正规表达式为指定的正规表达式,没有针对更多正规表达式进行测试。
2.使用Java语言编写程序,代码的执行效率较低。且使用链表、队列、堆栈等结构占用了大量额外空间,代码的时空性能可以优化。
通过实验加深了对于Thompson算法的理解,特别是对于正规表达式到NFA的转换过程中,各种计算规则的应用和处理,在熟悉了相关规则后对规则进行了一定程度的变形以方便实现。

【编译原理】【实验】THOMPSON 算法的实现相关推荐

  1. 编译实验 lr c语言代码,编译原理-实验5-LR(1)分析法

    <编译原理-实验5-LR(1)分析法>由会员分享,可在线阅读,更多相关<编译原理-实验5-LR(1)分析法(6页珍藏版)>请在人人文库网上搜索. 1.编译原理实验报告项目名称 ...

  2. html解析器编译原理,编译原理实验报告词法分析器(内含源代码).docx

    编译原理实验报告词法分析器(内含源代码) 编译原理实验(一) --词法分析器 实验描述 运行环境:vc++2008 对某特定语言A ,构造其词法规则. 该语言的单词符号包括: 1该程序能识别的单词符号 ...

  3. 编译原理逆波兰式实验java_逆波兰式算法的编译原理实验过程.doc

    逆波兰式算法的编译原理实验过程 实验目的 深入理解算符优先分析法 掌握FirstVt和LastVt集合的求法有算符优先关系表的求法 掌握利用算符优先分析法完成中缀表达式到逆波兰式的转化 实验内容及要求 ...

  4. 编译原理实验:代码生成作业(1)

    编译原理实验4:中间代码生成实验包-C++文档类资源-CSDN下载编译原理实验4:中间代码生成实验包更多下载资源.学习资料请访问CSDN下载频道.https://download.csdn.net/d ...

  5. 编译原理--实验2 语法分析

    文章目录 前言 1.1实验目的 1.2 实验任务 1.3 实验内容 1.3.1 实验要求 1.3.2 输入格式 1.3.3 输出格式 1.3.4 样例 1.4 程序 1.4.1 程序流程图 1.4.2 ...

  6. 编译原理实验二:赋值语句的语法分析程序设计

    编译原理实验二:赋值语句的语法分析程序设计 1.1实验内容 目的: 在前面实验的基础上,通过设计.编制.调试一个典型的赋值语句的语法分析程序,实现对词法分析程序所提供的单词序列进行语法检查,进一步掌握 ...

  7. 编译原理实验:自上而下语法分析

    编译原理实验:自上而下语法分析 1. 实验题目:自上而下语法分析 实验目的 实验内容 实验要求 输入输出 2. 设计思想 3. 算法流程 4. 源程序 5. 调试数据 1. 实验题目:自上而下语法分析 ...

  8. java编程实现算符优先分析法,编译原理实验三-算符优先分析法

    编译原理实验3-算符优先分析法 #include #include #include #include #define SIZE 128 char priority[6][6]; //算符优先关系表数 ...

  9. 编译原理实验 -- 文法分析

    编译原理实验 – 文法分析 终结符 和 非终结符 终结符 通常使用小写字母表示,例如 abcdef 非终结符 通常使用大写字母表示,例如 ABCDEF 产生式 通俗来说,就是由终结符和非终结符组合成的 ...

  10. c语言词法分析器实验原理,词法分析器的设计与实现 编译原理实验报告.doc

    词法分析器的设计与实现 编译原理实验报告 中北大学软件学院 实 验 报 告 专 业 软件工程 课程名称 编译原理 学 号 姓 名 辅导教师 张静 成绩 实验日期2015.5.19实验时间14:00~1 ...

最新文章

  1. Qt 打印时间 毫秒级
  2. [转帖]Sqlcmd使用详解
  3. ActiveMQ的几种集群配置
  4. 盘点Spring Boot最核心的27个注解
  5. 如何判断注册用户是否已经存在(membership验证)
  6. plsql最多可以存多少_银行内部透露:如果有10万块钱,可以都放在余额宝里吗?存银行是不是更好?...
  7. 【CCCC】L2-023 图着色问题 (25分),,图的染色判定,遍历
  8. QQ是怎样实现好友桌面快捷方式的?
  9. 豆丁文档无需豆元直接下载
  10. Excel数据导入Oracle数据库
  11. 测试用例设计方法大全下——场景法、、错误推测法、正交法
  12. Ffmpeg视频压制的基础知识
  13. 网课答案公众号查询方法
  14. 三大语言(C/Java/Python)基本数据类型大小 / 内置容器 总结
  15. Halcon例程分析2:颜色检测识别
  16. C语言---sprintf组包、sscanf解包
  17. 为什么很多公司不要培训机构出来的程序员?
  18. 计算机机房的安全知识有哪些,计算机公共机房安全管理制度
  19. 【学习笔记】人工智能导论
  20. 用多备份将网站数据备份到百度云,七牛云存储,阿里云OSS,亚马逊S3,金山云等云存储服务上

热门文章

  1. 火车采集器如何采集ajax数据和百度站内搜索数据
  2. 止增笑耳的星际迷航前传
  3. 帝都地铁隧道里的动态广告是什么原理?
  4. TSL1401 CCD传感器驱动
  5. [CSP-S模拟测试]:跳房子(模拟)
  6. “我去图书馆”公众号代码抢座的实现
  7. “海尔”业务流程再造给我们的启示(转载)
  8. dismiss和remove_关于dismiss的用法,dismiss和fire的区别?
  9. 适合女孩子玩的计算机游戏,小仙女们!NS上7款最适合女孩子玩的游戏盘点
  10. 大主宰PHP文章,大主宰:沈苍生让李玄通放弃洛璃?牧尘受到眷顾!北溟再出手相助...