【编译原理实验】Lab2(一)NFA确定化

一、实验目的

学习和掌握将NFA转为DFA的子集构造法。

二、实验任务

(1)存储NFA与DFA;
(2)编程实现子集构造法将NFA转换成DFA。

三、实验内容

在这里插入图片描述

四、实验步骤

确定如何存NFA

一个NFA如图所示
在这里插入图片描述
将NFA存在一个文件中,上图NFA表示信息如下

3	//字符数
a b c	//字符集
10	//状态数,0 - n-1
0	//开始状态
9	//结束状态
12	//边数
0 a 1	//边的格式:起始状态 边上的字符 终结状态
1 # 2	//#表示空字符边
2 # 3
2 # 9
3 # 4
3 # 6
4 b 5
6 c 7
5 # 8
7 # 8
8 # 9
8 # 3

子集构造法-算法思想

NFA为什么要转DFA?
需要查看所有可能的状态转移结果,需要大量遍历、回溯
在这里插入图片描述
在这里插入图片描述
算法伪代码:
在这里插入图片描述

如何存储一个状态集?

set是一个不错的方法,因为还可以保证状态集中无重复状态,但是求状态集的并很麻烦(如果用并查集,用数组存状态集也很方便)
vector,一维数组都很方便。

但是以上方法都是用一个容器来存,每个状态至少得用一个整数去表示,空间消耗还是较大的。
于是按照自己定义的NFA的状态是从0 - n-1一个序列。考虑用一个int整数来存一个状态集。如果用int型最多可表示32位,如果需要更多状态,用long int或者数组。
int型变量占4字节,也就是32个二进制位,给二进制位编号0-31(从低位到高位)。如果第x位为1,则表示该状态集中包含了标号为x的状态。
举个例子:
上面图中NFA,状态0的闭包就是1(10进制)
状态1的闭包是606(10进制)
606转二进制 10 0101 1110
从右往左从0开始数,第1位,第2位,第3位,第4位,第6位,第9位为1
因此表示状态集{s1,s2,s3,s4,s6,s9}。

接下来就需要用到一些位运算符(|,&,<<,>>)

如何求状态集的并集?
把两个int型整数按位或 |即可。

int x;
int y;
x|=y;

如何把一个状态加入状态集?
等价于如何把某二进制位置为1

int s;//表示一个现有状态集
int x;//需要把x加入状态集
s |= (1<<x)

如何遍历状态集?
等价于遍历二进制位

int x;
for(int i=0;i < 32;i++){
	printf("%d",(x>>i)&1)
}

代码具体实现

#include <iostream>
#include <fstream>
#include <queue>
#include <cstring>
#include <map>
using namespace std;
struct Node{//DFA节点
	int s;
	bool flag;//标记一个DFA节点集合是否包含NFA结束态,即表示DFA中是否是一个结束态 
	Node(int ss,bool f){
		s = ss;
		flag = f;
	}
};
struct Edge{//DFA边 
	int from,to;
	char c;
	Edge(int x,int y,char z){
		from = x;
		to = y;
		c = z;
	}
};
const int MAX = 101;
int zf;//字符数 
char zfj[MAX];//字符集 
int zt;//状态数,0开始 
int Begin,End;//起始和结束态
char G[MAX][MAX]; //存边 
int vis[MAX];//标记NFA状态是否被访问,求闭包用到 
vector<Node> V;//DFA节点集 
vector<Edge> edge;//DFA边集 
int eps_clos[MAX];//求过的闭包保存以后用 
queue<int> eq;
int E_closure(int x)//求eps闭包 
{
	if(eps_clos[x]!=-1) 
	return eps_clos[x];
	queue<int> q;
	memset(vis,0,sizeof(vis));
	int S = 0;
	S = S | (1<<x);
	q.push(x);
	while(!q.empty()){//BFS求闭包 
		int v = q.front();
		q.pop();
		for(int w = 0;w < zt;w++){
			if(G[v][w]=='#'&&!vis[w]){
				vis[w] = 1;
				S |= (1 << w);
				q.push(w);
			}		
		}
	}
	eps_clos[x] = S;
	return S;
}
int set_edge(int s,char c)//求一个状态集吃字符到达的状态集 
{
	int i,j;
	int S = 0;
	for(i = 0;i < zt;i++){
		if((s>>i)&1){
			for(j = 0;j < zt;j++){
				if(G[i][j]==c)
				S |= E_closure(j);
			}
		}
	}
	return S;
}
bool check(int s)//检查DFA节点集是否出现过 
{
	for(int i = 0;i < V.size();i++){
		if(V[i].s == s) return true;
	}
	return false;
}
bool is_end(int s)//状态集是否包含终结点 
{
	return (s>>End) & 1;
}
void ZJGZ()//子集构造算法 
{
	int i;
	queue<int> work;
	work.push(E_closure(0));
	V.push_back(Node(E_closure(0),is_end(E_closure(0))));//加入DFA节点集 
	while(!work.empty()){
		int v = work.front();
		work.pop();
		for(i = 0;i < zf;i++){//遍历字符集 
			int s = set_edge(v,zfj[i]);//生成NFA吃完该字符所能到达的所有状态 
			if(s!=0){
				edge.push_back(Edge(v,s,zfj[i]));
				if(!check(s)){
					V.push_back(Node(s,is_end(s)));
					work.push(s);
				}
			} 	
		}
	}
}

void out_put(int i,int s,bool f)
{
	printf("DFA状态:q%d 包含的NFA中的状态为:",i) ;
	for(int j = 0;j < zt;j++)
		if((s>>j)&1) printf("s%d ",j); 
	if(f) printf("包含结束态\n");
	else printf("不包含结束态\n"); 	
} 
int main()
{
	memset(eps_clos,-1,sizeof(eps_clos));
	ifstream in("nfa_3.txt");
	ofstream out("dfa_3.txt");
	in >> zf;
	for(int i = 0;i < zf;i++)
		in >> zfj[i];
	in >> zt;
	in >> Begin >> End;
	int n;//NFA边数 
	in >> n;
	int x,y;
	char c;
	for(int i = 0;i < n;i++){
		in >> x >> c >> y;
		G[x][y] = c;
	}
	in.close();
	ZJGZ();
	
	out << zf << endl; 
	for(int i = 0;i < zf;i++)
	out << zfj[i] << ' ';
	out << endl;
	map<int,int> mp;//DFA状态集映射到从0开始的状态标号 
	out << V.size() << endl;
	for(int i = 0;i < V.size();i++){
		out << i << ' '<<V[i].flag << endl;
		mp[V[i].s] = i;
	}
	out << edge.size() << endl;
	for(int i = 0;i < V.size();i++)
		out_put(i,V[i].s,V[i].flag);
	for(int i = 0;i < edge.size();i++){
		out << mp[edge[i].from] << ' ' << mp[edge[i].to] << ' ' << edge[i].c << endl;
		printf("状态q%d -> 状态q%d,吃入字符%c\n",mp[edge[i].from],mp[edge[i].to],edge[i].c);
	}
	out.close();
}
  

运行结果

在这里插入图片描述在这里插入图片描述

  • 10
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值