自己动手写编译器:DFA状态机最小化算法

news2024/11/26 12:21:09

上一节我们完成了从NFA到DFA的状态机转换,有个问题是状态机并非处于最有状态:
在这里插入图片描述
在上图的状态机中,状态6和7其实可以合成一个状态点,本节我们看看如何将这类节点进行合并,使得状态机处于最精简状态(状态4也是终结点,图中有误)。

首先我们把所有节点罗列成下表:

所有节点输入字符D后跳转输入字符.后跳转是否是接收状态
021no
13_no
254no
3__yes
46_yes
551no
67_yes
77_yes

第一步我们先把节点根据是否为终结点分成两组,非终结点为一组,终结点为一组,并给每组设置编号,第一组编号0,第二组编号1:
[0,1,2,5]->(0), [3,4,6,7]->(1)

接下来我们分别进入每个分区,分别取出其中两个节点,如果他们接收相同输入,如果跳转的节点在不同分区,那么我们就认为这两个节点不能合成一个点,首先我们看分区0中的点0,它接收D后跳转到2,而节点2位于分区0;分区0中的点1接收D后跳转到节点3,它位于分区1;分区0中节点2接收D后跳转到节点5,后者位于分区0;分区0中节点5接收D后跳转5,后者同样位于分区0,于是我们把分区0中的节点1单独隔离出来形成一个分区:
[0,2,5]->(0), [1]->(2), [3,4,6,7]->(1)

接着我们看分区1,分区1中的节点3接收D后没有跳转,节点4接收D后跳转6,后者位于分区1;节点6,7接收D后跳转分区1,因此节点3要从分区1中隔离出来自己形成一个分区:
[0,2,5]->(0), [1]->(2), [3]->(3), [4,6,7]->(1)

接下来我们以输入’.‘来看每个分区,在分区0中节点0接收’.‘后跳转1,后者位于分区2;节点2接收’.‘,跳转到节点4,后者位于分区1,节点5接收’.'后跳转到节点1,后者为于分区2,因此我们把节点2从分区0中区分开成为一个新分区:
[0,5]->(0), [1]->(2), [3]->(3), [2]->(4), [4,6,7]->(1)

对于只有1个节点的分区我们可以忽略,现在我们看分区1,节点4接收’.‘后没有跳转,节点6接收’.'后没有跳转,节点7接收’.'后也是没有跳转,因此这三个点依然可以属于同一个分区。接下来我们继续返回到分区0,节点0接收D后跳转节点2,后者位于分区4,节点5接收D后跳转到节点5,后者位于分区0,因此我们把点0从分区0拿出来形成一个新分区:
[5]->(0), [1]->(2), [3]->(3), [2]->(4),[4,6,7]->(1), [0]->(5)
由此我们把节点4,6,7合并成一个节点,其他分区都只有一个节点,现在我们用分区编号替代每个分区集合中的点,由此得到DFA状态机如下:

请添加图片描述

下面我们给出算法的步骤描述,首先给出变量声明:
c: 当前输入字符
group: 一个分区中节点的集合,它也对应一个分区
groups: 当前分区集合
new: 当前分区中被拿出来的节点集合
first: 当前分区中第一个节点
next: 分区中不同于first的另一个节点,如果当前分区除了first对应节点外没有其他未访问过的节点,那么它取值nil
go_first: first对应节点在接收c表示的输入后跳转的节点
go_next: next对应节点接收c表示输入后跳转的节点。

初始化:
先将所有非终结节点放入分区0,将所有终结节点放入分区1,于是groups中包含两个group对象

重复如下步骤,直到groups为空
for(从groups中取出一个group) {
new = []
first = group中第一个节点
next = group中不同于first的节点,如果没有新节点那么设置为nil
for next {
for (当前输入字符c) {
go_first = first 对应节点接收字符c后跳转的节点
go_next = next对应节点接收字符c后跳转的节点
if go_first 和 go_next不属于同一个分区,把next对应节点加入到集合new
}
next 指向当前集合中下一个节点
}

if new != nil {
groups = append(groups, new
}
}

下面我们看看具体代码实现,在nfa_to_dfa.go中继续添加以下代码:

func (n *NfaDfaConverter) initGroups() {
	//先把节点根据接收状态分为两个分区
	for i := 0; i < n.nstates; i++ {
		if n.dstates[i].isAccepted {
			n.groups[1] = append(n.groups[1], n.dstates[i].state)
			//记录状态点对应的分区
			n.inGroups[n.dstates[i].state] = 1
		} else {
			n.groups[0] = append(n.groups[0], n.dstates[i].state)
			n.inGroups[n.dstates[i].state] = 0
		}
	}

	n.numGroups = 2
}

func (n *NfaDfaConverter) printGroups() {
	//打印当前分区的信息
	for i := 0; i < n.numGroups; i++ {
		group := n.groups[i]
		fmt.Printf("分区号: %d", i)
		fmt.Println("分区节点如下:")
		for j := 0; j < len(group); j++ {
			fmt.Printf("%d ", group[j])
		}
		fmt.Printf("\n")
	}
}

func (n *NfaDfaConverter) minimizeGroups() {
	for {
		oldNumGroups := n.numGroups
		for current := 0; current < n.numGroups; current++ {
			//遍历每个分区,将不属于同一个分区的节点拿出来形成新的分区
			if len(n.groups[current]) <= 1 {
				//对于只有1个元素的分区不做处理
				continue
			}

			idx := 0
			//获取分区第一个元素
			first := n.groups[current][idx]
			newPartition := false
			for idx+1 < len(n.groups[current]) {
				next := n.groups[current][idx+1]
				//如果分区还有未处理的元素,那么看其是否跟first对应元素属于同一分区
				for c := MAX_CHARS - 1; c >= 0; c-- {
					gotoFirst := n.dtrans[first][c]
					gotoNext := n.dtrans[next][c]
					if gotoFirst != gotoNext && (gotoFirst == F || gotoNext == F || n.inGroups[gotoFirst] != n.inGroups[gotoNext]) {
						//如果first和next对应的两个节点在接收相同输入后跳转的节点不在同一分区,那么需要将next对应节点加入新分区
						//先将next对应节点从当前分区拿走
						n.groups[current] = append(n.groups[current][:idx+1], n.groups[current][idx+2:]...)
						n.groups[n.numGroups] = append(n.groups[n.numGroups], next)
						n.inGroups[next] = n.numGroups
						newPartition = true
						break
					}
				}

				if !newPartition {
					//如果next没有被拿出当前分区,那么idx要增加去指向下一个元素
					idx += 1
				} else {
					//如果next被挪出当前分区,那么idx不用变就能指向下一个元素♀️
					newPartition = false
				}
			}

			if len(n.groups[n.numGroups]) > 0 {
				//有新的分区生成,因此分区计数要加1
				n.numGroups += 1
			}
		}

		if oldNumGroups == n.numGroups {
			//如果没有新分区生成,算法结束
			break
		}
	}

	n.printGroups()
}

func (n *NfaDfaConverter) fixTran() {
	newDTran := make([][]int, DFA_MAX)
	//新建一个跳转表
	for i := 0; i < n.numGroups; i++ {
		newDTran[i] = make([]int, MAX_CHARS)
	}

	/*
		我们把当前分区号对应一个新的DFA节点,当前分区(用A表示)中取出一个节点,根据输入字符c获得其跳转的节点。
		然后根据跳转节点获得其所在分区(用B表示),那么我们就得到新节点A在接收字符c后跳转到B节点	。
		这里我们从当前分区取出一个节点就行,因为在minimizeGroups中我们已经确保最终的分区中,里面每个节点在接收
		同样的字符后,所跳转的节点所在的分区肯定是一样的。
	*/
	for i := 0; i < n.numGroups; i++ {
		//从当前分区取出一个节点即可
		state := n.groups[i][0]
		for c := MAX_CHARS - 1; c >= 0; c-- {
			if n.dtrans[state][c] == F {
				newDTran[state][c] = F
			} else {
				destState := n.dtrans[state][c]
				destPartition := n.inGroups[destState]
				newDTran[state][c] = destPartition
			}
		}
	}

	n.dtrans = newDTran
}

func (n *NfaDfaConverter) MinimizeDFA() {
	n.initGroups()
	n.minimizeGroups()
	n.fixTran()
}

func (n *NfaDfaConverter) PrintMinimizeDFATran() {
	for i := 0; i < n.numGroups; i++ {
		for j := 0; j < MAX_CHARS; j++ {
			if n.dtrans[i][j] != F {
				fmt.Printf("from state %d jump to state %d with input: %s\n", i, n.dtrans[i][j], string(j))
			}
		}
	}
}

完成上面代码后,我们在main.go调用一下上面的实现:

func main() {
	lexReader, _ := nfa.NewLexReader("input.lex", "output.py")
	lexReader.Head()
	parser, _ := nfa.NewRegParser(lexReader)
	start := parser.Parse()
	parser.PrintNFA(start)
	//str := "3.14"
	//if nfa.NfaMatchString(start, str) {
	//	fmt.Printf("string %s is accepted by given regular expression\n", str)
	//}
	nfaConverter := nfa.NewNfaDfaConverter()
	nfaConverter.MakeDTran(start)
	nfaConverter.PrintDfaTransition()

	nfaConverter.MinimizeDFA()
	fmt.Println("---------new DFA transition table ----")
	nfaConverter.PrintMinimizeDFATran()
}

上面代码运行后输出结果如下:

分区号: 0分区节点如下:
0 
分区号: 1分区节点如下:
3 
分区号: 2分区节点如下:
1 
分区号: 3分区节点如下:
4 6 7 
分区号: 4分区节点如下:
2 
分区号: 5分区节点如下:
5 
---------new DFA transition table ----
from state 0 jump to state 2 with input: .
from state 0 jump to state 4 with input: 0
from state 0 jump to state 4 with input: 1
from state 0 jump to state 4 with input: 2
from state 0 jump to state 4 with input: 3
from state 0 jump to state 4 with input: 4
from state 0 jump to state 4 with input: 5
from state 0 jump to state 4 with input: 6
from state 0 jump to state 4 with input: 7
from state 0 jump to state 4 with input: 8
from state 0 jump to state 4 with input: 9
from state 1 jump to state 1 with input: 0
from state 1 jump to state 1 with input: 1
from state 1 jump to state 1 with input: 2
from state 1 jump to state 1 with input: 3
from state 1 jump to state 1 with input: 4
from state 1 jump to state 1 with input: 5
from state 1 jump to state 1 with input: 6
from state 1 jump to state 1 with input: 7
from state 1 jump to state 1 with input: 8
from state 1 jump to state 1 with input: 9
from state 2 jump to state 3 with input: .
from state 2 jump to state 5 with input: 0
from state 2 jump to state 5 with input: 1
from state 2 jump to state 5 with input: 2
from state 2 jump to state 5 with input: 3
from state 2 jump to state 5 with input: 4
from state 2 jump to state 5 with input: 5
from state 2 jump to state 5 with input: 6
from state 2 jump to state 5 with input: 7
from state 2 jump to state 5 with input: 8
from state 2 jump to state 5 with input: 9
from state 4 jump to state 3 with input: 0
from state 4 jump to state 3 with input: 1
from state 4 jump to state 3 with input: 2
from state 4 jump to state 3 with input: 3
from state 4 jump to state 3 with input: 4
from state 4 jump to state 3 with input: 5
from state 4 jump to state 3 with input: 6
from state 4 jump to state 3 with input: 7
from state 4 jump to state 3 with input: 8
from state 4 jump to state 3 with input: 9
from state 5 jump to state 2 with input: .
from state 5 jump to state 5 with input: 0
from state 5 jump to state 5 with input: 1
from state 5 jump to state 5 with input: 2
from state 5 jump to state 5 with input: 3
from state 5 jump to state 5 with input: 4
from state 5 jump to state 5 with input: 5
from state 5 jump to state 5 with input: 6
from state 5 jump to state 5 with input: 7
from state 5 jump to state 5 with input: 8
from state 5 jump to state 5 with input: 9

上面输出的跳转表画出来时可能跟我们上面不太一样,不一样的主要是节点的编号,但是节点的跳转结构跟我们在前面的分析完全相符,更详细的讲解和调试演示,请在B站搜索coding迪斯尼。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/384916.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

KDZD土壤电阻率测试仪

一、简介 KDZD土壤电阻率测试仪专为现场测量接地电阻、土壤电阻率、接地电压、交流电压而精心设计制造的&#xff0c;采用新数字及微处理技术&#xff0c;精密4线法、3线法和简易2线法测量接地电阻&#xff0c;导入FFT(快速傅立叶变换)技术、AFC(自动频率控制)技术&#xff0c;…

基于树莓派4B设计的音视频播放器(从0开始)

一、前言 【1】功能总结 选择树莓派设计一款家庭影院系统,可以播放本地视频、网络视频直播、游戏直播、娱乐直播、本地音乐、网络音乐,当做FM网络收音机。 软件采用Qt设计、播放器引擎采用ffmpeg。 当前的硬件选择的是树莓派4B,烧写官方系统,完成最终的开发。 本篇文章主…

Qt QtCreator 安卓开发环境搭建

踩坑 我的qt是使用在线安装工具安装的&#xff0c;Qt版本使用的是5.15.2&#xff0c;QtCreator版本9.0.2 在网上很多教程都是如下步骤 1.安装qt 2.安装jdk 3.安装android-sdk 4.安装android-ndk 5.配置android设置 例如&#xff1a; https://blog.csdn.net/weixin_51363326/…

【p2p】专利:P2p网络中数据传输的方法、电子设备、装置、网络架构

基于混合CDN的低延时直播P2P技术实践 huya 大佬们的讲座。 而且发表了很多专利: 2018年的,点击直接阅读。 本文是学习笔记 本申请公开了P2P网络中数据传输的方法、电子设备、装置、网络架构,该方法包括步骤:接收服务器发送的数据包,所述数据包由共享资源拆分而成,并由服务…

金山轻维表项目进展自动通知

项目经理作为项目全局把控者&#xff0c;经常要和时间“赛跑”。需要实时了解到目前进展如何&#xff0c;跟进人是那些&#xff1f;哪些事项还未完成&#xff1f;项目整体会不会逾期&#xff1f;特别是在一些大型公司中&#xff0c;优秀的项目经理已经学会使用金山轻维表做项目…

05 Android基础--内部存储与外部存储

05 Android基础--内部存储与外部存储什么是内部存储&#xff0c;什么是外部存储&#xff1f;内部存储与外部存储的代码示例什么是内部存储&#xff0c;什么是外部存储&#xff1f; 1.内部存储与外部存储的存储介质&#xff1a; 内部存储的介质&#xff1a;RAM(内存) 内部ROM …

【连接池】什么是HikariCP?HikariCP 解决了哪些问题?为什么要使用 HikariCP?

文章目录什么是连接池什么是HikariCPHikariCP 解决了哪些问题&#xff1f;为什么要使用 HikariCP&#xff1f;HikariCP 的使用Maven支持数据库什么是连接池 数据库连接池负责分配、管理和释放数据库的连接。 数据库连接复用&#xff1a;重复使用现有的数据库长连接&#xff0…

PayPal轮询收款的那些事儿

想必做跨境电商独立站的小伙伴&#xff0c;对于PayPal是再熟悉不过了&#xff0c;PayPal是一个跨国际贸易的支付平台&#xff0c;对于做独立站的朋友来说跨境收款绝大部分都是依赖PayPal以及Stripe条纹了。简单来说PayPal跟国内的支付宝有点类似&#xff0c;但是PayPal它是跨国…

攒了一冬的甜,米易枇杷借力新电商走出川西大山

“绿暗初迎夏&#xff0c;红残不及春。魏花非老伴&#xff0c;卢橘是乡人。”苏轼文中的卢橘&#xff0c;就是枇杷&#xff0c;在苏轼看来&#xff0c;相较于姚黄魏紫&#xff0c;来自故乡四川的枇杷更为亲近。 四川省攀枝花市米易县是全国枇杷早熟产区之一&#xff0c;得益于…

【存储】RAID2.0+、多路径技术、磁盘可靠性技术

RAID2.0RAID 2.0技术RAID技术发展RAID 2.0软件逻辑对象RAID 2.0基本原理硬盘域Storage Pool & TierDisk Group&#xff08;DG&#xff09;LD&#xff08;逻辑磁盘&#xff09;Chunk&#xff08;CK&#xff09;Chunk Group&#xff08;CKG&#xff09;ExtentGrainVolume &am…

米尔Zynq 7000系列单板的FPGA农业生产识别系统

随着农业生产模式和视觉技术的发展&#xff0c;农业采摘机器人的应用已逐渐成为了智慧农业的新趋势&#xff0c;通过机器视觉技术对农作物进行自动检测和识别已成为采摘机器人设计的关键技术之一&#xff0c;这决定了机器人的采摘效果和农场的经济效率。目前市面上最常见的是基…

MATLAB-Scatter3-三维散点图投影至XYZ三个平面

MATLAB-Scatter3函数可以绘制立体的三维散点图&#xff0c;但有时候需要在该立体图中分析X-Y-Z三者的关系&#xff0c;即1副图呈现出4个信息&#xff0c;XYZ综合信息、XY信息、XZ信息、YZ信息。现有的Scatter3无法实现该功能&#xff0c;本文可实现Scatter3三维立体散点图在三个…

纯手动搭建大数据集群架构_记录011_搭建Nifi_安装部署_搭建集群---大数据之Hadoop3.x工作笔记0172

可以看到左侧,把nifi安装包先上传到服务器,然后,去解压,一样放到opt/software目录,然后解压到/opt/module目录 然后去修改这个配置文件nifi.properties,然后 然后nifi.web.http.port=58080 这里只把 nifi.web.http.port=8080 这个端口改成 58080就可以了. 然后我们进入nifi的bi…

《计算机系统基础》——数据的表示

文章目录《计算机系统基础》——数据的表示移码整数无符号整数 (Unsigned integer)带符号整数&#xff08;Signed integer&#xff09;测试代码浮点数表示范围IEEE 754标准例子规格化数0∞/-∞非数非规格数《计算机系统基础》——数据的表示 移码 &#x1f680;&#x1f680;…

2023年中国人工智能产业趋势报告

易观&#xff1a;尽管2022年人工智能市场发展活跃度不及预期&#xff0c;但2022年对人工智能产业来说无疑是令人激动的一年。年中由DALL-E 2以及其后Stable Diffusion和Midjourney等文本-图像生成模型引起公众对人工智能生成内容的大量关注&#xff0c;年末ChatGPT的横空出世刷…

硬件系统工程师宝典(13)-----PCB的布局“有讲究”

各位同学大家好&#xff0c;欢迎继续做客电子工程学习圈&#xff0c;今天我们继续来讲这本书&#xff0c;硬件系统工程师宝典。上篇我们说到EMC的标准以及提高EMC性能的一些常用方法。今天我们来看看PCB上模块的布局有什么讲究。 模块划分及布局 PCB上模块的划分和布局会影响到…

ATTCK v12版本战术实战研究—持久化(二)

一、前言前几期文章中&#xff0c;我们介绍了ATT&CK中侦察、资源开发、初始访问、执行战术、持久化战术的知识。那么从前文中介绍的相关持久化子技术来开展测试&#xff0c;进行更深一步的分析。本文主要内容是介绍攻击者在运用持久化子技术时&#xff0c;在相关的资产服务…

如何使用固态继电器为恒温器供电

恒温器有两种电源&#xff1a;电池和 24VAC。恒温器需要电池才能不间断地运行。电池消耗的能量尽可能低非常重要&#xff0c;但即使您最大限度地减少消耗&#xff0c;这仍然不是一个用户友好的选择&#xff0c;因为电池会不时需要更换。要降低更换频率&#xff0c;可以使用 24V…

Mysql InnoDB 存储引擎笔记

1 存储引擎 简介 Mysql 存储引擎有多种&#xff1a;包括 MyISAM、InnoDB 和 Memory。 其中MyISAM 和 INNODB 的区别&#xff1a; 事务安全&#xff08;MyISAM不支持事务&#xff0c;INNODB支持事务&#xff09;&#xff1b;外键 MyISAM 不支持外键&#xff0c; INNODB支持外…

渗透测试之地基服务篇:无线攻防之Kali自搭建钓鱼Wifi

简介 渗透测试-地基篇 该篇章目的是重新牢固地基&#xff0c;加强每日训练操作的笔记&#xff0c;在记录地基笔记中会有很多跳跃性思维的操作和方式方法&#xff0c;望大家能共同加油学到东西。 请注意 &#xff1a; 本文仅用于技术讨论与研究&#xff0c;对于所有笔记中复现…