(一)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)*
(二)DFA最小化(2小时)
实验目的
学会编程实现等价划分法最小化DFA。
实验任务
先完善DFA,再最小化DFA。
实验内容
(1)准备3个以上测试DFA文件。(提示:其中一定要有没有最小化的DFA)
(2)用C或C++语言编写用等价划分法最小化DFA的程序。
(3)经测试无误。测试不易。可求出两个DFA的语言集合的某个子集(如长度小于某个N),再证实两个语言集合完全相同!
通用NFA存储格式的建议(用以上的第三个测试NFA为例)
2 // 字符集中的字符个数 (以下两行也可合并成一行)
a b // 以空格分隔的字符集。
4 // 状态个数 (以下两行也可合并成一行)
1 2 3 4 // 状态编号。若约定总是用从1开始的连续数字表示,则此行可省略
1 // 开始状态的编号。若约定为1,则此行可省略
1 // 结束状态个数。若约定为1,则此行可省略
3 // 结束状态的编号
3 2 1 // 状态1的所有出去的转换。按字符集中的字符顺序给出,并在最左边加上一列关于e的转换。-1表示出错状态。若下一个状态有多个时,多个状态用逗号分隔。
-1 1 -1
-1 3 4
-1 -1 3
集合状态的内部表示的建议
使用示性函数。也即:
- 含有单个状态的状态集合用2的幂次表示。即状态1 ~ N分别用数21 ~ 2N 表示。
- 数的存储:若用32位整型(__int32)、64位整型(__int64)存储,可分别表示32个或64个状态。更多的状态表示需要使用整型数组。
- 含有多个状态的状态集合也用数来表示。若两个状态集合A与B用数表示为m和n,则状态集合AÈB与AÇB的数可用“位运算”表示,分别为m|n和m&n。
- 若想知道某个状态集合A(用数m表示)中是否包含原先的第i个状态,也可使用基于“位运算”来判断:若(m | 2i )> 0,则包含,否则不包含。
实验通过测试后,按规定时间上交源代码、测试样例、输出文件(如有输出文件)和电子版实验报告。
代码实现
#include<iostream>
#include<fstream>
#include<string.h>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<algorithm>
#define MAXN 101
using namespace std;
bool visited[MAXN]; //图算法遍历节点用,表示该节点是否被访问过;
int end_state[MAXN]; //终止状态集合
int state_num,trans_num,end_state_num; //状态的数量,转换弧的数量,终止状态集的状态个数
char trans_table[MAXN][MAXN]; //状态转换表
string letters; //合法字符集
string letters_null; //合法字符集加上一个空串
vector<set<int> > ziji; //构造的子集
map<set<int>,int> flag; //子集是否被标记过
map<int,map<char,map<int,int> > > nfa; //用于存放NFA
set<int>temp; //用于主函数临时变量
char alpha[26]={'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'}; //用于给DFA状态命名
int hebing=0;
set<string>nfa_ok;
set<string>dfa_ok;
set<string>min_ok; //存放符合条件的字符串
struct DFA_build
{
char start;
char trans; //起始状态,终止状态,转换条件
char end;
set<int> ziji;
};
vector<DFA_build> DFA;
struct DFA_state //新DFA的状态名称,是否为终结状态
{
char name;
bool is_end;
vector<char> alpha_to; //转换表,与输入字符集一一对应,比如alpha_to的[0]代表输入字符集的[0]会转换到的状态
vector<char> element; //存放被消除的原DFA状态,用于保存最简DFA状态是由原来哪几个状态构成的
};
vector<DFA_state> DFA_States; //声明两个vector,一个用于处理NFA转DFA,一个用于处理最简DFA
vector<DFA_state> DFA_MIN;
bool Find_RE(set<int> tmp) //查找一个子集是否存在于已有的子集集合中,以免构造状态重复
{
for(int i=0;i<ziji.size();i++)
{
if(tmp==ziji[i])
return false;
}
return true;
}
bool Find_End_State(set<int> tmp) //查找当前NFA所在状态是否在输入的结束状态集中
{
for(int x=0;x<end_state_num;x++)
for(set<int>::iterator i=tmp.begin();i!=tmp.end();i++)
{
if(end_state[x]==*i)
return true;
}
return false;
}
int Finish() //查找当前的子集扩展是否完毕,如子集被扩展会被标记,如有子集未标记则接着扩展它,返回它的下标,否则返回-1
{
for(int i=0;i<ziji.size();i++)
{
if(!flag[ziji[i]])
{
flag[ziji[i]]=1;
temp=ziji[i];
return i;
}
}
return -1;
}
bool nfaOK(int n) //对NFA,搜寻小于等于N字符串用,确认当前的字符串是否能被接收
{
for(int i=0;i<end_state_num;i++)
if(n==end_state[i])
return true;
return false;
}
bool dfaOK(char n) //对DFA,搜寻小于等于N字符串用,确认当前的字符串是否能被接收
{
for(int i=0;i<DFA_States.size();i++)
if(DFA_States[i].name==n && DFA_States[i].is_end==true)
return true;
return false;
}
bool minOK(char n) //对最简DFA,搜寻小于等于N字符串用,确认当前的字符串是否能被接收
{
for(int i=0;i<DFA_MIN.size();i++)
if(DFA_MIN[i].name==n && DFA_MIN[i].is_end==true)
return true;
return false;
}
void Print_Search(set<string> tp) //打印小于等于N的搜索结果
{
int count=1;
for(set<string>::iterator i=tp.begin();i!=tp.end();i++)
{
string tmp=*i;
cout<<count<<"、";
if(tmp.empty())
cout<<"空串符合要求。"<<endl;
else
cout<<tmp<<endl;
count++;
}
}
set<int> Move_and_find(set<int> last,char trans) //在NFA(一个三维map,类似于数组)上,根据输入的字符移动,寻找可以扩展下一个状态
{
set<int> EXT;
for(set<int>::iterator i=last.begin();i!=last.end();i++)
{
for(int j=0;j<=state_num;j++)
{
if(nfa[*i][trans][j]==1)
EXT.insert(j); //使用set数据结构的好处在于,它可以避免重复,insert一个已有的元素不会改变其结构,且set可以自排序
}
}
return EXT; //返回这个扩展出的新集合
}
set<int> e_closure(set<int> last) //扩展算法
{
memset(visited,false,sizeof(visited));
set<int> New;
queue<int> Q;
for(set<int>::iterator i=last.begin();i!=last.end();i++) //将当前有待扩展的set,压入队列,并插入新的set集合
{
Q.push(*i);
New.insert(*i);
}
while(!Q.empty()) //队列每次弹出首位,并以首位进行扩展
{
queue<int> Qn=Q;
int tmp=Q.front();
Q.pop(); //扩展以后弹出
for(int i=0;i<state_num;i++)
{
if(nfa[tmp]['*'][i]==1 && visited[i]==false) //搜索首位元素是否能够扩展到的一切其他未被访问的结点(类似于图算法)
{
New.insert(i);
Q.push(i);
visited[i]=true; //如果找到了,那么就加入新的set,并标记已访问
}
}
}
return New;
}
void Create(int start_state) //根据初始状态和字符集,构造新的子集
{
for(int x=0;x<letters.length();x++)
{
set<int> New;
New=e_closure(Move_and_find(ziji[start_state],letters[x])); //以当前状态为开始状态,调用上述两个函数find_move和closure,扩展一下所有存在于字符集中的字符
if(Find_RE(New) && !New.empty()) //如果扩展的子集在当前的子集集合中不重复,就加入,重复了就不行,调用Find_RE函数实现查找
ziji.push_back(New);
for(int i=0;i<ziji.size();i++)
{
if(New==ziji[i] && !New.empty())
{
DFA.push_back(DFA_build{alpha[start_state],letters[x],alpha[i],New}); //将构造好的子集信息存入DFA,包括开始状态、条件、新的状态,并根据alpha 26字母顺序给新状态名
break;
}
}
}
}
void Print_Chart() //打印子集构造信息和DFA状态转换表
{
cout<<"构造了"<<ziji.size()<<"个子集,与NFA状态对应关系如下:"<<endl;
for(int i=0;i<ziji.size();i++)
{
int Flag=0;
cout<<alpha[i]<<":{";
set<int>tp=ziji[i];
for(set<int>::iterator j=tp.begin();j!=tp.end();j++)
{
if(j!=tp.begin())
cout<<",";
cout<<*j;
}
cout<<"}";
for(int x=0;x<end_state_num;x++)
{
for(set<int>::iterator j=tp.begin();j!=tp.end();j++)
{
if(end_state[x]==*j) //如果子集中包括了之前NFA的接收状态,那么打印一个end state区分
{
Flag=1;
cout<<" "<<"[ End State ]";
break;
}
}
}
cout<<endl;
if(Flag==1) //构建新DFA状态vector,方便之后的引用和输出,构造最简DFA状态,方便化简,下面这两个vector一开始都是相同的,只是因为用途不同,所以拷贝了2份
{
DFA_States.push_back(DFA_state{alpha[i],true});
DFA_MIN.push_back(DFA_state{alpha[i],true});
}
else //如果之前的FLAG=1,那么说明包含了终结状态,is_end位置上要传入true,否则传入false
{
DFA_States.push_back(DFA_state{alpha[i],false});
DFA_MIN.push_back(DFA_state{alpha[i],false});
}
}
cout<<endl<<"构造的DFA状态转换表如下:"<<endl; //打印一个二维数组作为状态转换表,就是需要注意一下格式,其实没什么技术含量
for(int i=1;i<letters.length()+1;i++)
trans_table[0][i]=letters[i-1];
for(int i=1;i<ziji.size()+1;i++)
trans_table[i][0]=alpha[i-1];
for(int i=0;i<DFA.size();i++)
for(int j=1;j<ziji.size()+1;j++)
if(DFA[i].start==trans_table[j][0])
trans_table[j][letters.find(DFA[i].trans)+1]=DFA[i].end;
for(int i=0;i<ziji.size()+1;i++)
{
for(int j=0;j<letters.length()+1;j++)
{
if(i!=0 && j!=0 && (trans_table[i][j]<'A' || trans_table[i][j]>'Z'))
trans_table[i][j]='?';
if(i==0 && j==0)
cout<<'\t';
else
cout<<trans_table[i][j]<<'\t';
if(i>=1 && j>=1)
{
DFA_States[i-1].alpha_to.push_back(trans_table[i][j]);
DFA_MIN[i-1].alpha_to.push_back(trans_table[i][j]);
}
}
cout<<endl;
}
}
void PrintMIN(int x) //打印最小化的DFA
{
if(x==2)
cout<<"最小化并重命名后的DFA为:"<<endl;
if(x==1)
cout<<"消除过程:"<<endl;
cout<<'\t';
for(int i=0;i<letters.length();i++)
cout<<letters[i]<<'\t';
cout<<endl;
for(int i=0;i<DFA_MIN.size();i++)
{
cout<<DFA_MIN[i].name<<'\t';
for(int j=0;j<DFA_MIN[i].alpha_to.size();j++)
cout<<DFA_MIN[i].alpha_to[j]<<'\t';
cout<<endl;
}
cout<<endl;
}
void Min() //DFA最小化函数
{
for(int i=0;i<DFA_MIN.size();i++)
{
for(int j=i+1;j<DFA_MIN.size();j++)
{
if(DFA_MIN[i].alpha_to==DFA_MIN[j].alpha_to && DFA_MIN[i].is_end==DFA_MIN[j].is_end)
{ //如果存在两种状态同为终止或非终止状态,且输入所有字符,到达的状态都一样,那么就等效,可以合并
char tmp1=DFA_MIN[j].name;
char tmp2=DFA_MIN[i].name;
cout<<"当前消除:"<<tmp2<<" "<<tmp1<<endl;
for(int x=0;x<DFA_MIN.size();x++)
{
for(int y=0;y<DFA_MIN[x].alpha_to.size();y++) //多余的可合并状态,以前一个命名,并修改状态转换表中所有的名字,比如AC合并叫A,那么状态表中所有的C都改成A
{
if(DFA_MIN[x].alpha_to[y]==tmp1)
DFA_MIN[x].alpha_to[y]=tmp2;
}
}
DFA_MIN.erase(DFA_MIN.begin()+j); //删除多余的已合并状态
j=i;
PrintMIN(1);
}
}
}
for(int i=0;i<DFA_MIN.size();i++) //由于删除了一些状态,此处将剩余的所有状态重命名,按照字母表顺序
{
char tmp=DFA_MIN[i].name;
DFA_MIN[i].name=alpha[i];
for(int j=0;j<DFA_MIN.size();j++)
{
for(int k=0;k<DFA_MIN[j].alpha_to.size();k++)
if(DFA_MIN[j].alpha_to[k]==tmp)
DFA_MIN[j].alpha_to[k]=alpha[i];
}
}
PrintMIN(2);
}
string tmp1="";
void searchNFA(int cur_state,int n,int depth) //对NFA,搜索所有小于等于N长度的字符串,方法参考实验一
{
if(depth>n)
return;
if(nfaOK(cur_state))
nfa_ok.insert(tmp1);
for(int i=0;i<letters_null.length();i++)
{
for(int j=0;j<state_num;j++)
{
if(nfa[cur_state][letters_null[i]][j]==1)
{
if(letters_null[i]=='*')
searchNFA(j,n,depth);
else
{
tmp1+=letters_null[i];
searchNFA(j,n,depth+1);
tmp1=tmp1.erase(tmp1.length()-1,1);
}
}
}
}
}
string tmp2="";
void searchDFA(char cur_state,int n,int depth) //对DFA,搜索所有小于等于N长度的字符串,方法参考实验一
{
if(depth>n)
return;
if(dfaOK(cur_state))
dfa_ok.insert(tmp2);
for(int i=0;i<DFA_States.size();i++)
{
if(DFA_States[i].name==cur_state)
for(int j=0;j<letters.length();j++)
{
tmp2+=letters[j];
searchDFA(DFA_States[i].alpha_to[j],n,depth+1);
tmp2=tmp2.erase(tmp2.length()-1,1);
}
}
}
string tmp3="";
void searchMIN(char cur_state,int n,int depth) //对最简DFA,搜索所有小于等于N长度的字符串,方法参考实验一
{
if(depth>n)
return;
if(minOK(cur_state))
min_ok.insert(tmp3);
for(int i=0;i<DFA_MIN.size();i++)
{
if(DFA_MIN[i].name==cur_state)
for(int j=0;j<letters.length();j++)
{
tmp3+=letters[j];
searchMIN(DFA_MIN[i].alpha_to[j],n,depth+1);
tmp3=tmp3.erase(tmp3.length()-1,1);
}
}
}
int main()
{
fstream ff("lab3.txt",ios::in);
int st,ed;
char trans;
int less_than;
cout<<"请输入最大状态编号:";
cin>>state_num;
state_num++;
cout<<"请输入字符集,不要空格:";
cin>>letters;
letters_null=letters+'*';
cout<<"请输入终止状态集的集合数量(已经默认开始状态为0):";
cin>>end_state_num;
cout<<"请输入终止状态集合中的所有状态:";
for(int i=0;i<end_state_num;i++)
cin>>end_state[i];
cout<<"请输入一个整数N,求长度小于等于N的所有符合条件字符串:";
cin>>less_than;
cout<<"请输入NFA转换法则,格式(1 a 2,*为空),Ctrl+Z结束:";
while(cin>>st>>trans>>ed)
nfa[st][trans][ed]=1;
ff.close();
temp.insert(0);
temp=e_closure(temp);
ziji.push_back(temp);
while(1)
{
int i=Finish(); //如果子集构造时,所有状态都被标记,Finish返回-1,也就是构造完成
if(i==-1)
break;
else
Create(i);
}
cout<<endl;
Print_Chart();
cout<<endl;
Min();
cout<<endl<<"开始检查,输出长度小于等于"<<less_than<<"的所有字符串:"<<endl;
searchNFA(0,less_than,0);
cout<<"一共有以下"<<nfa_ok.size()<<"个串符合NFA:"<<endl;
Print_Search(nfa_ok);
searchDFA('A',less_than,0);
cout<<"一共有以下"<<dfa_ok.size()<<"个串符合DFA:"<<endl;
Print_Search(dfa_ok);
searchMIN('A',less_than,0);
cout<<"一共有以下"<<min_ok.size()<<"个串符合最简DFA:"<<endl;
Print_Search(min_ok);
return 0;
}
测试用例
/*样例一:开始就是结束
1
a
1
0
3
0 a 0
*/
/*样例二:编译原理习题3.7.3(4)
17
ab
1
17
6
0 * 1
1 * 2
2 a 3
3 * 6
1 * 4
4 b 5
5 * 6
6 * 1
0 * 7
6 * 7
7 a 8
8 b 9
9 b 10
10 * 11
10 * 17
11 * 12
11 * 14
12 a 13
14 b 15
15 * 16
13 * 16
16 * 11
16 * 17
*/
/*样例三:实验三文档
4
ab
1
2
4
0 a 1
1 a 0
0 b 0
0 * 2
2 a 2
2 b 3
3 b 2
*/