【編譯原理實驗】Lab2(一)NFA確定化
一、實驗目的
學習和掌握將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;
}
執行結果
相關文章
- 編譯原理: Thompson 構造法(正規表示式 轉 NFA)編譯原理
- 編譯原理實驗2:語法分析編譯原理語法分析
- 編譯原理實驗1:詞法分析編譯原理詞法分析
- 小C語言--詞法分析程式(編譯原理實驗一)C語言詞法分析編譯原理
- Typescript編譯原理(一)TypeScript編譯原理
- 識別浮點常量問題(編譯原理實驗二)編譯原理
- 從編譯原理看一個直譯器的實現編譯原理
- 《網路安全原理與實踐》一1.2資產確定
- 2017春季學期編譯原理期末實驗報告編譯原理
- 編譯原理編譯原理
- ucore作業系統lab2實驗報告作業系統
- Flutter 編譯原理Flutter編譯原理
- 編譯原理概述編譯原理
- 用實驗的思路優化webpack4專案編譯速度優化Web編譯
- 電路原理實驗一
- 編譯原理概覽編譯原理
- Vue 模板編譯原理Vue編譯原理
- 白話編譯原理編譯原理
- 深入理解flutter的編譯原理與優化Flutter編譯原理優化
- 編譯配置的一些經驗編譯
- 編譯原理第一章作業編譯原理
- Taro編譯打包優化實踐編譯優化
- 編譯原理入門篇|一篇文章理解編譯全過程編譯原理
- 模板函式編譯原理函式編譯原理
- 編譯原理與javacc初探編譯原理Java
- 《編譯原理》學習心得編譯原理
- 帶你深入 Dart 解析一個有趣的引用和編譯實驗Dart編譯
- 編譯核心的一點點經驗(轉)編譯
- 實現一個可定製化的FlowLayout -- 原理篇
- 前端與編譯原理——用 JS 寫一個 JS 直譯器前端編譯原理JS
- 前端與編譯原理——用JS寫一個JS直譯器前端編譯原理JS
- 編譯原理實戰入門:用 JavaScript 寫一個簡單的四則運算編譯器(修訂版)編譯原理JavaScript
- 編譯領域裡程碑之作:龍書《編譯原理》編譯原理
- Java 實現《編譯原理》簡單詞法分析功能Java編譯原理詞法分析
- vue模板編譯(原理篇)Vue編譯
- 深入分析 Javac 編譯原理Java編譯原理
- 編譯原理讀書筆記編譯原理筆記
- 【編譯原理】語法分析(三)編譯原理語法分析