編譯原理-DFA模擬程序

一、實驗目的

編寫一個C++語言程序,模擬實現DFA識別字符串的過程。

二、實驗內容

通過實驗教學,加深學生對所學的關於編譯的理論知識的理解,增強學生對所學知識的綜合應用能力,並通過實踐達到對所學的知識進行驗證。通過對DFA模擬程序實驗,使學生掌握詞法分析的實現技術,及具體實現方法。通過本實驗加深對詞法分析程序的功能及實現方法的理解。

三、實驗環境

供Windows7系統下的PC機,DEV C++軟件,語言:C++。

四、實驗內容

  1. 定義一個右線性正規文法,示例如(僅供參考)
    G[S]:S→aU|bV U→bV|aQ
    V→aU|bQ Q→aQ|bQ|e
    實驗前要考慮清楚用哪種數據結構存儲上述文法。

  2. 構造其有窮確定自動機,如在這裏插入圖片描述

  3. 利用有窮確定自動機M=(K,Σ,f, S,Z)行爲模擬程序算法,來對於任意給定的串,若屬於該語言時,該過程經有限次計算後就會停止並回答“是”,若不屬於,要麼能停止並回答“不是”。

    K:=S;  
	c:=getchar;  
	while c<>eof do   
	{K:=f(K,c);     
	  c:=getchar;       };  
	if K is in Z then return (‘yes’)  
	else return (‘no’) 

五、實驗原理

用類似於鄰接矩陣的二維數組M存儲上述的圖結構。首先定義轉換函數:
$T:S_i×c → S_j, c∈\Sigma $
該矩陣的行索引表示結點Si′,列索引爲對應的字母c,c爲字母表中的元素,c′ 表示該字符映射之後所得到的下標編號,因此對於數組M中的元素有如下形式M[Si][c′]=Sj ,之所以採用該存儲方式,而不採用鄰接矩陣是因爲該種方法可以保證在O(1)的時間複雜度內查找到對應轉換的下一個狀態。定義完上述的存儲結構之後,便可以方便地描述整個算法的思路:
算法步驟:
①將當前狀態Scurrent設爲自動機的起始狀態Sbegin
②根據輸入的字符串中的當前字符ccurrent 對當前狀態進行裝換Scurrent=M[Scurrent][c′current]
③循環遍歷字符串中的每個字符,重複步驟②,如果中間出現無法裝換或者字符串遍歷結束後未到達終止狀態Send則輸入的字符串不匹配。如果字符串匹配完成之後,有Scurrent 等於Send,則匹配成功。

六、實驗代碼

1.	/** 
2.	通過鄰接矩陣的形式構造確定性有窮自動機DFA
3.	判斷輸入的字符串都否合法 
4.	*/  
5.	#include <iostream>  
6.	#include <string>  
7.	#include <set>  
8.	#include <map>  
9.	#define N 10  
10.	using namespace std;  
11.	map<char,int> alpha2idx;    
12.	int Map[N][N];    
13.	set<char> alphaSet;//定義字母表,存儲所有符號的集合,用於將符號映射到索引  
14.	  
15.	int main()  
16.	{  
17.	    int n;//表示有轉換函數的個數  
18.	    int s_begin,s_end;//起始狀態和結束狀態  
19.	    int s0,s1;  
20.	    string str;//待識別的字符串  
21.	    cin>>n;  
22.	    cin>>s_begin>>s_end;  
23.	    char alpha;  
24.	    int idx4alpha=0;  
25.	    for(int i=0;i<N;i++)  
26.	        for(int j=0;j<N;j++)  
27.	            Map[i][j]=-1;//初始化鄰接矩陣  
28.	  
29.	    for(int i=0;i<n;i++){  
30.	        cin>>s0>>alpha>>s1;  
31.	        if(alphaSet.find(alpha)==alphaSet.end()){//判斷當前字符是否已經轉換爲對應的索引  
32.	            alpha2idx[alpha]=idx4alpha;  
33.	            alphaSet.insert(alpha);  
34.	            idx4alpha++;  
35.	        }  
36.	        Map[s0][alpha2idx[alpha]]=s1;//儲存轉換函數  
37.	    }  
38.	  
39.	      //打印鄰接矩陣  
40.	//    for(int i=0;i<N;i++){  
41.	//        for(int j=0;j<idx4alpha;j++){  
42.	//            cout<<Map[i][j]<<" ";  
43.	//        }  
44.	//        cout<<endl;  
45.	//    }  
46.	  
47.	    while(cin>>str){  
48.	        int curState=s_begin;//初始狀態  
49.	        cout<<curState<<"->";  
50.	        int len = str.size();  
51.	        bool flag=true;  
52.	        for(int i=0;i<len;i++){  
53.	            if(alphaSet.find(str[i])==alphaSet.end()){//判斷當前字母是否在字母表裏面  
54.	                flag=false;  
55.	                break;  
56.	            }  
57.	            else{  
58.	                i!=len-1?cout<<Map[curState][alpha2idx[str[i]]]<<"->":cout<<Map[curState][alpha2idx[str[i]]];  
59.	  
60.	                if(Map[curState][alpha2idx[str[i]]]!=-1){
61.	//轉換函數存在  
62.	                    curState=Map[curState][alpha2idx[str[i]]];
63.	//更新當前狀態  
64.	                }  
65.	                else{//不存在對應的轉換關係  
66.	                    flag=false;  
67.	                    break;  
68.	                }  
69.	            }  
70.	        }  
71.	        cout<<endl;  
72.	        //判斷最後是否達到了結束狀態  
73.	        if(curState==s_end&&flag){  
74.	            cout<<"True"<<endl;  
75.	        }  
76.	        else{  
77.	            cout<<"False"<<endl;  
78.	        }  
79.	  
80.	    }  
81.	  
82.	    return 0;  
83.	}  
84.	//測試數據:   
85.	/*  
86.	8 
87.	0 3 
88.	0 a 1 
89.	0 b 2 
90.	1 b 2 
91.	1 a 3 
92.	2 a 1 
93.	2 b 3 
94.	3 a 3 
95.	3 b 3 
96.	ababab  
97.	*/  
98.	//第二組測試數據   
99.	/* 
100.	9 
101.	0 4 
102.	0 a 1 
103.	0 b 2 
104.	1 a 1 
105.	1 b 3 
106.	2 b 2 
107.	2 a 1 
108.	3 a 1 
109.	3 b 4 
110.	4 a 1 
111.	aaaaaaabb 
112.	*/  


七、實驗結論

實驗代碼運行結果:

結果描述
(1)鍵盤輸入:

  1. /*
  2. 8//圖中的關係
  3. 0 3//初態結點與終態結點
  4. 0 a 1//狀態轉換圖
  5. 0 b 2
  6. 1 b 2
  7. 1 a 3
  8. 2 a 1
  9. 2 b 3
  10. 3 a 3
  11. 3 b 3
  12. ababab
  13. */

(2) 通過鄰接矩陣來存儲狀態圖,並且在存儲前初始化鄰接矩陣中所有值爲-1,-1即表示狀態之間沒有關係。代碼如下:
N表示有轉換函數的個數,所以運行時鍵盤輸入關係個數,上述例子中共有8個關係,所以輸入8.

  1. for(int i=0;i<N;i++)
  2.     for(int j=0;j<N;j++)  
    
  3.         Map[i][j]=-1;//初始化鄰接矩陣  
    
  4. for(int i=0;i<n;i++){  
    
  5.     cin>>s0>>alpha>>s1;  
    
  6.     if(alphaSet.find(alpha)==alphaSet.end()){//判斷當前字符是否已經轉換爲對應的索引  
    
  7.         alpha2idx[alpha]=idx4alpha;  
    
  8.         alphaSet.insert(alpha);  
    
  9.        idx4alpha++;  
    
  10.    }  
    
  11.    Map[s0][alpha2idx[alpha]]=s1;//儲存轉換函數  
    

(3)遍歷整個矩陣,查看輸入的字符串是否存在轉換關係,若存在則輸出true,並且輸出接收態的過程,否則輸出false代碼如下:

  1. while(cin>>str){
  2.   int curState=s_begin;//初始狀態  
    
  3.   cout<<curState<<"->";  
    
  4.   int len = str.size();  
    
  5.   bool flag=true;  
    
  6.   for(int i=0;i<len;i++){  
    
  7.       if(alphaSet.find(str[i])==alphaSet.end()){//判斷當前字母是否在字母表裏面  
    
  8.           flag=false;  
    
  9.           break;  
    
  10.      }  
    
  11.      else{  
    
  12.          i!=len-1?cout<<Map[curState][alpha2idx[str[i]]]<<"->":cout<<Map[curState][alpha2idx[str[i]]];  
    
  13.          if(Map[curState][alpha2idx[str[i]]]!=-1){//轉換函數存在  
    
  14.              curState=Map[curState][alpha2idx[str[i]]];//更新當前狀態  
    
  15.          }  
    
  16.          else{//不存在對應的轉換關係  
    
  17.              flag=false;  
    
  18.              break;  
    
  19.          }  
    
  20.      }  
    
  21.  }  
    
  22.  cout<<endl;  
    
  23.  //判斷最後是否達到了結束狀態  
    
  24.  if(curState==s_end&&flag){  
    
  25.      cout<<"True"<<endl;  
    
  26.  }  
    
  27.  else{  
    
  28.      cout<<"False"<<endl;  
    
  29.  }  
    

八、實驗存在的問題以及待改進的地方

1)實驗問題描述:用鄰接矩陣存儲狀態圖時,沒有初始化,雖然編譯通過,但是運行結果與預期不相符。

解決措施:通過分析發現是沒有初始化鄰接矩陣,添加代碼如下:

  1. for(int i=0;i<N;i++)
  2.  for(int j=0;j<N;j++)  
    
  3.      Map[i][j]=-1;//初始化鄰接矩陣 
    

(編程時存在許多問題,但在此處只例舉一個)
實驗存在的不足:
① 初態和終態在該代碼中只能有一個,無法實現多個終態的狀態圖。
② 實驗給出的狀態圖是採取字母表示狀態,而爲了方便採取鄰接矩陣,直接用數字來描述,二者其實同一個原理,但是在實驗初期預料我不知道如何用字母來建立鄰接矩陣,存儲狀態圖的數據結構選取的鄰接矩陣,後來通過詢問同學,發現可以用圖來存儲,更加直觀,所有着也是問題考慮不周的地方,總的來說是數據結構沒學好,無法實現圖的數據結構來編程實現DFA。

九、實驗心得

此次實驗相對來說難度不是很大,主要是考慮用什麼方式來存儲狀態圖,並且用相對應的數據結構以及算法來實現DFA M是否是可接收的,但是在初次考慮這個問題的時候,我並沒有想到用鄰接矩陣來存儲,因爲趕時間直接採用了二維數組來存儲,枚舉輸入字符串的個數中所有的可接受的關係,並且查看輸入的字符串是否在這個關係表裏,實現比較簡單,但是複雜度過高,其中輸入格式沒有設計好,花了許多時間,實驗二基本沒寫,這是此次實驗的最大不足之處。
通過此次實驗,我進一步對確定的有窮自動機(DFA)有了一定的理解,並且重溫了數據結構。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章