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

ps:实验要求文件的推荐 NFA 数据格式有大问题,对于NFA来说,因为NFA对于一个字符可能有多个转移状态,所以推荐的格式不能全部储存到。我做了修改,替换成了状态转移表的存储格式,但是出现了一个新的问题:对于一个状态到另一个状态如果有多个字符可以到达那么无法表示。谨慎参考。不过,状态子集用unsigned long long 二进制表示存储非常方便,推荐。

编译原理第二次实验报告
(一)NFA—>DFA(2小时)

一、实验目的
学习和掌握将NFA转为DFA的子集构造法。
二、实验任务
(1)存储NFA与DFA;
(2)编程实现子集构造法将NFA转换成DFA。
三、实验内容
(1)确定NFA与DFA的存储格式。
要求为3个以上测试NFA准备好相应有限自动机的存储文件。(可利用实验一(二)的基础)
NFA和DFA的存储格式(输入文件txt):
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

格式为:
自动机类型的字符串NFA或者DFA
字符表字符数量(包括空字符’’)
字符表字符(包括空字符’
’)
状态数量
状态编号 开始状态标记 接收状态标记
状态编号 开始状态标记 接收状态标记
……
状态编号 开始状态标记 接收状态标记
状态编号 开始状态标记 接收状态标记
起始状态 识别字符 到达状态
起始状态 识别字符 到达状态
……
起始状态 识别字符 到达状态
起始状态 识别字符 到达状态
(2)用C或C++语言编写将NFA转换成DFA的子集构造法的程序。
在这里插入图片描述

NFA_Trans_DFA()子集法NFA确定化函数:
首先需要一个子集数组,类型为unsigned long long,二进制表示时位值为1表示此位置的次序状态存在此子集中。随后声明一个子集转换表move_dfa[MAXS][MAXN]二维数组,用来存储子集法NFA确定化时产生的子集字符转换关系。再将subset[0]存储开始状态的闭包,调用closure()函数,closure()函数内部又调用search()函数通过自动机的状态转换表move[MAXN][MAXN]递归搜索空字符转换边求得闭包。
之后进入大循环,大循环的终止条件是所有子集都已经在子集数组中了。下一层循环是对每一个子集遍历字符表,将每一个字符下对应的转换状态子集的闭包求出。此时需要调用charTrans()函数求得转换状态,随后在对转换状态子集调用closure()函数求得闭包。每次求得闭包后需要检查是否已经在子集数组中了,不在则扩充子集数组,并且更新子集转换表move_dfa[MAXS][MAXN]。最后根据得到的子集数组和子集转换表move_dfa[MAXS][MAXN]来更新NFA,将其转换为DFA。
(3)测试验证程序的正确性。
求出NFA与DFA的语言集合的某个子集(如长度小于某个N),再证实两个语言集合完全相同!
因为输出字符串太多,所以详情请开文档:
out_1.txt out_2.txt out_3.txt out_4.txt
屏幕输出:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(二)DFA最小化(2小时)
一、实验目的
学会编程实现等价划分法最小化DFA。
二、实验任务
先完善DFA,再最小化DFA。
三、实验内容
(1)准备3个以上测试DFA文件。(提示:其中一定要有没有最小化的DFA)
因为我编写的程序是先将NFA确定化为DFA,再将DFA最小化。所以初始的输入文件只设计了NFA,DFA是NFA确定化后运行可以在屏幕显示。
NFA和DFA的存储格式(输入文件txt):

如上图

格式为:
自动机类型的字符串NFA或者DFA
字符表字符数量(包括空字符’’)
字符表字符(包括空字符’
’)
状态数量
状态编号 开始状态标记 接收状态标记
状态编号 开始状态标记 接收状态标记
……
状态编号 开始状态标记 接收状态标记
状态编号 开始状态标记 接收状态标记
起始状态 识别字符 到达状态
起始状态 识别字符 到达状态
……
起始状态 识别字符 到达状态
起始状态 识别字符 到达状态
(2)用C或C++语言编写用等价划分法最小化DFA的程序。
在这里插入图片描述

Minimize_DFA()等价划分法最小化DFA函数:
首先声明一个等价划分数组divide[MAXN]和最大划分数num = 1。divide[MAXN]存储次序状态属于划分的数字,num是最大的划分数字。初始划分是,非接收状态划分值为0,接收状态划分值为1。随后进入大循环进行遍历划分值开始进行划分,首先将对应划分值的划分状态次序单独拿出来存储进数组set[MAXN]中。随后遍历字符表的所有字符,以当前划分的首状态set[0]为标杆,当同划分的其他状态通过move[MAXN][MAXN]状态转换表到达的状态不与set[0]进入状态属于同一划分时,即divide数组元素的值不一样,则进行再划分。
再划分的状态的划分值为num+1,并且更新标记flag = 1,表示新的划分产生,num的值再进行自增。而且对于当前划分需要遍历完所有字符才表示完全划分干净了,并且在此过程中有的状态进入了新的划分,需要将它们踢出去,利用divide划分值识别不将他们与当前划分首状态set[0]进行比较。最后,当划分完成时,num值不在变化,循环终止。最后根据划分数组divide[MAXN][MAXN]进行更新最小化后的DFA。
(3)经测试无误。测试不易。可求出两个DFA的语言集合的某个子集(如长度小于某个N),再证实两个语言集合完全相同!
因为输出字符串太多,所以详情请开文档:
out_1.txt out_2.txt out_3.txt out_4.txt
屏幕输出:

如上图

(三)自动机类以及其他的实现函数

  1. 自动机类以及方法函数(注释已详细介绍)
    在这里插入图片描述

  2. void read() 读取输入文件更新自动机
    ](https://img-blog.csdnimg.cn/20201122122225199.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1hNUFRGUQ==,size_16,color_FFFFFF,t_70#pic_center)
    在这里插入图片描述

这个函数的实现逻辑很简单,就是依照上面我使用的NFA、DFA存储方法来一行一行更新自动机的数据。不过需要注意的是:
if(letter[i] == '’){ //如果为空字符
pos = i; //记录空字符次序
continue;
}
这一步将NFA的空字符’
’出现的次序i用pos记录下来。因为DFA字符表是没有空字符的,所以我们需要记录空字符位置以便删除。
if(pos != -1){ //如果字符表有空字符
letter[pos] = letter[letter_num-1]; //交换空字符与字符表末尾字符的次序
letter[letter_num-1] = ‘’;
char_order[letter[pos]] = pos; //重新调整字符->次序转换表
char_order[’
’] = letter_num-1;
}
这部分逻辑是将字符表末尾字符与空字符交换位置,然后更新 字符—次序 转换表。这样DFA字符表删除空字符时可以直接将字符数目减一就可以了。
3.map<string,string> getString(int order,string ans,ull
curlen,map<string,string> m){ //递归查询自动机能识别的字符串
在这里插入图片描述

getString()函数是不断循环本状态通过状态转换表move[][]可以到达的状态进行字符串的增长获取。不过需要注意的是在NFA中当字符串长度等于最大长度length但状态不是接收状态时也不能递归返回,因为可能经过了几个空字符后到达接收状态。所以只能continue。其他情况就根据字符是否为空字符进行字符串及长度的更新递归。最后状态所有到达状态都搜索完毕时返回
4. void printString(){ //在文件中输出自动机能识别的所有长度小于等于LEN的字符串
在这里插入图片描述

这个函数逻辑也很简单,就是在输出文件内输出自动机的数据结构队列set存储的可识别字符串。只需要注意的是,当自动机set还未生成,为空时,需要先调用createQueue()函数生成。
5.void createQueue(){ //生成自动机能能识别的所有长度小于等于LEN的字符串队列
在这里插入图片描述

通过对自动机每一个开始状态调用之前解析的getString()函数获得所有长度小于等于length的可识别字符串,先存储在map中进行去重去空字符。随后遍历map容器将字符串存入自动机队列set中,以字典序顺序。
6. vector getOrder(ull set){ //获取子集中所有状态的次序
在这里插入图片描述

通过状态的unsigned long long 类型二进制表示来得知子集中包含的状态的次序。不断将子集数右移,然后将最低位与0x1进行位与,如果为1则此位置值1,如果为0则此位置值0。再由不断自增一的sum来获得次序,最后压入vector中。
7. ull search(ull ans,int num,bool flag[]){ //递归搜索子集通过不限次空字符获得的子集
在这里插入图片描述

search()函数通过递归不断获取一个子集的状态如何通过不限次的空字符得到另一个子集。就是由当前状态经过状态转换表的空字符move[][]获得下一状态,最后将状态的二进制表示与子集位或获得最终的子集。不过需要注意的是要用flag[]数组来标记经过的状态,不然会进入死循环当中。
8. ull closure(ull ans){ //获取子集闭包closure
在这里插入图片描述

closure()函数通过调用getOrder()获得子集包含的状态次序并存储到vector中后,对每一个状态调用search()函数递归搜索空字符串到达状态,最后形成的子集就是闭包。
9. ull charTrans(ull set,char ch){ //获取子集通过字符转换形成的子集
在这里插入图片描述

charTrans()函数调用getOrder()获得子集状态次序后,对每一个状态经过字符ch达到的状态存入子集tmp中,最后返回tmp。
10. int inSet(ull subset[],int size,ull set){ //检测子集是否在子集集合中并返回次序
在这里插入图片描述

inSet()函数通过循环if分支判断子集set是否在子集集合subset中,当存在时返回子集set在子集集合subset中的次序,否则返回-1。
11. void isEqual(AUTOMATA automata[]){ //由识别字符串集合判断 NFA DFA min_DFA 是否等价
在这里插入图片描述

isEqual()通过比较两个自动机的可识别字符串队列set是否相同来判定两个自动机是否等价。利用一个双重循环对三个自动机进行两两比较,首先当可识别字符串队列set的大小不同时,将对应标识flag置0,否则继续利用循环判断两者队列的字符串是否相同(可识别字符串在队列中已经是字典序排列了)。如果有一个可识别字符串不同,那么也将对应标识flag置0。最后根据三个标记flag[3]的值来判断三个自动机之间的等价情况。输出提示信息。
需要注意的是,一些情况需要排除,三者中有两对相互等价,但是剩下的一对不等价的情况是不可能的,比如a b c中a == b, a == c, 那么必定b == c。
12.int main() //主函数
在这里插入图片描述

因为需要调用isEqual()函数判断NFA DFA min_DFA三者的等价性,所以需要把它们声明成一个长度为3的AUTOMATA类数组。

(四)完整源代码

#include <bits/stdc++.h>
#define ull unsigned long long
#define MAXN 500    
#define MAXS 100
#define LEN 7             //识别输出字符串最大长度 
#define IN "in_1.txt"     //输入文件  "in_1.txt"  "in_2.txt"  "in_3.txt"  "in_4.txt" 
#define OUT "out_1.txt"   //输出文件  "out_1.txt"  "out_2.txt"  "out_3.txt"  "out_4.txt"
using namespace std;

class AUTOMATA{          //自动机类 
public:
	string type;         //自动机类型:NFA DFA 
	bool is_min;         //是否最小化的标志 
	ull length;          //设置最大识别字符串长度  
	
	int letter_num;      //字符表长度 
	char letter[MAXN];   //字符表数组 
	
	int state_num;       //状态集合数目
	struct State{        //储存状态信息的结构体 
		int id;          //状态id 
		ull bin_id;      //在状态集合的二进制次序
		bool is_start,is_finish;     //是否为开始、接收状态的标志 
		State(){                     //结构体初始化构造函数 
            id = 0; bin_id = 0;
            is_start = 0; is_finish = 0;
        }
	}state[MAXN];        //状态数组 
	
	int start_num;       //开始状态数目 
	int start_state[MAXN];      //开始状态在状态集合次序的集合 
	
	int fin_num;         //接收状态数目
	int fin_state[MAXN];        //接收状态在状态集合次序的集合
	
	map<char,int> char_order;      //字符->字符表次序  转换表 
	map<int,int> id_order;         //状态id->状态数组次序  转换表
	queue<string> set;      //自动机能识别的长度小于等于length的字符串去重去空字符串集合 
	
	char move[MAXN][MAXN];      //自动机状态转换表的二维数组 
	
	AUTOMATA();   //自动机初始化构造函数 
    void read();   //读入文件数据更新自动机 
    void showAutomata();    //打印输出自动机的全部信息  
    map<string,string> getString(int,string,ull,map<string,string>);   //递归查询自动机能识别的字符串 
    void printString();   //在文件中输出自动机能识别的所有长度小于等于LEN的字符串 
    void createQueue();   //生成自动机能能识别的所有长度小于等于LEN的字符串队列 
	bool legalDetect();   //自动机合法性的检查 
	int inSet(ull*,int,ull);   //检测子集是否在子集集合中并返回次序 
    vector<int> getOrder(ull);    //获取子集中所有状态的次序 
    ull charTrans(ull,char);    //获取子集通过字符转换形成的子集 
    ull search(ull,int,bool*);    //递归搜索子集通过不限次空字符获得的子集 
    ull closure(ull);     //获取子集闭包closure 
    AUTOMATA NFA_Trans_DFA();   //NFA确定化函数 
	AUTOMATA Minimize_DFA();    //DFA最小化函数 
};

AUTOMATA:: AUTOMATA(){      //自动机初始化构造函数 
    length = LEN;
    is_min = letter_num = state_num = start_num = fin_num = 0;
    memset(letter, 0, sizeof(letter));
    memset(start_state, -1, sizeof(start_state));
    memset(fin_state, -1, sizeof(fin_state));
    memset(move, 0, sizeof(move));
}

void AUTOMATA:: read(){      //读入文件数据更新自动机
	int pos = -1;    //NFA空字符检测标志 
	ifstream infile(IN,ios::in);
	assert(infile.is_open());
	getline(infile,type);    //更新自动机类型 
	infile >> letter_num;    //更新字符表长度 
	for(int i = 0; i < letter_num; i++){     //更新字符表 
		infile >> letter[i];
		if(letter[i] == '_'){     //如果为空字符 
			pos = i;    //记录空字符次序 
			continue;
		}
		char_order[letter[i]] = i;   //更新字符->次序转换表 
	}
	if(pos != -1){      //如果字符表有空字符 
		letter[pos] = letter[letter_num-1];    //交换空字符与字符表末尾字符的次序 
	    letter[letter_num-1] = '_';
	    char_order[letter[pos]] = pos;     //重新调整字符->次序转换表 
	    char_order['_'] = letter_num-1;
	}
	infile >> state_num;    //更新状态集合数目 
	for(int i = 0; i < state_num; i++){    //更新状态数组 
		infile >> state[i].id;
		state[i].bin_id = 1 << i;     //无符号long long变量的二进制表示在状态数组次序置 1,其余位置 0
		infile >> state[i].is_start >> state[i].is_finish;
		if(state[i].is_start)
			start_state[start_num++] = i;
		if(state[i].is_finish)
			fin_state[fin_num++] = i;
		id_order[state[i].id] = i;   //更新状态id->次序转换表
	}
	while(!infile.eof()){
		int id1,id2;
		char ch;
		infile >> id1 >> ch >> id2;
		move[id_order[id1]][id_order[id2]] = ch;   //更新自动机状态转换表的二维数组 
	}
	infile.close();
}

map<string,string> AUTOMATA:: getString(int order,string ans,ull curlen,map<string,string> m){  //递归查询自动机能识别的字符串 
	if(state[order].is_finish)    //如果当前为接收状态 
		m[ans] = ans;             //将当前识别字符串ans加入map容器 m中  
	for(int j = 0; j < state_num; j++){    //循环状态转换表的列 
		if(move[order][j] != 0){    //如果存在字符状态转换 
			if(move[order][j] != '_'){    //如果字符不是空字符
			    if(curlen == length)   //当前字符串长度为最大长度length 
			    	continue;       //略过,希望能找到一条空字符转换边到达接收状态并且当前字符串长度不变 
				m = getString(j,ans+move[order][j],curlen+1,m);   //更新行号,字符串ans,长度+1 
			}
			else if(j != order)    //如果字符是空字符且列号不等于行号 
				m = getString(j,ans,curlen,m);   //更新行号 
		}
	}
	return m;    //返回map容器 m 
}

void AUTOMATA:: printString(){    //在文件中输出自动机能识别的所有长度小于等于LEN的字符串
	ofstream outfile;
	outfile.open(OUT,ios_base::app);
	assert(outfile.is_open());
	if(set.empty())       //如果自动机字符串队列为空 
		createQueue();    //调用createQueue()生成自动机识别字符串队列 
	if(is_min)            //DFA已经最小化 
		outfile <<  "minimized " << type << "可识别的字符串(长度小于等于" << length << ")为:" << endl;
	else
		outfile << type << "可识别的字符串(长度小于等于" << length << ")为:" << endl;
	for(int i = 0; i < (int)set.size(); i++){    //在文件中循环输出识别字符串 
		outfile << set.front() << endl;
		set.push(set.front());
		set.pop();
	}
	outfile << endl << endl;
	outfile.close();
}

void AUTOMATA:: createQueue(){   //生成自动机能能识别的所有长度小于等于LEN的字符串队列 
	map<string,string> m;
	for(int i = 0; i < start_num; i++)    //从自动机的每一个开始状态出发识别字符串 
		m = getString(start_state[i],"",0,m);
	for(map<string,string>::iterator iter = m.begin(); iter != m.end(); iter++)  //将map容器存储的非空字符串转储到队列中 
		if(iter->second != "")
			set.push(iter->second);
} 

bool AUTOMATA:: legalDetect(){     //自动机合法性的检查 
	if(type == "DFA" && start_num != 1){
		cout << type << "错误:开始状态不唯一" << endl;
		return 0;
	} 
	
	for(int i = 0; i < letter_num-1; i++){
		for(int j = i+1; j < letter_num; j++){
			if(type == "DFA" && (letter[i] == '_' || letter[j] == '_')){
				cout << "DFA错误:字符集出现空字符" << endl;
				return 0;
			}
			if(letter[i] == letter[j]){
				cout << type << "错误:字符集有重复" << endl;
				return 0;
			}
		}
	}
	
	for(int i = 0; i < state_num-1; i++){
		for(int j = i+1; j < state_num; j++){
			if(state[i].id == state[j].id){
				cout << type << "错误:状态集有重复" << endl;
				return 0;
			}
		}
	}
	
	for(int i = 0; i < start_num-1; i++){
		if(start_state[i] < 0 || start_state[i] >= state_num || start_state[start_num-1] < 0 || start_state[start_num-1] >= state_num){
			cout << type << "错误:存在开始状态不在状态集当中" << endl;
			return 0;
		}
		for(int j = i+1; j < start_num; j++){
			if(start_state[i] == start_state[j]){
				cout << type << "错误:开始状态集有重复" << endl;
				return 0;
			}
		}
	}
	
	for(int i = 0; i < fin_num-1; i++){
		if(fin_state[i] < 0 || fin_state[i] >= state_num || fin_state[fin_num-1] < 0 || fin_state[fin_num-1] >= state_num){
			cout << type << "错误:存在接受状态不在状态集当中" << endl;
			return 0;
		}
		for(int j = i+1; j < fin_num; j++){
			if(fin_state[i] == fin_state[j]){
				cout << type << "错误:接受状态集有重复" << endl;
				return 0;
			}
		}
	}
	
	for(int i = 0; i < state_num; i++){
		int in = 0,out = 0;
		for(int j = 0; j < state_num; j++){
			if(j != i && move[i][j] != 0)
				out = 1;
			if(j != i && move[j][i] != 0)
				in = 1;
			if(j != state_num-1){
				for(int k = j+1; k < state_num; k++){
					if(type == "DFA" && move[i][j] != 0 && move[i][j] == move[i][k]){
						cout << type << "错误:同一个字符出现在同状态射出的多条弧上" << endl;
				        return 0;
					}
				}
			}
		}
		if(in + out == 0 && (!state[i].is_start || !state[i].is_finish)){
			cout << type << "错误:存在一个不同时是开始和结束的孤立状态" << endl;
			return 0;
		}
		else if(in == 0 && out == 1 && !state[i].is_start){
			cout << type << "错误:存在一个没有其他状态转入的无效的非开始状态" << endl;
			return 0;
		} 
		else if(in == 1 && out == 0 && !state[i].is_finish){
			cout << type << "错误:存在一个不转入其他状态的死循环的非接收状态" << endl;
			return 0;
		}
	} 	
	return 1; 
}

vector<int> AUTOMATA:: getOrder(ull set){    //获取子集中所有状态的次序
	int sum = -1;  //状态次序初始化为-1 
	vector<int> order;
	while(set){
		ull bit = (1&set);   //利用000...001与子集set进行位与获得set最低位数字(0或者1) 
		sum++;  //当前状态次序+1 
		if(bit)   //如果子集set状态次序sum位为 1 
			order.push_back(sum);   //将状态次序sum压入向量 
		set = set >> 1;   //因为set是unsigned long long型变量,所以逻辑右移 1位 
	}
	return order;
}

ull AUTOMATA:: search(ull ans,int num,bool flag[]){    //递归搜索子集通过不限次空字符获得的子集 
	for(int i = 0; i < state_num; i++){     //遍历状态转换表的二维数组的列 
		if(!flag[i] && move[num][i] == '_'){   //如果转换边未遍历标记且转换字符为空字符 
			ans |= state[i].bin_id;   //将到达状态的二进制表示与子集ans进行位或 
			flag[i] = 1;    //标记此转换边已访问过 
			ans |= search(ans,i,flag);    //从目前到达状态重新开始递归搜索 
		}
	}
	return ans;
}

ull AUTOMATA:: closure(ull ans){    //获取子集闭包closure 
	vector<int> order = getOrder(ans);    //调用getOrder()获取子集中所有状态的次序  
	bool flag[MAXN];
	memset(flag, 0, sizeof(flag));
	for(int i = 0; i < (int)order.size(); i++)   
		ans = search(ans,order[i],flag);       //从子集中每个状态开始调用search()搜索通过不限次空字符获得的闭包子集 
	return ans;
}

ull AUTOMATA:: charTrans(ull set,char ch){    //获取子集通过字符转换形成的子集 
	ull tmp = 0;  
	vector<int> order = getOrder(set);    //调用getOrder()获取子集中所有状态的次序
	for(int i = 0; i < (int)order.size(); i++){
		for(int j = 0; j < state_num; j++){
			if(move[order[i]][j] == ch){     //如果转换表字符为指定字符 
				tmp |= state[j].bin_id;      //将子集tmp与到达状态次序的二进制表示位或 
			}
		}
	}
	return tmp;
}

int AUTOMATA:: inSet(ull subset[],int size,ull set){    //检测子集是否在子集集合中并返回次序 
	for(int i = 0; i < size; i++){     
		if(set == subset[i])    //如果子集set等于子集集合中的一个子集 
			return i;  //返回其在子集集合中的次序 
	}
	return -1;   //不存在则返回 -1 
}

AUTOMATA AUTOMATA:: NFA_Trans_DFA(){   //NFA确定化函数 
	if(type == "DFA")   //如果自动机已经是DFA则直接返回自身 
		return *this;
	AUTOMATA dfa;
	dfa.type = "DFA"; 
	ull subset[MAXS];
	int pos = 0,size = 0;
	int move_dfa[MAXS][MAXN];    //创建一个子集转换表(因为列号为字符次序,与自动机状态装换表不同) 
	memset(move_dfa, -1, sizeof(move_dfa));
	subset[pos] = closure(state[start_state[0]].bin_id);  //调用closure()求开始状态闭包获得开始子集subset[0] 
	size++;  //子集集合subset[]大小 +1 
	while(1){
		for(int i = 0; i < letter_num - 1; i++){   //遍历字符表除空字符外的所有字符 
			ull J = charTrans(subset[pos],letter[i]);  //获得当前子集subset[pos]的字符letter[i]转换子集 J 
			if(!J)   //如果子集 J 为空 
				continue;
			ull I = closure(J);   //子集 I为子集 J的闭包closure 
			int number = inSet(subset,size,I);   //number存储子集 I在子集集合subset[]中的次序 
			if(number == -1){   //如果number == -1即子集 I不在子集集合subset[]中 
				subset[size] = I;   //将子集 I加入到子集集合subset[]中 
				move_dfa[pos][i] = size;  //更新子集转换表 
				size++;   //子集集合subset[]大小 +1 
			}
			else   //集 I存在子集集合subset[]中 
				move_dfa[pos][i] = number;    //更新子集转换表
		}        
		pos++;  //当前子集转换表行 +1 
		if(pos == size)  //当所有子集都存在于子集集合subset[]中,子集转换表行数不再变化 
		break;   //子集转换表创建完成,跳出循环 
	}
	
	if(letter[letter_num-1] == '_')   //如果NFA字符表有空字符 
		dfa.letter_num = letter_num - 1;     //DFA的字符表不能有空字符,所以字符数目 -1 
	for(int i = 0; i < dfa.letter_num; i++){   //更新DFA字符表 
		dfa.letter[i] = letter[i];
		dfa.char_order[dfa.letter[i]] = i;  //更新字符->字符表次序 转换表 
	}
	dfa.start_num = 1;   //DFA开始状态唯一 
	dfa.start_state[0] = 0;
	dfa.state[0].is_start = 1;
	dfa.state_num = size;
	for(int i = 0; i < size; i++){   //更新DFA的状态数组 
		dfa.state[i].id = i;
		dfa.state[i].bin_id = 1 << i;
		dfa.id_order[i] = i;
		for(int j = 0; j < fin_num; j++){
			if((subset[i]&state[fin_state[j]].bin_id)>0){
				dfa.fin_state[dfa.fin_num++] = i;
				dfa.state[i].is_finish = 1;
				break;
			}
		}
	}
	for(int i = 0; i < size; i++){   //由获得的子集转换表move_dfa[][]更新DFA的状态转换表move[][] 
		for(int j = 0; j < dfa.letter_num; j++){
			if(move_dfa[i][j] != -1){
				dfa.move[i][move_dfa[i][j]] = dfa.letter[j];
			}
		}
	}
	return dfa;	
}

AUTOMATA AUTOMATA:: Minimize_DFA(){   //DFA最小化函数 
	AUTOMATA dfa;
	if(type != "DFA")     //如果自动机不是DFA 
		dfa = this->NFA_Trans_DFA();  //将自动机调用NFA_Trans_DFA()转换的DFA赋予dfa 
	else                  //如果自动机是DFA
		dfa = *this;   //将自动机赋予dfa 
	int divide[MAXN],num = 1;   //声明子集划分表数组divide[]和子集初始最大划分数num 
	memset(divide, 0, sizeof(divide));
	for(int i = 0; i < dfa.fin_num; i++)    //子集划分表divide[]形成基本划分,非接收状态划分为 0,接收状态划分为 1 
		divide[dfa.fin_state[i]] = 1;
	for(int i = 0; i <= num; i++){     //进入划分循环 
		int set[MAXN],ans = 0;   //声明当前划分集合set[]和当前划分大小ans 
		for(int j = 0; j < dfa.state_num; j++)  //创建划分为 i的当前划分 
			if(divide[j] == i)
				set[ans++] = j;
		for(int j = 0; j < dfa.letter_num; j++){   //当前划分set[]读入字符表所有字符进行再划分 
			int key = -1;   
			bool flag = 0;
			for(int k = 0; k < dfa.state_num; k++){
				if(dfa.move[set[0]][k] == dfa.letter[j]){   //查询状态转换表 
					key = divide[k];     //当前划分首状态通过字符letter[j]到达状态的划分为 key 
					break;
				}
			}
			for(int k = 1; k < ans; k++){  //遍历当前划分set[]除首状态外的所有状态 
				int value = -1;
				for(int l = 0; l < dfa.state_num; l++){     
					if(dfa.move[set[k]][l] == dfa.letter[j]){   //查询状态转换表
						value = divide[l];      //当前划分其他状态通过字符letter[j]到达状态划分为 value 
						break;
					}
				}
				if(key != value && divide[set[0]] == divide[set[k]]){  //当首状态转移状态划分 key 不等于 其他状态转移状态划分 value,且此其他状态未进行过再划分与首状态仍在同一划分中 
					divide[set[k]] = num+1;    //其他状态进入新划分 num+1 
					flag = 1;   //标志新划分产生 
				}
			}
			if(flag)   //如果新划分产生 
				num++;   //最大划分数 +1 
		}
	}
	
	dfa.is_min = 1;     //标记DFA已最小化 
	dfa.start_num = 1;    //DFA开始状态唯一 
	char move_copy[MAXN][MAXN];    
	for(int i = 0; i < dfa.state_num; i++){     //将当前DFA状态转换表拷贝进move_copy[][],然后将状态转换表move[][]初始化 
		for(int j = 0; j < dfa.state_num; j++){     
			move_copy[i][j] = dfa.move[i][j];
			dfa.move[i][j] = 0;
		}
	}
	for(int i = 0; i <= num; i++){   //由划分数进行状态数组更新 
		dfa.state[i].id = i;
		dfa.state[i].bin_id = 1 << i;
		dfa.id_order[i] = i;
		dfa.state[i].is_start = 0;
	}
	for(int i = 0; i < dfa.state_num; i++){   //更新开始状态 
		if(i == dfa.start_state[0]){
			dfa.start_state[0] = divide[i];
			dfa.state[divide[i]].is_start = 1;
			break;
		}
	}
	dfa.fin_num = 0;
	for(int i = 0; i <= num; i++){              //更新接收状态 
		int time = 1;
		for(int j = 0; j < dfa.state_num; j++){
			if(divide[j] == i){
				if(time && dfa.state[j].is_finish){
					dfa.fin_state[dfa.fin_num++] = i;
					time = 0;
				}
			}
		}
	}
	for(int i = 0; i <= num; i++){       //更新状态数组的接收状态标志信息 
		dfa.state[i].is_finish = 0;
		for(int j = 0; j < dfa.fin_num; j++){
			if(i == dfa.fin_state[j]){
				dfa.state[i].is_finish = 1;
				break;
			}
		}
	} 
	for(int i = 0; i < dfa.state_num; i++){     //由move_copy[][]存储的未最小化DFA状态转换信息创建最小化DFA的状态转换表move[][] 
		for(int j = 0; j < dfa.state_num; j++){
			if(move_copy[i][j] != 0 && (i == j || divide[i] != divide[j])){   //如果move_copy[i][j]可转换且当前状态有自旋或者当前状态不与到达状态在同一划分中 
				dfa.move[divide[i]][divide[j]] = move_copy[i][j];    //更新最小化DFA转换表move[][] 
			}
		}
	}
	dfa.state_num = num+1;   //最小化DFA状态数等于最大划分数 +1 
	return dfa;
}

void AUTOMATA:: showAutomata(){    //打印输出自动机的全部信息 
	if(!legalDetect())   //当自动机不合法时打印不合法信息后直接返回 
		return;
	if(is_min == 0)
		cout << "This is a " << type << ":" << endl;
	else
		cout << "This is a minimized " << type << ":" << endl;
	cout << "字符个数:" << letter_num << endl;
	cout << "字符集:";
	for(int i = 0; i < letter_num; i++)
		cout << letter[i] << " ";
	cout << endl << "状态个数:" << state_num << endl;
	cout << "状态集:";
	for(int i = 0; i < state_num; i++)
		cout << state[i].id << " ";
	cout << endl << "开始状态:";
	for(int i = 0; i < start_num; i++)
		cout << state[start_state[i]].id << " ";
	cout << endl << "接收状态:";
	for(int i = 0; i < fin_num; i++)
		cout << state[fin_state[i]].id << " ";
	cout << endl << type << "状态转换表:" << endl;
	cout << "  ";
	for(int i = 0; i < state_num; i++)
		cout << state[i].id << " ";
	cout << endl;
	for(int i = 0; i < state_num; i++){
		cout << state[i].id << " ";
		for(int j = 0; j < state_num; j++){
			if(move[i][j] == 0)              //当转换表 状态i->状态j 没有字符可到达时 
				cout << '.' << " ";          //转换表move[i][j]以 . 表示 
			else
				cout << move[i][j] << " ";
		}
		cout << endl;
	} 
	cout << endl;
}

void init(){       //输出文件初始化,进行清空 
	ofstream outfile(OUT,ios::out);
	assert(outfile.is_open());
	outfile.clear();
	outfile.close();
}

void isEqual(AUTOMATA automata[]){    //由识别字符串集合判断 NFA DFA min_DFA 是否等价 
	int flag[3] = {1,1,1};            // NFA DFA min_DFA 三者之间相互等价的标志 
	for(int i = 0; i < 3; i++)
		if(automata[i].set.empty())  //如果自动机还未生成可识别字符串队列 
			automata[i].createQueue();
	for(int i = 0; i < 2; i++){      //三个自动机之间两两比较判断 
		for(int j = i+1; j < 3; j++){
			if((int)automata[i].set.size() != (int)automata[j].set.size())  //识别字符串数量都不一样 
				flag[i+j-1] = 0;     //两者不等价 
			for(int k = 0; k < (int)automata[i].set.size(); k++){   
				if(automata[i].set.front() != automata[j].set.front())  //在字典序排序下相同位置的识别字符串不一样 
					flag[i+j-1] = 0;  //两者不等价 
				automata[i].set.push(automata[i].set.front());   //分别将队首字符串压入队尾 
				automata[j].set.push(automata[j].set.front());
				automata[i].set.pop();                           //分别将队首字符串出列,让下一字符串成为队首  
				automata[j].set.pop();
			}
		}
	}
	if(flag[0]+flag[1]+flag[2] == 3)        //当三者之间相互等价时 
		cout << automata[0].type << " 和 " << automata[1].type << " 和 minimized " << automata[2].type << " 三者完全等价" << endl;
	else if(flag[0]+flag[1]+flag[2] == 0)   //当三者之间互不等价时 
		cout << automata[0].type << " 和 " << automata[1].type << " 和 minimized " << automata[2].type << " 三者之间互不等价" << endl;
    else if(flag[0]+flag[1]+flag[2] == 1){  //当三者之间只有一对不等价时 
    	if(flag[0])   //NFA 和 DFA 等价 
    		cout << automata[0].type << " 和 " << automata[1].type << " 等价,两者与 minimized " << automata[2].type << " 不等价" << endl;
    	else if(flag[1])  //NFA 和 min_DFA 等价 
    		cout << automata[0].type << " 和 minimized " << automata[2].type << " 等价,两者与 " << automata[1].type << " 不等价" << endl;
    	else if(flag[2])  //DFA 和 min_DFA 等价 
    	    cout << automata[1].type << " 和 minimized " << automata[2].type << " 等价,两者与 " << automata[0].type << " 不等价" << endl;
    }
    //不可能出现三者之间有两对不等价的情况 
}

int main(){
	init();         //输出文件初始化,进行清空 
	AUTOMATA automata[3];   //automata[0]为 NFA  automata[1]为 DFA  automata[2] 为 min_DFA 
	automata[0].read();              //读入输入文件更新NFA 
	automata[0].showAutomata();      //打印输出NFA的全部信息 
	automata[0].printString();       //在文件中输出NFA能识别的所有长度小于等于LEN的字符串
	automata[1] = automata[0].NFA_Trans_DFA();    //DFA为NFA确定化后的自动机
	automata[1].showAutomata();                   //打印输出DFA的全部信息  
	automata[1].printString();                    //在文件中输出DFA能识别的所有长度小于等于LEN的字符串
	automata[2] = automata[1].Minimize_DFA();     //MIN_DFA为DFA最小化后的自动机 
	automata[2].showAutomata();                   //打印输出MIN_DFA的全部信息 
	automata[2].printString();                    //在文件中输出MIN_DFA能识别的所有长度小于等于LEN的字符串
	isEqual(automata);           //由识别字符串集合判断 NFA DFA min_DFA 是否等价 
	return 0; 
} 

(输文件txt)
(in_1.txt)
NFA
3
ab_
4
1 1 0
2 0 0
3 0 1
4 0 0
1b1
1a2
1_3
2a1
3a3
3b4
4b3

(in_2.txt)
NFA
3
ab_
6
1 1 0
2 0 0
3 0 0
4 0 0
5 0 0
6 0 1
1_2
1a1
2a3
2b4
3a5
4b5
5_6
6b6

(in_3.txt)
NFA
3
ab_
8
1 1 0
2 0 0
3 0 0
4 0 0
5 0 0
6 0 0
7 0 0
8 0 1
1_2
2_3
2b2
3a4
3b5
4a6
5b6
6_7
7_8
7a7

(in_4.txt)
NFA
3
a_b
6
0 1 0
1 0 0
2 0 0
3 0 1
4 0 0
5 0 0
0b0
0a1
0_3
1a2
2_0
3a3
3b4
4b5
5_3

  • 10
    点赞
  • 68
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
厦门理工学院编译原理实验是指在学习编译原理课程中进行的实验,具体涉及到了NFA(非确定有限自动机)转 DFA确定有限自动机)的相关内容。 NFADFA都属于有限状态自动机,用于描述形式语言的模型。NFA相对于DFA而言具有更高的表达能力,因为它在某些状态下可以有多个后继状态。而DFA则是在NFA的基础上进行了优,通过消除不确定性,使得状态转移更加明确和确定实验的主要目的是通过实践操作,加深对NFADFA的理解,并且掌握NFADFA的方。在实验中,我们会先根据给定的正则表达式或自动机图设计一个NFA,然后通过确定状态、转换表和终态等步骤,将NFA转换为DFA。这个转换过程需要考虑NFA中的ε-转移以及多个状态同时转移的情况,从而得到一个等价的DFA实验的步骤主要包括:通过给定的正则表达式构建NFA确定NFA的状态集、计算每个状态的ε-闭包、根据输入符号和ε-闭包进行状态转换,并得到新的状态集和转换表,最后确定DFA的终态。通过这些步骤,我们可以将一个NFA转换为一个等价的DFA,实现了从非确定性到确定性的转变。 在实验过程中,我们需要注意各个状态的转换规则以及ε-闭包的计算和使用,这些是实验中较为重要的部分。通过实际操作和计算,我们可以更好地理解NFADFA之间的关系,并且加深对编译原理中有限状态自动机的理解与应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值