一、實驗目的
編寫一個C++語言程序,模擬實現DFA識別字符串的過程。
二、實驗內容
通過實驗教學,加深學生對所學的關於編譯的理論知識的理解,增強學生對所學知識的綜合應用能力,並通過實踐達到對所學的知識進行驗證。通過對DFA模擬程序實驗,使學生掌握詞法分析的實現技術,及具體實現方法。通過本實驗加深對詞法分析程序的功能及實現方法的理解。
三、實驗環境
供Windows7系統下的PC機,DEV C++軟件,語言:C++。
四、實驗內容
-
定義一個右線性正規文法,示例如(僅供參考)
G[S]:S→aU|bV U→bV|aQ
V→aU|bQ Q→aQ|bQ|e
實驗前要考慮清楚用哪種數據結構存儲上述文法。 -
構造其有窮確定自動機,如
-
利用有窮確定自動機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)鍵盤輸入:
- /*
- 8//圖中的關係
- 0 3//初態結點與終態結點
- 0 a 1//狀態轉換圖
- 0 b 2
- 1 b 2
- 1 a 3
- 2 a 1
- 2 b 3
- 3 a 3
- 3 b 3
- ababab
- */
(2) 通過鄰接矩陣來存儲狀態圖,並且在存儲前初始化鄰接矩陣中所有值爲-1,-1即表示狀態之間沒有關係。代碼如下:
N表示有轉換函數的個數,所以運行時鍵盤輸入關係個數,上述例子中共有8個關係,所以輸入8.
- for(int i=0;i<N;i++)
-
for(int j=0;j<N;j++)
-
Map[i][j]=-1;//初始化鄰接矩陣
-
for(int i=0;i<n;i++){
-
cin>>s0>>alpha>>s1;
-
if(alphaSet.find(alpha)==alphaSet.end()){//判斷當前字符是否已經轉換爲對應的索引
-
alpha2idx[alpha]=idx4alpha;
-
alphaSet.insert(alpha);
-
idx4alpha++;
-
}
-
Map[s0][alpha2idx[alpha]]=s1;//儲存轉換函數
(3)遍歷整個矩陣,查看輸入的字符串是否存在轉換關係,若存在則輸出true,並且輸出接收態的過程,否則輸出false代碼如下:
- while(cin>>str){
-
int curState=s_begin;//初始狀態
-
cout<<curState<<"->";
-
int len = str.size();
-
bool flag=true;
-
for(int i=0;i<len;i++){
-
if(alphaSet.find(str[i])==alphaSet.end()){//判斷當前字母是否在字母表裏面
-
flag=false;
-
break;
-
}
-
else{
-
i!=len-1?cout<<Map[curState][alpha2idx[str[i]]]<<"->":cout<<Map[curState][alpha2idx[str[i]]];
-
if(Map[curState][alpha2idx[str[i]]]!=-1){//轉換函數存在
-
curState=Map[curState][alpha2idx[str[i]]];//更新當前狀態
-
}
-
else{//不存在對應的轉換關係
-
flag=false;
-
break;
-
}
-
}
-
}
-
cout<<endl;
-
//判斷最後是否達到了結束狀態
-
if(curState==s_end&&flag){
-
cout<<"True"<<endl;
-
}
-
else{
-
cout<<"False"<<endl;
-
}
八、實驗存在的問題以及待改進的地方
1)實驗問題描述:用鄰接矩陣存儲狀態圖時,沒有初始化,雖然編譯通過,但是運行結果與預期不相符。
解決措施:通過分析發現是沒有初始化鄰接矩陣,添加代碼如下:
- for(int i=0;i<N;i++)
-
for(int j=0;j<N;j++)
-
Map[i][j]=-1;//初始化鄰接矩陣
(編程時存在許多問題,但在此處只例舉一個)
實驗存在的不足:
① 初態和終態在該代碼中只能有一個,無法實現多個終態的狀態圖。
② 實驗給出的狀態圖是採取字母表示狀態,而爲了方便採取鄰接矩陣,直接用數字來描述,二者其實同一個原理,但是在實驗初期預料我不知道如何用字母來建立鄰接矩陣,存儲狀態圖的數據結構選取的鄰接矩陣,後來通過詢問同學,發現可以用圖來存儲,更加直觀,所有着也是問題考慮不周的地方,總的來說是數據結構沒學好,無法實現圖的數據結構來編程實現DFA。
九、實驗心得
此次實驗相對來說難度不是很大,主要是考慮用什麼方式來存儲狀態圖,並且用相對應的數據結構以及算法來實現DFA M是否是可接收的,但是在初次考慮這個問題的時候,我並沒有想到用鄰接矩陣來存儲,因爲趕時間直接採用了二維數組來存儲,枚舉輸入字符串的個數中所有的可接受的關係,並且查看輸入的字符串是否在這個關係表裏,實現比較簡單,但是複雜度過高,其中輸入格式沒有設計好,花了許多時間,實驗二基本沒寫,這是此次實驗的最大不足之處。
通過此次實驗,我進一步對確定的有窮自動機(DFA)有了一定的理解,並且重溫了數據結構。