3.2-3.3 词法分析---NFA转换到DFA~DFA 最小化 Hopcroft 算法

2024-04-05 04:32

本文主要是介绍3.2-3.3 词法分析---NFA转换到DFA~DFA 最小化 Hopcroft 算法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

子集构造算法
因为NFA不适合直接用来做词法分析器的识别,是因为它的状态转移是不确定的,这种情况下写一个算法往往需要回溯,对于分析的效率影响会比较大,所以需要用子集构造算法由NFA将它转换成与它等价的DFA(因为DFA是确定有限状态自动机),最终转换成词法分析器可以使用的代码。

子集构造算法思想
a(b|c)*
下图是一个NFA,很明显它的转移边包含 ε 所以它的状态转移是不确定的,我们所要做的是
由最开始的 NFA 状态出发,先读入任何一个字符看它能走到 NFA 的那个节点,并且考虑由于 ε 边的存在,实际上他能拓展走到那个边界上(走到这个边界,那么就没有任何的 ε 边能继续走下去),这个边界就是所有节点中的子集。(q0)
在这个边界上再读如任何的字符,可以走到那些节点,这些节点通过 ε 又可以走到那些集合,这样就会得到一个新的节点(q1)
现在可以想 这些节点(q2) 又可以走那些节点走到 ε 边界,得到一个集合…

在这里插入图片描述

除了 a 可以走到的边之外,由于 ε 可以简化(它不消耗任何输入状态),所以我们转换 ε 边后它还可以走到后面的5个节点

— a —> n1
n0 — a —> {n1,n2,n3,n4,n6,n9}; 由 n0 读入 a 这个状态后所能走到的所有可能的状态 把这个集合记作 q0

在这里插入图片描述
在这个子集上,我们再读入 b,在这个子集中所有的状态上看b造成的转换
q1 — b —> n5
q1 — b —> {n5,n8,n9,n3,n4,n6} ;q1

那么按照以上逻辑及转换的节点:

q0 – a–> [q1] – b --> [q2] …

我们通过这样的构造过程就由 NFA 转换到DFA,因为转换后的结果就不会得到 ε 边了。
需要注意的是 q0 集合中,我们会到达接受状态 n9 ,q1 状态中我们也会最终到达接受状态所以这里 q1 和 q2 都是接受状态

q0 – a–> [[q1]] – b --> [[q2]] …

状态转换, 有一个子集 q1 ,q1在 字符 b 的驱动下可以转换成那些状态,这里实际有两个重要的操作。

1.先看原来的NFA,在所有的节点上b都能转换成那些状态,就能得到第一个集合,把他叫做 delta(q) ,
2.在这个集合上对每一个元素再求 ε 边界

对算法的讨论:
不动点算法:
算法为什么能够运行终止。 这个算法每循环一次,往工作表中增加元素的同时,还会向大Q增加元素,大Q里面的元素是 所有 NFA 状态集合的子集

Q = {{...} {...} ...}q0    q1

时间复杂度
最坏的情况 O(2n);
但在实际中不常发生,因为并不是每个子集都会出现

ε - 闭包的计算:深度优先

在这里插入图片描述
ε 闭包的计算 是通过 delta 转换后的三个节点边界,和(浅色)边界节点中的 ε 边 继续走还会到达那个节点,所有的这些节点加起来就会构造 ε 闭包 。

set closure = {}   //全局变量 ε closure 这样一个闭包,他是一个集合,开始时初始化为空集//显然 对任何一个节点x计算一个 ε 闭包都将是一个集合
void sps_closure (x)closure += {x}  //把 x 这个集合并到 closure 这个集合中去 是 U 操作,实际上在x的 ε 闭包上增加了一个元素它自身,任何节点的闭包首先包含它自身foreach (y:x --ε--> y)  //对每个y元素做循环,y要满足条件 x 有一条 ε 指向 y,在有向图中 y 是x的后继节点,并且他们直接有效边上标的是 εif(!visited(y))        // 如果y还没有走过的话,那么递归的走y ,这是一个典型深度优先遍历算法eps_closure(y)    

例子:
求 N1 的 ε 闭包

在这里插入图片描述

把 n1 加入到 ε closure 闭包中去
然后对于存在 ε 转换这样一条边的后继节点 n2 来说我再递归计算 n2,递归结束后 n2 会被加入到 closure 闭包中
然后递归 n3 … n9 这样

ε - 闭包的计算:宽度优先
它是基于队列的概念,就是它从q开始能够走到的后继所有的y 把他加到访问队列中访问队列不为空的时候就循环,不停按层序来遍历。

set closure = {};Q = []; //queue
void eps_closure(x) = 
Q = [x];
while(Q not empty)q <- deQueue(Q)closure +=qforeach(y:q--ε -->y)if(!visited(y))enQueue(Q,y)

具体代码运作:

// 子集构造算法:工作表算法q0 <- eps_closure(n0)  //起始状态是 n0 ,因为n0 上没有ε 边,所以 q0 = {n0}
Q <-{q0}; //此时 Q = {{n0}}
workList <- q0
while(workList != [])remove q from workList        // q0 从 workList 中被移除foreach(character c)          //对每一个字符做循环t <- e-closure (delta(q,c)) //循环的时候看 q上面对c的转换 q =0 ,当循环256 次到 c的时候才对它有转换 此时 delta(q0,'a') = {n1} //加上 ε{delta(q0,'a') = {n1}} ,就是算里面每一个元素的 delta 也就是 {n1,n2,n3,n4,n6,n9} 共6个不同的元素D[q,c] <- t                 //工作表中增加 q1,这里它还是只有一个元素,之前的元素 remove 掉了if(t\not\in Q)add t to Q and workList

计算结果:

起始状态 no  q0 = {n0} 
workList = q0
进图到 第七行 remove q from workList ,从 workList 移除 q0
然后 算 delta  q1 = {n1,n2,n3,n4,n6,n9}D[q,c] <- t   就是 q0 --- a ---> q1
把 q1 加到 工作表 workList 中加入子集 workList q2
q1 delta b 转换子集 q2 = {n5,n8,n9,n3,n4,n6}
D[q,c] <- t  q0 --- a ---> q1 --- b ---> q2加入子集 workList q3
q1 delta b 转换子集 q3 = {n7,n8,n9,n3,n4,n6}
D[q,c] <- t   q0 --- a ---> q1 --- c ---> q2--- b ---> [q2]/
[q0[ --- a ---> [q1] \ --- c ---> [q2]q2 读入一个b 会在 n4 状态上做转换,到n5 就是 -> {n5,n8,n9,n3,n4,n6} ,我们发现如果从q2 读入一个状态,那么它得到的结果
和q2 是相同的,那么它起始是自循环的一条边q3 读入一个c 也是一个自循环在q2 状态中 读入一个 c 会转换到 q3
在q3 状态中 读入一个 b 会回到   q2

在这里插入图片描述

这个 DFA 包含4个状态,其中 q0 是初始状态,q1,q2,q3 是接受状态,这些状态可以进行相互合并。
非接受状态和接受状态不能合并,如果同样是非接受状态,或者都是接受状态,那么就有可以合并。

q2 和 q3 如果用一个状态来合并

在这里插入图片描述

把 q1 和 q4 合并融合成一个节点

在这里插入图片描述
这样的有限状态自动机 DFA 最后做代码实现的话要变成内部的一个数据结构表示显然状态和边越少,它所占用的
资源和内存也就越少,有可能会提升算法效率,所以我们要来研究有限状态自动机最小化的算法怎么来做

Hopcroft 算法

在这里插入图片描述

// 基于等级类的思想
split(s)  //s 是状态的集合foreach (character c)  //对每一个字符c做循环256次if(c can split s)    //如果c能够对这个集合做切分的话就把它切成t1..tksplit s into T1,...,Tk //这里我们可以看到 q1 q2是一组 是同类的,q3对a的转移到了s3也就是说 a 这个字符把集合s切成了2个子集//一个是q1,q2,一个是q3
hopcroft()split all nodes into N,A  //一开始就把所有的节点切分成2个等价类,一个N非接受状态,一个A接受状态while(set is still changes)  //继续把里面是子集再切分split(s)

Hopcroft 算法在实际中是如何工作的:
示例1:
a(b|c)*

在这里插入图片描述
根据 Hopcroft 的思想首先我们会切分2个集合出来
N: q0
A: q1,q2,q3
继续看每个集合还是不是能切分,首先N不用看了它是单元素集合,
b 和 c 都无法区分q1,q2,q3的不同性,所以我们把这三个节点可以合并成一个新节点q4
并且b和c 都是会在自身上做的状态转移,所以我们要加上一条回边

在这里插入图片描述
示例2:
fee|fie
f(ee|ie)

在这里插入图片描述
首先切分成2个初始的集合:
N: q0,q1,q2,q4
A q3,q5
循环到字符 e 的时候都转换到了集合 A,q0,q1接受e的话,e导致q2, 都在它内部
e:{q0,q1},{q2,q4}

集合e:{q0,q1},{q2,q4},{q3,q5}s       A

q2,q4 都能接受e 并且他们的子集都到了 集合A,所以这个集合不能被划分了
q0,q1,假设q1 接受了e,就会进入集合 s (q2,q4) 中
所以就把这个集合进一步切分成 {q0},{q1}

集合e:{q0}{q1},{q2,q4},{q3,q5}s       A

那么最终的自动机
在这里插入图片描述

这篇关于3.2-3.3 词法分析---NFA转换到DFA~DFA 最小化 Hopcroft 算法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/877636

相关文章

Go使用pprof进行CPU,内存和阻塞情况分析

《Go使用pprof进行CPU,内存和阻塞情况分析》Go语言提供了强大的pprof工具,用于分析CPU、内存、Goroutine阻塞等性能问题,帮助开发者优化程序,提高运行效率,下面我们就来深入了解下... 目录1. pprof 介绍2. 快速上手:启用 pprof3. CPU Profiling:分析 C

MySQL表锁、页面锁和行锁的作用及其优缺点对比分析

《MySQL表锁、页面锁和行锁的作用及其优缺点对比分析》MySQL中的表锁、页面锁和行锁各有特点,适用于不同的场景,表锁锁定整个表,适用于批量操作和MyISAM存储引擎,页面锁锁定数据页,适用于旧版本... 目录1. 表锁(Table Lock)2. 页面锁(Page Lock)3. 行锁(Row Lock

Python3脚本实现Excel与TXT的智能转换

《Python3脚本实现Excel与TXT的智能转换》在数据处理的日常工作中,我们经常需要将Excel中的结构化数据转换为其他格式,本文将使用Python3实现Excel与TXT的智能转换,需要的可以... 目录场景应用:为什么需要这种转换技术解析:代码实现详解核心代码展示改进点说明实战演练:从Excel到

golang字符串匹配算法解读

《golang字符串匹配算法解读》文章介绍了字符串匹配算法的原理,特别是Knuth-Morris-Pratt(KMP)算法,该算法通过构建模式串的前缀表来减少匹配时的不必要的字符比较,从而提高效率,在... 目录简介KMP实现代码总结简介字符串匹配算法主要用于在一个较长的文本串中查找一个较短的字符串(称为

通俗易懂的Java常见限流算法具体实现

《通俗易懂的Java常见限流算法具体实现》:本文主要介绍Java常见限流算法具体实现的相关资料,包括漏桶算法、令牌桶算法、Nginx限流和Redis+Lua限流的实现原理和具体步骤,并比较了它们的... 目录一、漏桶算法1.漏桶算法的思想和原理2.具体实现二、令牌桶算法1.令牌桶算法流程:2.具体实现2.1

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

Java数字转换工具类NumberUtil的使用

《Java数字转换工具类NumberUtil的使用》NumberUtil是一个功能强大的Java工具类,用于处理数字的各种操作,包括数值运算、格式化、随机数生成和数值判断,下面就来介绍一下Number... 目录一、NumberUtil类概述二、主要功能介绍1. 数值运算2. 格式化3. 数值判断4. 随机

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

Python实现视频转换为音频的方法详解

《Python实现视频转换为音频的方法详解》这篇文章主要为大家详细Python如何将视频转换为音频并将音频文件保存到特定文件夹下,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. python需求的任务2. Python代码的实现3. 代码修改的位置4. 运行结果5. 注意事项