编译原理: Subset Construction 子集构造法(幂集构造)(NFA转DFA)

文章目录

  • 编译原理: Subset Construction 子集构造法(幂集构造)(NFA转DFA)
    • 简介
    • 参考
  • 正文
    • 示例回顾
    • 子集构造法 Subset Construction
      • 函数定义
      • 算法过程伪代码
      • 构造 Dtran
      • 绘制 DFA
  • 结语

简介

上一篇:编译原理: Thompson 构造法(正则表达式 转 NFA)

我们再回顾一次使用正则表达式构建 DFA 的具体流程:

  1. Thompson 构造法:正则表达式 RegExp -> 不确定有限状态机 NFA
  2. 子集构造法:不确定有限状态机 NFA -> 确定有限状态机 DFA
  3. 最小化:最小化确定有限状态机 DFA
  4. 验证:DFA 转换为的等价正则表达式并验证等价

在前一篇我们完成了第一步构建出了 NFA,本篇将要使用子集构造法(Subset Construction)将 NFA 转换成 DFA。

本篇内容源于编译原理第二版(龙书),直接看书搭配网上说明并动手实践是最清楚的。

参考

NFA转DFA的子集构造(subset construction)算法 https://www.jianshu.com/p/8e0fb9cf3f49
幂集构造-百度百科 https://baike.baidu.com/item/%E5%B9%82%E9%9B%86%E6%9E%84%E9%80%A0/22735892?fr=aladdin

正文

示例回顾

首先我们先来回顾以下上一篇使用 Thompson 构造法构造出的两个例子的 NFA

示例1: a(b∣c)∗a(b|c)^{*}a(b∣c)∗

示例2: (a∣b)∗abb(a|b)^{*}abb(a∣b)∗abb

有了 NFA 你以为就可以了吗,那可不行。回顾上一篇说的 DFA 和 NFA 的差异,NFA 存在一个问题是对于同样的输入状态和输入字符,可能存在多个输出状态

这可不行,这样我们在实际应用的时候怎么知道该往那个状态转移呢?所以我们就需要将 NFA 转换成 DFA 才方便后续的使用。

子集构造法 Subset Construction

接下来我们将使用子集构造法(Subset Construction,也称幂集构造)来将 NFA 转变成 DFA。

函数定义

在开始构建 DFA 之前,我们需要定义几个函数和状态的概念(以下提到的状态分为 DFA 状态和 NFA 状态,而 DFA 状态是一个 NFA 状态的集合。以下提到的状态除非特别说明否则默认指的是 NFA 状态)

  1. ϵ−closure(s),s∈SNFA\epsilon-closure(s), s \in S_{NFA}ϵ−closure(s),s∈SNFA​ 闭包
  • 输入:一个状态集 s
  • 输出:所有 s 中的状态可经由任意长度的 ϵ\epsilonϵ 边能抵达的状态集合
  • 补充:这边的状态指的是 NFA 中的状态
  1. move(A,a),A∈SDFA,a∈∑move(A, a), A \in S_{DFA}, a \in \summove(A,a),A∈SDFA​,a∈∑
  • 输入:一个 DFA 状态(即一个 NFA 的状态的集合,后续),一个输入字符(a)
  • 输出:DFA 状态中每个 NFA 状态透过 aaa 边能抵达的所有 NFA 状态的集合

算法过程伪代码

接下来我们使用伪代码来描述子集构造法的构造过程

Subset-Construction(NFA)let Dtran be a tableDFA_States = {ε-closure(NFA.s0)}  # DFA 状态集合的初始状态为 NFA 初始状态的闭包,并且未标记while (exist T in DFA_States not marked) { # 存在未标记的 DFA 状态mark T  # 标记 T,表示查过 T 状态的所有后续状态了for (a in Σ) { Tc = ε-closure(move(T, a)) # 找到所有输入字符对应的下一个状态if (Tc not in DFA_States) { # 将状态加入到 DFA_Statespush Tc in DFA_States & unmarked Tc}Dtran[T, a] = Tc}}return Dtran

上面看起来很复杂,简单来说从起始状态开始(ϵ−closure(s0)\epsilon-closure({s0})ϵ−closure(s0)),使用输入字符找到下一个 DFA 状态,并对于所有 DFA 状态递归搜索下一个状态知道成为闭包。

如果还是觉得很抽象不妨直接看向下面的示例吧!

构造 Dtran

上面 Subset−ConstructionSubset-ConstructionSubset−Construction 的伪代码的最终目的就是要构建出一个 Dtran(同时就是 DFA 的一种表示)

接下来我们就分别对两个例子进行构建吧

示例1: a(b∣c)∗a(b|c)^{*}a(b∣c)∗

首先我们现对原来的 NFA 状态进行编号

  1. 首先我们先求出起始 DFA 状态设为 AAA
A = ε-closure({a1}) = {a1}

这时候的 Dtran 状态如下

  1. 第二步找到未标记状态 AAA,可输入字符为 aaa
Dtran[A,a] = ε-closure(move(A,a))= ε-closure({a2})= {a2,a3,a4,a5,a7,a10}

Dtran[A,a]Dtran[A,a]Dtran[A,a] 表示的集合不在 Dtran 状态表里,则记为新状态 BBB

  1. 下一个未标记状态为 BBB,可输入字符有 b,cb, cb,c
Dtran[B,b] = ε-closure(move(B,b))= ε-closure({a6})= {a4,a5,a6,a7,a9,a10}= CDtran[B,c] = ε-closure(move(B,c))= ε-closure({a8})= {a4,a5,a7,a8,a9,a10}= D

加入新状态 C,DC, DC,D

  1. 下一个状态 CCC,可输入字符有 b,cb, cb,c
Dtran[C,b] = ε-closure(move(C,b))= ε-closure({a6})= {a4,a5,a6,a7,a9,a10}= CDtran[C,c] = ε-closure(move(C,c))= ε-closure({a8})= {a4,a5,a7,a8,a9,a10}= D

没有新状态,仅仅添加 CCC 的输出状态

  1. 下一个状态 DDD,可输入字符有 b,cb, cb,c
Dtran[D,b] = ε-closure(move(D,b))= ε-closure({a6})= {a4,a5,a6,a7,a9,a10}= CDtran[D,c] = ε-closure(move(D,c))= ε-closure({a8})= {a4,a5,a7,a8,a9,a10}= D

一样没有新状态,这时 A,B,C,DA, B, C, DA,B,C,D 四个 DFA 状态形成了闭包,代表已经完成了最终 Dtran 表的构建了

示例2: (a∣b)∗abb(a|b)^{*}abb(a∣b)∗abb

第二个例子一样按照上面的顺序先编号后递归查找未标记状态对每个输入的输出状态,各个步骤的说明就省略了

  1. 起始状态 AAA
A = ε-closure({b1}) = {b1,b2,b3,b5,b8,b9}

  1. 状态 AAA,输入 a,ba, ba,b
Dtran[A,a] = ε-closure(move(A,a))= ε-closure({b4,b10})= {b2,b3,b4,b5,b7,b8,b9,b10,b11}= 新状态 BDtran[A,b] = ε-closure(move(A,b))= ε-closure({b6})= {b2,b3,b5,b6,b7,b8,b9}= 新状态 C

  1. 状态 BBB,输入 a,ba, ba,b
Dtran[B,a] = ε-closure(move(B,a))= ε-closure({b4,b10})= {b2,b3,b4,b5,b7,b8,b9,b10,b11}= BDtran[B,b] = ε-closure(move(B,b))= ε-closure({b6,b12})= {b2,b3,b5,b6,b7,b8,b9,b12,b13}= 新状态 D

  1. 状态 CCC,输入 a,ba, ba,b
Dtran[C,a] = ε-closure(move(C,a))= ε-closure({b4,b10})= {b2,b3,b4,b5,b7,b8,b9,b10,b11}= BDtran[C,b] = ε-closure(move(C,b))= ε-closure({b6})= {b2,b3,b5,b6,b7,b8,b9}= C

  1. 状态 DDD,输入 a,ba, ba,b
Dtran[D,a] = ε-closure(move(D,a))= ε-closure({b4,b10})= {b2,b3,b4,b5,b7,b8,b9,b10,b11}= BDtran[D,b] = ε-closure(move(D,b))= ε-closure({b6,b14})= {b2,b3,b5,b6,b7,b8,b9,b14}= 新状态 E

  1. 状态 EEE,输入 a,ba, ba,b
Dtran[E,a] = ε-closure(move(E,a))= ε-closure({b4,b10})= {b2,b3,b4,b5,b7,b8,b9,b10,b11}= BDtran[E,b] = ε-closure(move(E,b))= ε-closure({b6})= {b2,b3,b5,b6,b7,b8,b9}= C

绘制 DFA

其实到此我们已经完成 DFA 的构建了,Dtran 表就是一种也是应用时使用的 DFA 转换函数表,现在我们根据构建好的 Dtran 表绘制出对应的 DFA

示例1: a(b∣c)∗a(b|c)^{*}a(b∣c)∗

  • Dtran 表

  • DFA 图

示例2: (a∣b)∗abb(a|b)^{*}abb(a∣b)∗abb

  • Dtran 表

  • DFA 图

结语

到此我们已经成功的从正则表达式 -> NFA -> DFA,其实到现在这个步骤已经非常接近答案了,这个阶段构建出来的 DFA 也确实已经是能拿来使用了。下一篇我们将要介绍如何最小化 DFA 并且验证我们构建出来的 DFA 于原来的正则表达式等价。

编译原理: Subset Construction 子集构造法(幂集构造)(NFA转DFA)相关推荐

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

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

  2. 编译原理 词法分析 算符优先分析法

    编译原理 词法分析 算符优先分析法 实验目的 加深对语法分析器工作工程的理解,加强对算符优先分析法实现语法分析程序的掌握:能够采用一种编程语言实现简单的语法分析程序:能够使用自己辨析的分析程序对简单的 ...

  3. 编译原理-如何使用flex和yacc工具构造一个高级计算器

    Flex工具的使用方法 Lex 是一种生成扫描器的工具. Lex是Unix环境下非常著名的工具,主要功能是生成一个扫描器(Scanner)的C源码. 扫描器是一种识别文本中的词汇模式的程序. 这些词汇 ...

  4. 编译原理教程_6 LR分析法

    文章原稿 https://gitee.com/fakerlove/fundamentals-of-compiling 文章目录 6. LR分析法 6.1 简介 (1)LR分析法的优缺点 (2)分析表的 ...

  5. 编译原理:语法分析实验(LR分析法)

    语法分析实验 一.实验目的 根据LR分析法的原理,对指定文法构造识别活前缀的DFA,做出相应的LR分析表,并编程实现相应的语法分析程序.或根据预测分析法的原理,对指定文法构造预测分析表,并编程实现相应 ...

  6. 编译原理: 最小化 DFA(划分) 验证 DFA(Kleene 闭包)

    编译原理: 最小化 DFA(划分) & 验证 DFA(Kleene 闭包) 文章目录 编译原理: 最小化 DFA(划分) & 验证 DFA(Kleene 闭包) 简介 参考 正文 示例 ...

  7. 【编译原理】学习笔记1 词法分析

    进行词法分析,打印分析结果. 编译器是一个程序:输入字符串,输出目标代码. 词法分析: 读入源码字节,将其组成有意义的TOKEN流. 语法分析: 根据TOKEN流构建树形的中间表示. 语义分析: 检查 ...

  8. 【编译原理】FIRST集和FOLLOW集构造法速学

    编译原理速成大法 FIRST集和FOLLOW集构造法速成 FIRST FOLLOW FIRST集和FOLLOW集构造法速成 例:对于文法G(E) 首先像E,T,E',F这样的就是非终结符 +,*, ε ...

  9. 朱娜斐编译原理复习笔记-北京工业大学软件学院

    朱娜婓编译原理学习笔记 说明 笔记大部分内容来自参考资料[1], 看了B站上中科大华保健老师的编译原理课视频(参考资料[2]),补充完善了DFA的代码表示.Hopcroft 算法.文法重写.LL(1) ...

最新文章

  1. 动态规划和分治法的区别
  2. 智能家居隐私问题再遭热议:涉案设备中的数据究竟受不受保护?
  3. python怎么使用int四舍五入_python中如何取整数
  4. Mac FinalShell 连接 VirtualBox 命令行卡顿
  5. hdu4396 多状态spfa
  6. Linux信号 三 信号发送接口集合
  7. bzoj 3033 太鼓达人——欧拉图搜索
  8. 用C语言用指针怎么算通用定积分,C语言:利用指针编写程序,用梯形法计算给定的定积分实例...
  9. 【WebRTC---进阶篇】(五)mediasoup的信令系统
  10. Python pandas.DataFrame.median函数方法的使用
  11. 基于ARM处理器的U-BOOT详细移植总结
  12. ListView演练 - 带有组头的汽车品牌展示
  13. 厦门大学计算机科学夏令营过程,厦门大学信息学院计算机系夏令营学生见面会如期召开-厦门大学计算机科学系...
  14. Hibernate 的检索策略
  15. 基因数据处理72之GATK安装成功
  16. 如何获取QQ邮箱授权码——步骤详解
  17. 卡塔尔世界杯门线技术(GOAL LINE TECHNOLOGY)背后的黑科技
  18. java什么算垃圾代码,[把代码写成诗]Java美好的承诺,自动回收垃圾
  19. Chromium浏览器下载
  20. from Crypto.Cipher import AES报错解决【WindowsLinux】

热门文章

  1. 多核cpu的缓存一致性
  2. Android Studio 连接第三方模拟器
  3. 创业路演前准备好这些,距离融资成功还会远吗?
  4. 手动测量变量溢出长度
  5. 编写第一个JavaFX界面
  6. 将土豆或者youku 的视频放到自己的网站上面.
  7. linux pppd源码下载_LINUX下的拨号利器:wvdial和pppd —— 转载
  8. 系统是综合应用最新多媒体计算机技术,RM-6280C多道生理信号采集处理系统
  9. 【亲测纯净版】10月最新晴天贷立刻贷小额借贷大数据借贷平台网站源码已对接免签支付
  10. 商标注册过程中,如何避免字体侵权?