【編譯原理實驗】Lab2(一)NFA確定化

CodeFriday發表於2020-11-10

一、實驗目的

學習和掌握將NFA轉為DFA的子集構造法。

二、實驗任務

(1)儲存NFA與DFA;
(2)程式設計實現子集構造法將NFA轉換成DFA。

三、實驗內容

在這裡插入圖片描述

四、實驗步驟

確定如何存NFA

一個NFA如圖所示
在這裡插入圖片描述
將NFA存在一個檔案中,上圖NFA表示資訊如下

3	//字元數
a b c	//字符集
10	//狀態數,0 - n-1
0	//開始狀態
9	//結束狀態
12	//邊數
0 a 1	//邊的格式:起始狀態 邊上的字元 終結狀態
1 # 2	//#表示空字元邊
2 # 3
2 # 9
3 # 4
3 # 6
4 b 5
6 c 7
5 # 8
7 # 8
8 # 9
8 # 3

子集構造法-演算法思想

NFA為什麼要轉DFA?
需要檢視所有可能的狀態轉移結果,需要大量遍歷、回溯
在這裡插入圖片描述
在這裡插入圖片描述
演算法虛擬碼:
在這裡插入圖片描述

如何儲存一個狀態集?

set是一個不錯的方法,因為還可以保證狀態集中無重複狀態,但是求狀態集的並很麻煩(如果用並查集,用陣列存狀態集也很方便)
vector,一維陣列都很方便。

但是以上方法都是用一個容器來存,每個狀態至少得用一個整數去表示,空間消耗還是較大的。
於是按照自己定義的NFA的狀態是從0 - n-1一個序列。考慮用一個int整數來存一個狀態集。如果用int型最多可表示32位,如果需要更多狀態,用long int或者陣列。
int型變數佔4位元組,也就是32個二進位制位,給二進位制位編號0-31(從低位到高位)。如果第x位為1,則表示該狀態集中包含了標號為x的狀態。
舉個例子:
上面圖中NFA,狀態0的閉包就是1(10進位制)
狀態1的閉包是606(10進位制)
606轉二進位制 10 0101 1110
從右往左從0開始數,第1位,第2位,第3位,第4位,第6位,第9位為1
因此表示狀態集{s1,s2,s3,s4,s6,s9}。

接下來就需要用到一些位運算子(|,&,<<,>>)

如何求狀態集的並集?
把兩個int型整數按位或 |即可。

int x;
int y;
x|=y;

如何把一個狀態加入狀態集?
等價於如何把某二進位制位置為1

int s;//表示一個現有狀態集
int x;//需要把x加入狀態集
x |= (1<<x)

如何遍歷狀態集?
等價於遍歷二進位制位

int x;
for(int i=0;i < 32;i++){
	printf("%d",(x>>i)&1)
}

程式碼具體實現

#include <iostream>
#include <fstream>
#include <queue>
#include <cstring>
#include <map>
using namespace std;
struct Node{//NFA節點
	int s;
	bool flag;//標記一個DFA節點集合是否包含NFA結束態,即表示DFA中是否是一個結束態 
	Node(int ss,bool f){
		s = ss;
		flag = f;
	}
};
struct Edge{//DFA邊 
	int from,to;
	char c;
	Edge(int x,int y,char z){
		from = x;
		to = y;
		c = z;
	}
};
const int MAX = 101;
int zf;//字元數 
char zfj[MAX];//字符集 
int zt;//狀態數,0開始 
int Begin,End;//NFA起始和結束態
char G[MAX][MAX]; //存NFA邊 
int vis[MAX];//標記NFA狀態是否被訪問,求閉包用到 
vector<Node> V;//DFA節點集 
vector<Edge> edge;//DFA邊集 
int eps_clos[MAX];//求過的閉包儲存以後用 
int E_closure(int x)
{
	if(eps_clos[x]!=-1) 
	return eps_clos[x];
	queue<int> q;
	memset(vis,0,sizeof(vis));
	int S = 0;
	S = S | (1<<x);
	q.push(x);
	while(!q.empty()){
		int v = q.front();
		q.pop();
		for(int w = 0;w < zt;w++){
			if(G[v][w]=='#'&&!vis[w]){
				vis[w] = 1;
				S |= (1 << w);
				q.push(w);
			}		
		}
	}
	eps_clos[x] = S;
	return S;
}
int set_edge(int s,char c)
{
	int i,j;
	int S = 0;
	for(i = 0;i < zt;i++){
		if((s>>i)&1){
			for(j = 0;j < zt;j++){
				if(G[i][j]==c)
				S |= E_closure(j);
			}
		}
	}
	return S;
}
bool check(int s)//檢查DFA節點集是否出現過 
{
	for(int i = 0;i < V.size();i++){
		if(V[i].s == s) return true;
	}
	return false;
}
bool is_end(int s)
{
	return (s>>End) & 1;
}
void ZJGZ()//子集構造演算法 
{
	int i;
	queue<int> work;
	work.push(E_closure(0));
	V.push_back(Node(E_closure(0),is_end(E_closure(0))));//加入DFA節點集 
	while(!work.empty()){
		int v = work.front();
		work.pop();
		for(i = 0;i < zf;i++){//遍歷字符集 
			int s = set_edge(v,zfj[i]);//生成NFA吃完該字元所能到達的所有狀態 
			if(s!=0){
				edge.push_back(Edge(v,s,zfj[i]));
				if(!check(s)){
					V.push_back(Node(s,is_end(s)));
					work.push(s);
				}
			} 	
		}
	}
}
int main()
{
	memset(eps_clos,-1,sizeof(eps_clos));
	ifstream in("nfa.txt");
	in >> zf;
	for(int i = 0;i < zf;i++)
		in >> zfj[i];
	in >> zt;
	in >> Begin >> End;
	int n;//NFA邊數 
	in >> n;
	int x,y;
	char c;
	for(int i = 0;i < n;i++){
		in >> x >> c >> y;
		G[x][y] = c;
	}
	ZJGZ();
	map<int,int> mp;//DFA狀態集對映到從0開始的狀態標號 
	for(int i = 0;i < V.size();i++){
		cout << i << ' '<<V[i].flag << endl;
		mp[V[i].s] = i;
	}
	for(int i = 0;i < edge.size();i++)
	cout << mp[edge[i].from] << ' ' << mp[edge[i].to] << ' ' << edge[i].c << endl;
}
  

執行結果

在這裡插入圖片描述在這裡插入圖片描述

相關文章