一、实验目的
学习和掌握将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();
}