编译原理笔记6:从正规式到词法分析器(3):DFA最小化、词法分析器的构造、Lex 使用示例

简介:

从 DFA 到最小 DFA

关于星闭包的补充:一个语言被认为是所有可能字的子集。所有可能字的集合可以被认为是所有可能的字符串串接的集合。

DFA 最小化的过程,就是通过某些等效转换减少原 DFA 状态数的过程——这里的“等效转换”,就是对多余的状态进行合并。

那,什么叫多余?这里的多余,指的是对于同样的输入会得到同样的结果——比如在上面NFA转DFA的例子中,我们观察得到的DFA,发现其中的A、C状态对于字母表中任意的输入,都会给出相同的结果。那么这个A和C对任意输入而言就是等效的,它们两个就可以互相替代,我们也就可以把它们合并成一个状态。

将DFA最小化需要去除多余状态,所以如何最小化DFA的问题就转化为了如何找出“多余状态”的问题。我们需要一个方法来帮助我们判断某个状态是否是多余的。这里引入“等价”、“可区分”、“划分”的概念:

等价

定义:设 p、q ∈ S,对于任一输入序列 w∈Σ*,有 move(p, w)∈F 且 move(q, w)∈F,则称状态 p 和 q 等价。否则称 p、q 是可区分的,也就是存在 x∈Σ*,使得move(p, x) 和 move(q, x) 不能同时进入终态。

可区分

对于DFA中的任意两个状态t、s,若从其中一个状态出发能够接受输入字符串 ω,而从另一个状态出发却不能接受 ω,则称 ω区分状态t和s。如果存在某个能够区分状态 t 和 s 的串,那么状态 t 和 s 就是可区分的。

因此,如果我们找到这样一对 t 和 s ,对于从它们两个状态分别出发的任何的输入序列 ω,都能够最终到达相同的结果,那么 t 和 s 就可以合并成一个状态。

因此,最小化 DFA 的本质,就是将 DFA 中的状态分成不同的组,使得同一个组中的状态之间不可区分(也就是等价的),而不同组的状态之间可区分(也就是不等价)。当我们把每个组内的状态都合并成为一个状态后,我们就能够得到一个最小 DFA 了

(其实就是把DFA中的各个状态划分成几个等价类,然后把等价类内的状态进行合并。这样最终得到的最小DFA的状态数就是之前我们划分的等价类数。每一个等价类对应最小DFA中的一个状态)

划分

给定一个DFA,我们可以确定:

  • DFA 的终态和非终态是可区分的;(使用 ε 即可区分终态和非终态。因为 DFA 中不存在 ε 转移。DFA 中的任意状态经过 ε 都不会发生改变)
  • 若分别从 s 和 t 出发,沿着标记为 x 的路径到达的两个状态p、q是可区分的,那么 s 和 t 也是可区分的。
    这里的 p、q 是我们已知可区分的——因为对于 p、q,如果有相同的输入序列 y 使得他们分别到达终态和非终态,那么 p、q 就是可区分的了。一旦 p、q 可区分,则 s、t 经过输入序列 xy 就能够分别到达 f、g ,也就随之满足可区分的定义了

3_11

算法:最小化 DFA 的状态数(DFA化简)

输入:DFA D = {S, Σ, move, s0, F }

输出:等价的 D' = {S', Σ, move', s0', F' }(D' 的状态数最少)

主要步骤:

  1. 进行初始化分,获得终态和非终态;
  2. 利用“可区分”的概念,反复分裂划分中的组 Gi,直到不可再分裂;
  3. 由最终划分构造 D',关键是用等价的概念对一些状态进行合并,选出新的代表状态并修改状态转移;
  4. 消除可能的死状态的不可达状态。

化简的基本思路,其实就是把原来的状态集按照等价关系进行划分(划分后的子集互不相交),同一子集内元素等价,不同子集元素可区分。将每一个子集中的状态合并成一个状态,就可以得到一个新的状态集 Snew,原初态s0所在子集就成为化简后状态机 Dnew 的新初态 s0new ,原终态所在子集就成为化简后的 Dnew 的一个新终态。

下面用这个算法来化简我们前面的 DFA
6_1
每个DFA都能够最小化,这里有个定理:

对于一个 DFA D = {S, Σ, mvoe, s0, F}, 存在唯一一个最小状态(就状态数而言)DFA D' = {S', Σ, move', s0, F'} 与 D 等价。

手写 DFA

建出来了 DFA 的状态转移图,我们就可以通过直接编码的方式来为我们的 DFA 手写词法分析器了。由于操作复杂,故实际应用中不会使用这种方法构造词法分析器,而是会使用 Lex 进行该工作。

此处先略,日后再补。。。(坑)

词法分析器的构造

实际应用中,我们使用工具来生成词法分析器。因为从正规式到词法分析器这个过程中的每一步都有对应的算法来实现。我们构造自己的词法分析器,只需要使用 Lex 就可以了。
6_2

Lex 使用示例

6_3
点击 Lex 中的 编译按钮 ,将会生成一个 .c 和一个 .h 文件

对于词法解析错误的输入,lex 编译出的程序将会直接把错误的输入回显

相关文章
|
5月前
|
Python
Python将字符串拆成单字的函数代码设计
要想将字符串拆成单字,在Python中有一个非常简单的方法,一行代码就可以搞定了。那就是将字符串转换成列表list即可。这个过程可以使用内置的list()函数。不过,如果是要将英文的拆分为单个单词,那么这种方法就行不通了,因为该函数会将英文单词逐个拆分为字母,如果是这种情况,
77 2
|
4月前
|
自然语言处理 容器
S语言词法分析器设计
还有很多需要优化的地方,作为小白发出了也和大家一起交流下,这次我是分文件写的,因为考虑到以后的实验都用这一套代码,分文件写方便一点,用的是C++14标准
27 0
|
5月前
编译原理——构造预测分析表(判断某字符串是否是文法G(E)的句子)
编译原理——构造预测分析表(判断某字符串是否是文法G(E)的句子)
50 0
编译原理实验:NFA转化为DFA
编译原理实验:NFA转化为DFA
153 0
|
自然语言处理 C语言 C++
编译原理 实验二:词法分析器的手动实现(基于状态机的词法分析器)
编译原理 实验二:词法分析器的手动实现(基于状态机的词法分析器)
967 0
编译原理 实验二:词法分析器的手动实现(基于状态机的词法分析器)
|
存储 自然语言处理 Unix
编译原理 实验一:词法分析器的自动实现(Lex词法分析)
编译原理 实验一:词法分析器的自动实现(Lex词法分析)
916 0
编译原理 实验一:词法分析器的自动实现(Lex词法分析)
|
存储 Go C语言
编译原理,C语言实现LR(0)分析(扩展文法的生成、项目集规范簇的生成、ACTION GOTO表的生成、句子的分析)
注:代码生成的项目集规范簇、ACTION GOTO表的顺序可能和课本、教材、参考答案的顺序不同,但这并不影响分析过程的正确性,毕竟机器是按规律办事的😄
457 0
编译原理,C语言实现LR(0)分析(扩展文法的生成、项目集规范簇的生成、ACTION GOTO表的生成、句子的分析)
|
存储 自然语言处理
词法分析器的设计与实现
加深对词法分析器的工作过程的理解;加强对词法分析方法的掌握;能够采用一种编程语言实现简单的词法分析程序;能够使用自己编写的分析程序对简单的程序段进行词法分析。
193 0
|
存储 自然语言处理 算法
简单的词法设计——DFA模拟程序
简单的词法设计——DFA模拟程序
248 0
简单的词法设计——DFA模拟程序