编译原理实验三:NFA确定化和DFA最小化

(一)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

 

集合状态的内部表示的建议

使用示性函数。也即:

  1. 含有单个状态的状态集合用2的幂次表示。即状态1 ~ N分别用数21 ~ 2N 表示。
  2. 数的存储:若用32位整型(__int32)、64位整型(__int64)存储,可分别表示32个或64个状态。更多的状态表示需要使用整型数组。
  3. 含有多个状态的状态集合也用数来表示。若两个状态集合A与B用数表示为m和n,则状态集合AÈB与AÇB的数可用“位运算”表示,分别为m|n和m&n。
  4. 若想知道某个状态集合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
*/

 

  • 21
    点赞
  • 107
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值