博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
编译原理实验二(NFA转DFA、DFA最小化)
阅读量:799 次
发布时间:2019-03-25

本文共 9722 字,大约阅读时间需要 32 分钟。

       

目录

  

  

  


(一)NFA转DFA(2小时)

  • 实验目的

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

  • 实验任务

(1)存储NFA与DFA;

(2)编程实现子集构造法将NFA转换成DFA。

  • 实验内容

(1)确定NFA与DFA的存储格式。要求为3个以上测试NFA准备好相应有限自动机的存储文件。

(2)用C或C++语言编写将NFA转换成DFA的子集构造法的程序。

(3)测试验证程序的正确性。

测试不易。可求出NFA与DFA的语言集合的某个子集(如长度小于某个N),再证实两个语言集合完全相同!(

(4)测试用例参考:将下列语言用RE表示,再转换成NFA使用:

(a) 以a开头和结尾的小字字母串;a (a|b|…|z)*a | a

(b) 不包含三个连续的b的,由字母a与b组成的字符串;(e | b | bb) (a | ab | abb)*

(c) (aa|b)*(a|bb)

1.思路:

  • 开始状态S,求出I=ε-closure({S})
  • 求出Ia与Ib
  • 对未分析过的集合Ia和Ib重复第二步,直到所有的Ia 和Ib都已经被分析过
  • 对所有的I、Ia、Ib编号,成为要求的DFA中的状态
  • 根据状态转换表构造DFA图中的转换关系

2.数据处理:

字符:

数组 letters[n](一个下标对应一个字符)

状态:

编号 (一个整数对应一个状态 )

状态集合:

数表示,表示成二进制的形式 a1,a2,a3……an(2) 其中ai=1表示第i个状态包含在 集合中。

状态之间的转换关系:

二维数组int trans_state[n][n]   ( int S = trans_state[i][j] 表示状态i输入编号为j的字 符后转换到状态S,若S=-1表示输入字符j不进行转换

要处理的计算出的新集合:

set<int> newS存储,这样就不会多次处理相同的集合

char letters[n] : n个字符Int s : nfa_s,dfa_s个状态; int nfa_S[nfa_s],dfa_S[dfa_s]是状态转换图的状态集合int nfa_stateTrans[n][n]:状态转换关系set
dfa_setS: 子集法中计算出的集合,对应DFA中的状态

 3.方法:

InputNFA(string path) :: void  从路径path下的文件输入NFANFA2DFA() :: void 将NFA转换为DFAgetIa(int I, int a) :: int 求集合Ia,即在集合I的状态中输入字符a能到达的状态的集合的ε闭包getClosue(int I) :: int 求出集合I的ε闭包

4.NFA2DFA算法的步骤:

①求出初始状态集合的ε闭包,并加入newS

②对newS中的下一个集合I进行处理:计算Ic并加入newS (c是字符集中的字符,j是字符的编号)并且new_StateTrans[i][j] = Ic

③重复2步骤,当处理到newS的最后一个元素时,结束。

④处理new_StateTrans数组,使得dfa_stateTrans[i][j]表示状态i输入字符j后到达的状态编号。(此处需要一个获得状态编号的函数int getStateId(int S)

5.遇到的问题:

  • set中元素会自动排序,遍历时会出现问题,因此可以用set来保存一份无重复状态的集合,然后同时存入数组,进行遍历
  • 计算Ia时: 注意那些转换状态值为-1的状态,表示在原状态不发出标记该字符的边,不应该作为一个有效状态加入Ia中
  • 注意在NFA2DFA中,计算得到的Ia=0时表示为空集合,则写入转换矩阵中相应位置的值是-1,这里的处理是:用getStateId从new_state里面找到状态的索引,如果没找到就返回-1

6.测试: 

 

2a b41 2 3 41133 2 1-1 1 -1-1 3 4-1 -1 3

 结果:

7.主要代码:

struct FA{    int nfa;//!是否为NFA    int letter_num,snum;//!字符个数,状态个数#    int starts,ends;//!开始状态集合,终止状态集合;    char letters[MAX_LETTER_NUM];//!存储字符#    int state[MAX_STATE_NUM];//!存储状态编号    int stateTrans[MAX_STATE_NUM][MAX_LETTER_NUM];//!状态转换矩阵};
/**将NFA转换为DFA*/FA NFA2DFA(FA nfa){    FA dfa;    std::set 
dfa_setS;//!用来存储新得到的集合,去除重复元素 int new_state[MAX_STATE_NUM]; int I = nfa.starts; int ends = 0,temp; int s=1,index=0;//!状态数目和此时处理的集合索引 I = getClosure(nfa,I);//!第一个集合 dfa_setS.insert(I); new_state[0] = I; if(I&(nfa.ends)){//!如果该集合包含原NFA的结束状态,则也为DFA的结束状态 ends |= 1<

(二)DFA最小化(2小时)

  • 实验目的

学会编程实现等价划分法最小化DFA。

  • 实验任务

先完善DFA,再最小化DFA。

  • 实验内容

(1)准备3个以上测试DFA文件。(提示:其中一定要有没有最小化的DFA)

(2)用C或C++语言编写用等价划分法最小化DFA的程序。

(3)经测试无误。测试不易。可求出两个DFA的语言集合的某个子集(如长度小于某个N),再证实两个语言集合完全相同!

1.思路: 

 

2.数据处理:

3.方法:

inputDFA(char* path) :: void 从文件输入DFAjudgeIc(int I,vector
PP) :: int 计算集合I中状态属于PP中的不同子集个数getContainerId (int s, vector
PP) :: int 计算状态s包含在PP中的子集的索引getSimplifiedTrans( int S, int c, vector
PP ) :: int 计算状态集合S上输入字符c后到达的状态集合的索引

4.simplifyDFA的思路:

5.测试:

2a b60 1 2 3 4 50 20 1-1 1 2 -1 1 4-1 1 3 -1 3 2-1 0 5-1 5 4

结果:

6.主要代码:

 

 

/**DFA最小化算法主体*/FA DFA_simplify(FA dfa){    std::vector 
PP; //!首先分为终态和非终态两个集合 int temp = (1<<(dfa.snum)) -1;//!状态的全集 PP.push_back(dfa.ends); PP.push_back(temp - dfa.ends); int kState[MAX_STATE_NUM]; int I,c,Ic,k,x,mark;//!子集,字符,Ic,Ic属于PP中k个不同子集,mark表示是否删除了子集I for( int eraseCnt = 0; eraseCnt
1){//!k>1需要划分子集I for(int j=1; j<=dfa.snum; j++){//!遍历I的每一个状态 temp = 1<<(dfa.snum -j); if(temp&I){//!子集I包含第j个状态 x = getSimplifiedTrans(dfa,temp,i,PP);//!记录第j个状态在输入c后到达的状态s’属于PP的第x+1 (0~n-1)个子集 kState[x] |= temp;//!kState中添加第j个状态 } } PP.erase(PP.begin()+eraseCnt);//!擦除PP中的子集I! mark = 1;//!标记 int cnt = 0;//!计数 int j=0; while(cnt

 

 

结果检验:

主要代码:

运行结果:

实验心得

  • 通过这次实验,我更加注重写代码前的思路整理过程了,在拿到实验题目后,我首先开始回顾相关的知识点,思考方法的封装、数据结构的使用等等,并将思考的过程写在报告里。接下来我便对着之前的思路,进行代码的编写,逐个测试函数的正确性,并在编写代码的过程中记录发现的问题,回过头去思考和修改之前的架构。最后再组装进行案例测试和代码的调试。
  • 同时,我也吃了一大亏,一些细节方面的处理,比如状态转换矩阵DFA和NFA的结构没有统一,导致重用函数后发生逻辑错误,最后才回过头来更改相应的数据结构和引用的代码,导致工作量非常的大!
  • 另外在实验调试阶段,我通过打印算法执行过程中的关键数据,并结合案例一步一步地分析,对比实际地数据和输出地数据来寻找代码的bug,虽然效率不太高但是也算是对算法流程的又一次熟悉。
  • 总之,通过这次试验,我不仅提高了写代码的能力,也巩固了学习到的知识点,收获良多。

源码:

#include 
#include
#include
#include
#include
#include
#define MAX_LETTER_NUM 256#define MAX_STATE_NUM 100#define MAX_STRING_LENGTH 256#define MAX_STRING_NUM 10000/************************数据结构*****************************//*****************************************************************/struct FA{ int nfa;//!是否为NFA int letter_num,snum;//!字符个数,状态个数# int starts,ends;//!开始状态集合,终止状态集合; char letters[MAX_LETTER_NUM];//!存储字符# int state[MAX_STATE_NUM];//!存储状态编号 int stateTrans[MAX_STATE_NUM][MAX_LETTER_NUM];//!状态转换矩阵};/************************公用方法*****************************//*****************************************************************//**!打印转换矩阵*/void printStateTrans(int snum,int num_letter,int stateTrans[MAX_STATE_NUM][MAX_LETTER_NUM]){ printf("Print state transform chart:\n"); int num_s = snum,num_l,j; int i=0; while(num_s>0){ num_l=num_letter;j=0; while(num_l>0){ printf("%d ",stateTrans[i][j++]); num_l--; } printf("\n"); num_s--;i++; } printf("\n\n");}/**计算状态集合I的空闭包E-closure*/int getClosure(FA fa,int I){ int I_new = I;//!新的集合 int num = fa.snum;//!状态个数 int i=0,exist,trans; while(i
0){//!该集合中有第i个状态 trans = fa.stateTrans[i][0];//!输入E后到达的状态trans加入集合I_new if(trans!=-1){ I_new |= (1<<(num-trans-1)); while(fa.stateTrans[trans-1][0] != -1){//!新进来的状态trans输入E后到达的状态也加入I_new trans = fa.stateTrans[trans-1][0]; I_new |= (1<<(num-trans-1)); } } } i++; } return I_new;}/**得到编号为id的状态在字符集合中的索引*/int getStateIndex(int state[MAX_STATE_NUM],int snum,int id){ for(int i=0; i
0){//!该集合中有第i个状态 trans = fa.stateTrans[i][a]; if(trans!=-1)//!输入a后到达的状态trans加入集合Ia Ia |= (1<<(num-trans-1)); } i++; } Ia = getClosure(fa,Ia); return Ia;}/**输入FA的信息,并构造状态转换矩阵*/FA inputFA(char* path){ FILE* fp = fopen(path,"r"); if(fp == NULL){ perror("打开FA文件失败啦"); exit(1); } FA fa;//!建立一个FA结构体对象 int i,num,temp; //!字符输入 fscanf(fp,"%d",&num);//!字符个数 fgetc(fp);//!换行符 fa.letter_num = num; i=0; while(num>0){ fscanf(fp,"%c",&(fa.letters[i++])); fgetc(fp);//!空格 num--; } //!状态输入 fscanf(fp,"%d",&num);//!状态个数 fa.snum = num; i=0; while(num>0){ fscanf(fp,"%d",&(fa.state[i++])); num--; } //!开始状态和结束状态信息 fscanf(fp,"%d",&num);//!开始状态的编号 i= getStateIndex(fa.state,fa.snum,num);//!开始状态在字符集中的位置 fa.starts = 1<<(fa.snum - i -1); fscanf(fp,"%d",&num);//!结束状态个数 fa.ends = 0;i=0; while(num>0){ fscanf(fp,"%d",&temp); i = getStateIndex(fa.state,fa.snum,temp); (fa.ends) |= (1<<(fa.snum-i-1)); num--; } //!转换图的输入(注意最左边一列是输入空字符的列) int num_s = fa.snum,num_l,j; i=0; while(num_s>0){ num_l=fa.letter_num+1;j=0; while(num_l>0){ fscanf(fp,"%d",&num); temp = getStateIndex(fa.state,fa.snum,num); fa.stateTrans[i][j++] = temp; num_l--; } num_s--;i++; } return fa;}/************************NFA转换为DFA*****************************//*****************************************************************//**求集合I在set
dfa_setS中的索引,即编号*/int getStateId(int I, int s, int state[MAX_STATE_NUM]){ for(int i=0;i
dfa_setS;//!用来存储新得到的集合,去除重复元素 int new_state[MAX_STATE_NUM]; int I = nfa.starts; int ends = 0,temp; int s=1,index=0;//!状态数目和此时处理的集合索引 I = getClosure(nfa,I);//!第一个集合 dfa_setS.insert(I); new_state[0] = I; if(I&(nfa.ends)){//!如果该集合包含原NFA的结束状态,则也为DFA的结束状态 ends |= 1<
PP){ int cnt = 0,states; for(std::vector
::iterator it = PP.begin(); it!=PP.end(); it++){ states = *it; if(I&states) cnt++; } return cnt;}/**计算状态s包含在PP中的子集的索引*/int getContainerId (int s, std::vector
PP){ int index = 0,states; for(std::vector
::iterator it = PP.begin(); it!=PP.end(); it++){ states = *it; if(s&states) return index; index++; } return -1;}/**计算状态集合S上输入字符c后到达的PP中子集的索引*/int getSimplifiedTrans(FA dfa,int S, int c, std::vector
PP ){ int index = 0,trans,temp; int snum = dfa.snum; for(int i=1; i<=snum; i++){ temp = 1<<(snum-i);//!第i个状态 if(S&temp){//!状态集合S中包含第i个状态 trans = dfa.stateTrans[i-1][c]; if(trans!=-1){//!第i个状态输入字符c后达到状态trans trans = 1<<(snum-trans-1);//!状态编号转换为状态集合形式 return getContainerId(trans,PP); } } } return -1;}/**DFA最小化算法主体*/FA DFA_simplify(FA dfa){ std::vector
PP; //!首先分为终态和非终态两个集合 int temp = (1<<(dfa.snum)) -1;//!状态的全集 PP.push_back(dfa.ends); PP.push_back(temp - dfa.ends); int kState[MAX_STATE_NUM]; int I,c,Ic,k,x,mark;//!子集,字符,Ic,Ic属于PP中k个不同子集,mark表示是否删除了子集I for( int eraseCnt = 0; eraseCnt
1){//!k>1需要划分子集I for(int j=1; j<=dfa.snum; j++){//!遍历I的每一个状态 temp = 1<<(dfa.snum -j); if(temp&I){//!子集I包含第j个状态 x = getSimplifiedTrans(dfa,temp,i,PP);//!记录第j个状态在输入c后到达的状态s’属于PP的第x+1 (0~n-1)个子集 kState[x] |= temp;//!kState中添加第j个状态 } } PP.erase(PP.begin()+eraseCnt);//!擦除PP中的子集I! mark = 1;//!标记 int cnt = 0;//!计数 int j=0; while(cnt
snum-curState-1); if(temp&(fa->ends) ){//!到达终止状态可以形成一个字符串 if(!findStr(curstr,grep,num)) strcpy(grep[num++],curstr); } if(pos==MAX_STRING_NUM||n<=0){ //!nfa是否可以通过空字符到达终止状态 int nextState = fa->stateTrans[curState][0]; if(fa->nfa && nextState!= -1){ findFAStr(grep,fa, nextState, pos, n); } return;//!越界或者到达最大长度 } int i=(fa->nfa)?0:1;//!只有NFA需要考虑空字符 for(; i<=fa->letter_num;i++){//!遍历所有字符 int nextState = fa->stateTrans[curState][i]; int temp1 = pos,temp2=n;curstr[pos]='\0'; if(nextState != -1){//!可以从标记为i的边出去 if(i!=0){//!不是空字符 curstr[temp1++] = fa->letters[i-1]; curstr[temp1]='\0'; temp2--; } findFAStr(grep,fa, nextState, temp1, temp2); } } return;}/**搜索从FA开始状态,产生的长度小于n的字符串集合*/int findAllFAstr(char grep[MAX_STRING_NUM][MAX_STRING_LENGTH],FA*fa, int n){ int temp; num =0; for(int i=1; i<= fa->snum; i++){ temp = 1<<(fa->snum -i); if(temp&(fa->starts)){ memset(curstr, 0, sizeof(curstr));//!清空字符串 findFAStr(grep,fa,i-1,0,n); } } return num;}/**比较两个FA是否等价*/bool cmpFA(FA fa1, FA fa2, int n){ printf("in fa1:\n"); int num1 = findAllFAstr(grep1,&fa1, n); printf("strings in fa1:\n"); for(int i=0;i

转载地址:http://hbdyk.baihongyu.com/

你可能感兴趣的文章