NFA转换为DFA及DFA的化简
实验三(一)NFA DFA(2小时)
实验目的:学会编程实现子集构造法
实验任务:存储NFA与DFA,编程实现子集构造法将NFA转换成DFA
实验内容:
(1)确定NFA与DFA的存储格式,为3个以上测试NFA准备好存储文件。
NFA的存储格式:
3 //转换边数
0 // 开始状态
3 -1 //结束状态集,以-1为结束标志
0 1 a //状态转换
0 2 a //如状态0在a输入下转换为状态2
2 3 b //其中用#表示空转换
如测试用例:
(a)以a开头和结尾的小字字母串;a (a|b|…|z)*a | a
(b)不包含三个连续的b的,由字母a与b组成的字符串;( | b | bb) (a | ab | abb)*
(c) (aa|b)*(a|bb)*
(2)用C或JA V A语言编写将NFA转换成DFA的子集构造法的程序。子集构造法原理:
子集构造法主要代码:
子集构造函数:
void child_method()
{
int m,n;
for(m=0;m<100;m++)
for(n=0;n<100;n++)
Dtran[m][n]='#';
for(m=0;m<100;m++)
DFA[m].flag=-1;
State S0,U;
S0.flag=0;
S0.count=1;
S0.H[0]=first;
State T;
T=closure(S0);
T.mark=0;
T.flag=0;
DFA[numof_Dtran++]=T;
memset(useof_DFA,0,sizeof(useof_DFA));
//检查DFA中是否存在未被标记的状态
int j=check_inDFA();
int k;
while(j!=-1)
{
useof_DFA[j]=1;
for(k=0;k { U=closure(move(DFA[j],alpha[k])); //求闭包 //if U不在DFA中 if(!check_whetherin_DFA(U)) //判断是否在DFA中 { U.mark=numof_Dtran; DFA[numof_Dtran++]=U; } Dtran[DFA[j].mark][U.mark]=alpha[k]; } j=check_inDFA();//检查DFA中是否存在未被标记的状态,有返回标号,否则返回-1 } } 闭包函数: State closure(State T)//求闭包 { stack State temp; int i,j,k; for(i=0;i { STACK.push(T.H[i]); temp.H[i]=T.H[i]; } temp.count=T.count; /*temp.flag=0;*/ temp.mark=T.mark; while(!STACK.empty()) { int t=STACK.top(); STACK.pop(); //搜索状态t通过一个或多个空字符到达的状态int search_result[100]; int num; arriveBynone(t,search_result,num); for(j=0;j { if(!check(search_result[j],temp)) { temp.H[temp.count++]=search_result[j]; STACK.push(search_result[j]); } } } for(k=0;k { if(f[temp.H[k]]==1) { temp.flag=1; break; } if(f[temp.H[k]]==0) { temp.flag=0; break; } } sort(temp.H,temp.H+temp.count); for(i=0;i { if(temp.count!=DFA[i].count) continue; sort(DFA[i].H,DFA[i].H+DFA[i].count); for(j=0;j { if(DFA[i].H[j]!=temp.H[j]) break; } if(j>=DFA[i].count) temp.mark=DFA[i].mark; } return temp; } 检查未标记状态函数: int check_inDFA() //检查DFA中是否存在未被标记的状态,有则返回标号,否则返回-1 { int i; for(i=0;i { if(!useof_DFA[i]) return i; } return -1; } 检查一个状态是否在DFA中的函数: bool check_whetherin_DFA(State T) { int i,j; sort(T.H,T.H+T.count); for(i=0;i { if(T.count!=DFA[i].count) continue; sort(DFA[i].H,DFA[i].H+DFA[i].count); for(j=0;j { if(DFA[i].H[j]!=T.H[j]) break; } if(j>=DFA[i].count) return true; } if(i>=numof_Dtran) return false; else return true; } (3)测试。 利用步骤1中所给的测试用例来验证。 a)以a开头和结尾的小字字母串;a (a|b|…|z)*a | a 运行结果及分析: 结果分析: 首先将读入的NFA信息输出: 然后将转换后的DFA输出,其中2号状态表示转换后的?状态,即无效状态。 状态解释: 0:1表示用0状态表示NFA中的1状态 1:2 3 表示用1状态表示{2,3}状态集合。 2:表示用2状态表示?状态,即无效状态。 3:2 表示用3状态表示NFA中的状态2。 最后,判断字符串是否能由此DFA得到: 显然,从以上三个例子可以看出,以a开头且以a结尾的字符串可以由该DFA得到而以b开头或结尾的字符串则无法由该DFA得到。所以得到的DFA正确。 b)不包含三个连续的b的,由字母a与b组成的字符串;( | b | bb) (a | ab | abb)* 同样可以得到结果及字符串的判断: 显然,得到的DFA可以接受连续最多2个b,到3个就不能接受,与NFA一样,正确。 c)(c) (aa|b)*(a|bb)* 显然,验证结果均正确。 实验三(二)DFA化简(2小时) 实验目的:学会编程实现等价划分法化简DFA。 实验任务:先完善DFA,再化简DFA。 实验内容: (1)准备3个以上测试DFA文件。 测试文件格式即DFA存储格式说明: 2 //字符个数 a b //字符 3 //非终结状态个数 1 //终止状态个数 0 1 2 //非终止状态 3 //终止状态 1 2 //状态转换表 0 2 //如第二行表示状态1在a输入下转换成状态0,在b输入下转换成状态2 0 3 3 2 测试文件1: 测试文件2: 测试文件3: (2)用C或JA V A语言编写用等价划分法化简DFA的程序。 DFA最小化原理: ●确定型有穷自动机(DFA): 有有穷状态集合和输入符号集合,转移函数,初始状态,以及一个终结状态集合。表示 为。DFA的状态图:用箭头来表示状态间的转换,用圆圈+文字表示状态,用一个同心圆表示终结状态,用start表示开始状态。状态表:可以想象。 ●DFA状态最小化: 最小有限自动机,是指满足下述条件的确定有限自动机: ⑴没有无用状态(无用状态已删除);⑵没有等价状态(等价状态已合并)。 Ⅰ.删除无用状态算法 无用状态是指自动机从开始态出发,对任何符号串都不能到达的状态。 判别算法:构造有用状态集Qus ⑴设q0 为开始态,则令q0∈Qus ; ⑵若qi∈Qus 且有d(qi,a)= qj 则令qj∈Qus ; ⑶重复执行⑵,直到Qus不再增大为止。 ⑷从状态集Q中,删除不在Qus中的所有状态。 Ⅱ. 合并等价状态算法 等价状态: 两个状态i,j等价,当且仅当满足下面两个条件: ①必须同是结束态,或同不是结束态; ②对所有字母表上符号,状态i,j必变换到等价状态。 划分不等价状态集 ⑴初始,把状态集Q化分成两个不等价子集: Q1(结束状态集),Q2(非结束状态集); ⑵把每个Qi再划分成不同的子集,条件是: 对同一Qi中两个状态i和j,若对字母表中的某个符号,变换到已划分的不同的状态集中,则i和j应分离: 如d(i,a)∈Qm , d(j,a)∈Qn 且m≠n ⑶重复步骤⑵,直到再不能划分为止; ⑷合并最终划分的每个子集中的各状态(合而为一)。 部分函数代码: 判断等价状态: while( endflag ) { int flag; for(i=0;i<(temp+1);i++) //对每个等价类集合 for(int j=0;j { flag=0; if(sta[j].lastf==i) { for(int k=0;k /若是在同一个等价类,继续测试 if(sta[k].lastf==sta[j].lastf&&sta[k].newf==sta[j].newf) / for(int t=0;t { int s1,s2; s1=move[j][t]; s2=move[k][t]; if(sta[s2].lastf!=sta[s1].lastf)//出现分歧 { sta[k].newf=f+1; flag=1; break; } } } if(flag==1) f++; } for(i=0;i if(sta[i].lastf==sta[i].newf) ; else break; if(i==n) //若所有的状态等价类编号不再变化,退出while循环endflag=0; for(i=0;i sta[i].lastf=sta[i].newf; temp=f; } //循环结束 判断是否存在不可达状态: for(i=0;i sta[s0].kd=1; for(int j=0;j { sta[move[s0][i]].kd=1; s0=move[s0][i]; } } int unreach=0; for(i=0;i if(sta[i].kd==1) ; else//contain unreachble state { unreach=1; cout<<"new state "< for (int j=0;j move[i][j]=-1; } (3)测试 测试1: 输入文件已在步骤1中进行过说明。 运行结果及分析: 从输入文件和状态转换图可以看出状态0和状态2是等价状态 可以看出程序将0和2号状态合并,可是建立新的状态转移的时候好像有点问题,没有解决。 测试2:(测试文件格式在步骤1中) 测试3:(测试文件格式在步骤1中) 经验证,状态分类是正确的,最小化之后的状态转移有点不对。 实验心得: 通过本次实验,学会了如何将一个非确定型状态机转换成确定型状态机以及如何将一个确定型的有穷自动机化简成一个状态最小的有穷自动机。更加深刻地理解了有穷自动机、等价状态等概念,同时也遇到了很多困难,例如,如何将一个自动机用C++语言来表示,如何将一个图形改成抽象的语言描述等。总之,对课本的知识有了进一步的理解,收获颇多。 附:两个实验的代码+工程 // NFA转为DFA.cpp : 定义控制台应用程序的入口点。 #include "stdafx.h" #include #include #include #include #include #include #include using namespace std; struct edge{ int start,end; char c; }E[100],Ekong[100];//E保存所有的边,Ekong保存转换字符为空的边struct State{ int H[100];//状态集合 int count;//状态集合中的元素个数 int flag;//是否是接受状态 int mark;//状态编号 }; int n;//n:边数 int nk=0;//空字符转换的边数 int first,accept;//开始状态,接受状态 char alpha[100];//输入字母表,#代表空串 int numof_char=0;//字母表中的字符个数 int useof_char[256];//该转换字符是否用过 int f[200];//状态属性标志:0表示始态,1表示接受态,-1表示中间态State DFA[100];//DFA状态集 int useof_DFA[100];//标志构造出来的状态是否已存在 int numof_Dtran=0;//最后得到的DFA中的状态数 char Dtran[100][100];//DFA状态转换表 void input() { ifstream fin("input3.txt",ios::in); int i,s,e; char ch; fin>>n; cout<<"此状态转换有"< fin>>first; cout<<"开始状态为:"< memset(f,-1,sizeof(f)); memset(useof_char,0,sizeof(useof_char)); f[first]=0; cout<<"接受状态集:"; //cin>>accept; fin>>accept; while(accept!=-1) { f[accept]=1; cout< fin>>accept; } cout<<"\n\n起点,终点,转换字符('#'表示空字符):"< int k=0; for(i=0;i { fin>>s>>e>>ch; cout< E[i].start=s; E[i].end=e; E[i].c=ch; if(ch!='#'&&!useof_char[ch]) { alpha[numof_char++]=ch; useof_char[ch]=1; } if(ch=='#') { Ekong[nk].start=s; Ekong[nk].end=e; Ekong[nk].c=ch; nk++; } } } State move(State T,char s)//c!='#' { State temp; temp.count=0; /*temp.flag=0;*/ temp.mark=T.mark; int i,j=0,k=0; for(i=0;i { j=0; while(j { if(E[j].start==T.H[i]&&E[j].c==s) { temp.H[temp.count++]=E[j].end; } j++; } } return temp; } void arriveBynone(int t,int result[],int& num)//搜索状态t通过一个或多个空字符到达的状态,结果存在result中 { int k=0; int m=0; num=0; stack S.push(t); int j; while(!S.empty()) { j=S.top(); S.pop(); m=0; while(m { if(Ekong[m].start==j) { result[num++]=Ekong[m].end; S.push(Ekong[m].end); } m++; } } } bool check(int i,State T)//判断状态i是否在T中 { int j; for(j=0;j { if(T.H[j]==i) return true; } return false; } State closure(State T)//求闭包 { stack State temp; int i,j,k; for(i=0;i { STACK.push(T.H[i]); temp.H[i]=T.H[i]; } temp.count=T.count; /*temp.flag=0;*/ temp.mark=T.mark; while(!STACK.empty()) { int t=STACK.top(); STACK.pop(); //搜索状态t通过一个或多个空字符到达的状态 int search_result[100]; int num; arriveBynone(t,search_result,num); for(j=0;j { if(!check(search_result[j],temp)) { temp.H[temp.count++]=search_result[j]; STACK.push(search_result[j]); } } } for(k=0;k { if(f[temp.H[k]]==1) { temp.flag=1; break; } if(f[temp.H[k]]==0) { temp.flag=0; break; } sort(temp.H,temp.H+temp.count); for(i=0;i { if(temp.count!=DFA[i].count) continue; sort(DFA[i].H,DFA[i].H+DFA[i].count); for(j=0;j { if(DFA[i].H[j]!=temp.H[j]) break; } if(j>=DFA[i].count) temp.mark=DFA[i].mark; } return temp; } int check_inDFA() //检查DFA中是否存在未被标记的状态,有则返回标号,否则返回-1 { int i; for(i=0;i { if(!useof_DFA[i]) return i; } return -1; } bool check_whetherin_DFA(State T) { int i,j; sort(T.H,T.H+T.count); for(i=0;i { if(T.count!=DFA[i].count) continue; sort(DFA[i].H,DFA[i].H+DFA[i].count); for(j=0;j { if(DFA[i].H[j]!=T.H[j]) break; } if(j>=DFA[i].count) return true;