regex -> 逆波兰表达式 -> NFA -> DFA

本文介绍了如何利用DFA解决LeetCode中的正则表达式问题,步骤包括后序到中序表达式转换、Thompson算法构建NFA、NFA转DFA,并通过实例演示了字符串匹配的过程。关键步骤涉及字符优先级处理和自动机构造技术。
  1 class Solution {
  2 public:
  3 
  4     /*
  5     * 利用DFA解决leetcode 10.正则表达式 问题
  6     * 
  7     * 步骤:
  8     * 1. 将正则字符串视为后序遍历表达式,利用栈和运算符优先级转换为
  9     *    中序遍历表达式,即逆波兰表达式。
 10     * 2. 使用中序串通过Thompson算法构造NFA。
 11     * 3. 将NFA使用子集构造算法转换为DFA。
 12     * 4. 将给定串s输入DFA模拟所有状态,若出现s读取完毕且处于结束节点则返回true。
 13     * 
 14     * 注意:
 15     * 为了简单起见,所有的new都没有进行delete。
 16     * 
 17     * 参考:
 18     * 第1步可以参考台湾清华大学 韩永楷 《数据结构》课程 第7.2 - 7.3课
 19     * 第2-4步可以参考中科大 华保健 《编译原理》课程 第2.3.1 - 3.2.4课
 20     */
 21 
 22     // NFA使用的节点
 23     struct Node;
 24 
 25     // NFA使用的路径
 26     struct Path
 27     {
 28         char c;
 29         Node * dest;
 30         Path(char in_c, Node* in_dest) :c(in_c), dest(in_dest) {}
 31     };
 32 
 33     struct Node
 34     {
 35         int index;
 36         vector<Path> pathes;
 37         Node()
 38         {
 39             static int i = 0;
 40             index = i++;
 41         }
 42         Node(vector<Path> in_pathes) :pathes(in_pathes) {}
 43         void AddPath(char c, Node* dest)
 44         {
 45             pathes.emplace_back(c, dest);
 46         }
 47     };
 48 
 49     // 将后序遍历正则串转换为中序遍历
 50     // 使用建立ExpressionTree的方法
 51     // 字符连接则添加一个&作为运算符
 52     queue<char> ToInOrder(string s)
 53     {
 54         int len = s.length();
 55         queue<char> ans;
 56         stack<char> symbols; // 符号栈
 57         bool prevIsChar = false;
 58 
 59         // 将低优先级的运算符全部弹出至ans
 60         auto PopLowPriority = [&]()
 61         {
 62             while (!symbols.empty())
 63             {
 64                 ans.push(symbols.top());
 65                 symbols.pop();
 66             }
 67 
 68         };
 69 
 70         for (int i = 0; i < len; ++i)
 71         {
 72             char c = s[i];
 73             if (c == '*')
 74             {
 75                 if (!symbols.empty() && symbols.top() == '&')
 76                 {
 77                     symbols.push(c);
 78                 }
 79                 else
 80                 {
 81                     PopLowPriority();
 82                     symbols.push(c);
 83                 }
 84                 prevIsChar = true;
 85                 continue;
 86             }
 87 
 88             if (prevIsChar)
 89             {
 90                 PopLowPriority();
 91                 symbols.push('&');
 92             }
 93 
 94             ans.push(c);
 95 
 96             //
 97             if (i + 1 < len && s[i + 1] != '*')
 98                 prevIsChar = true;
 99             else
100                 prevIsChar = false;
101         }
102 
103         PopLowPriority();
104 
105         return ans;
106     }
107 
108     struct Tree
109     {
110         Node* start,*end;
111     };
112 
113     // 使用Thompson算法,将中序的串构造成NFA
114     Tree CalcInOrder(queue<char> inorder)
115     {
116         stack<Tree> temp;
117 
118         while (!inorder.empty())
119         {
120             char c = inorder.front();
121             inorder.pop();
122 
123             if (c == '*')
124             {
125                 auto &tree = temp.top();
126 
127                 Node* oldStart = tree.start;
128                 Node* oldEnd = tree.end;
129 
130                 Node* newStart = new Node;
131                 Node* newEnd = new Node;
132                 newStart->AddPath(0, oldStart);
133                 newStart->AddPath(0, newEnd);
134                 oldEnd->AddPath(0, oldStart);
135                 oldEnd->AddPath(0, newEnd);
136 
137                 tree.start = newStart;
138                 tree.end = newEnd;
139                 continue;
140             }
141 
142             if (c == '&')
143             {
144                 auto e2 = temp.top();temp.pop();
145                 auto e1 = temp.top(); temp.pop();
146 
147                 Tree tree;
148                 tree.start = e1.start;
149                 tree.end = e2.end;
150                 e1.end->AddPath(0, e2.start);
151 
152                 temp.push(tree);
153                 continue;
154             }
155 
156             Node* node1 = new Node;
157             Node* node2 = new Node;
158             node1->AddPath(c, node2);
159             temp.push({ node1,node2 });
160         }
161         return temp.top();
162     }
163 
164     // 闭包函数
165     // 返回:一个节点的闭包,即
166     //       一个节点通过ε路径能够达到的所有节点的集合(包括自身)
167     set<Node*> e_closure(Node* start)
168     {
169         set<Node*> ans;
170         queue<Node*> q;
171         q.push(start);
172         while (!q.empty())
173         {
174             auto node = q.front();
175             q.pop();
176 
177             ans.insert(node);
178 
179             for (auto& path : node->pathes)
180                 if (path.c == 0 && ans.find(path.dest) == ans.end())
181                     q.push(path.dest);
182         }
183         return ans;
184     }
185 
186     // DFA使用的节点结构
187     struct FinalNode;
188     struct FinalPath
189     {
190         char c;
191         FinalNode* dest;
192         FinalPath(char c, FinalNode* dest) :c(c), dest(dest) {}
193     };
194 
195     struct FinalNode
196     {
197         int index;
198         bool isEnd;
199         vector<FinalPath> pathes;
200         FinalNode(bool isEnd) :isEnd(isEnd)
201         {
202             static int i = 0;
203             index = i++;
204         }
205         void AddPath(char c, FinalNode *dest)
206         {
207             pathes.emplace_back(c,dest);
208         }
209     };
210 
211     using FinalTree = FinalNode*;
212 
213     // 子集构造算法 将NFA转换为DFA
214     FinalTree BuildSubSet(const Tree tree)
215     {
216         // 返回一个节点是否为end节点
217         auto IsEnd = [&](Node* node)->bool
218         {
219             return node == tree.end;
220         };
221 
222         // 返回一个集合中是否有end节点
223         auto SetIsEnd = [&](const set<Node*>& nodeSet)->bool
224         {
225             return nodeSet.find(tree.end) != nodeSet.end();
226         };
227 
228         set<Node*> q0;
229         q0 = e_closure(tree.start);
230 
231         FinalNode* n0 = new FinalNode(SetIsEnd(q0));
232         map<set<Node*>, FinalNode*> newTreeDict;
233         newTreeDict[q0] = n0;
234 
235         FinalTree newTree;
236         newTree = n0;
237 
238         set<set<Node*>> Q;
239         Q.insert(q0);
240 
241         queue<set<Node*>> worklist;
242         worklist.push(q0);
243         while (!worklist.empty())
244         {
245             auto q = worklist.front();
246             worklist.pop();
247 
248             // 
249             map<char, set<Node*>> dict;
250             for (auto node : q)
251             {
252                 for (auto& path : node->pathes)
253                 {
254                     if (path.c != 0)
255                     {
256                         dict[path.c].insert(path.dest);
257                     }
258                 }
259             }
260 
261             for (auto& pr : dict)
262             {
263                 for (auto node : pr.second)
264                 {
265                     auto t = e_closure(node);
266                     FinalNode* node_t = nullptr;
267                     if (newTreeDict.find(t) != newTreeDict.end())
268                         node_t = newTreeDict[t];
269                     else
270                     {
271                         node_t=new FinalNode(SetIsEnd(t));
272                         newTreeDict[t] = node_t;
273                     }
274 
275                     //
276                     FinalNode* node_q = newTreeDict[q];
277                     node_q->AddPath(pr.first, node_t);
278 
279                     if (Q.find(t) == Q.end())
280                     {
281                         Q.insert(t);
282                         worklist.push(t);
283                     }
284                 }
285             }
286         }
287         return newTree;
288     }
289 
290     // 遍历DFA,确定字符串是否匹配
291     bool IsMatch(string s, FinalTree dfa)
292     {
293         struct State
294         {
295             int spos; // 当前的字符串序号
296             FinalNode* node; // 当前所在的节点
297         };
298 
299         int len = s.length();
300         State state0 = { -1,dfa };
301         queue<State> q;
302         q.push(state0);
303 
304         while (!q.empty())
305         {
306             auto state = q.front();
307             q.pop();
308 
309             // 若字符串已读取到头,且当前节点为末尾,则返回true
310             if (state.spos == len-1)
311             {
312                 if (state.node->isEnd)
313                     return true;
314 
315                 // 只是字符串读到头,但节点不是尾节点
316                 continue;
317             }
318 
319             for (auto& path : state.node->pathes)
320             {
321                 // 若下一个字符 和路径匹配,或者该路径匹配一个'.'
322                 if (path.c == s[state.spos + 1] || path.c=='.')
323                 {
324                     // 加入队列
325                     q.push({ state.spos + 1,path.dest });
326                 }
327             }
328         }
329         return false;
330     }
331 
332     bool isMatch(string s, string p)
333     {
334         if (p.empty()) return s.empty();
335         auto q = ToInOrder(p);
336         auto tree = CalcInOrder(q);
337         auto DFA = BuildSubSet(tree);
338 
339         return IsMatch(s, DFA);
340     }
341 };
应用程序执行异常,返回值:-1073741819。请修改下列代码,要在所给的代码的基础上修改#include "NFAToDFA.h" #include "RegexpToPost.h" #include "PostToNFA.h" #include "NFAStateStack.h" #include "NFAFragmentStack.h" #include "OutputResult.h" #include <stdlib.h> #define MAX_STATE_NUM 1024 // 增大数组容量 NFAFragmentStack FragmentStack; // 栈。用于储存 NFA 片段 NFAStateStack StateStack; // 栈。用于储存 NFA 状态 const char VoidTrans = '$'; // 表示空转换 char regexp[256]; int main(int argc, char *argv[]) { char *post; DFA* dfa = (DFA*)malloc(sizeof(DFA)); dfa->length = 0; // // 初始化栈 // InitNFAFragmentStack(&FragmentStack); // 在input1.txt ~ input3.txt文件中包含了不同的正则表达式案例。 // 程序开始运行时会通过命令行参数(例如 app.exe < input1.txt)将标准输入(stdin)重定向到一个input文件。 // 所以这里调用scanf函数就可以读取到input文件中的正则表达式了。 scanf("%255s", regexp); // // 调用 re2post 函数将正则表达式字符串转换成解析树的后序遍历序列 // post = re2post(regexp); // // 调用 post2dfa 函数将解析树的后序遍历序列转换为 DFA // dfa = post2dfa(dfa, post); // // 将 DFA 打印输出 // OutputResult(dfa); return 0; } /* 功能: 创建一个 DFA 状态的转换。 参数: TransformChar -- 转换符号。 NFAStateArray -- NFA 状态指针数组。 Count -- 数组元素个数。 返回值: Transform 结构体指针。 */ Transform* CreateDFATransform(char TransformChar, NFAState** NFAStateArray, int Count) { int i; Transform* pTransform = (Transform*)malloc(sizeof(Transform)); for (i=0; i<Count; i++) { pTransform->NFAlist[i] = NFAStateArray[i]; } pTransform->NFAStateCount = Count; pTransform->TransformChar = TransformChar; pTransform->DFAStateIndex = -1; pTransform->NextTrans = NULL; return pTransform; } /* 功能: 创建一个 DFA 状态。 参数: pTransform -- DFA 状态转换指针。 返回值: DFAState 结构体指针。 */ DFAState* CreateDFAState(Transform* pTransform) { int i; DFAState* pDFAState = (DFAState*)malloc(sizeof(DFAState)); for (i=0; i<pTransform->NFAStateCount; i++) { pDFAState->NFAlist[i] = pTransform->NFAlist[i]; } pDFAState->NFAStateCount = pTransform->NFAStateCount; pDFAState->firstTran = NULL; return pDFAState; } /* 功能: 判断一个转换中的 NFA 状态集合是否为某一个 DFA 状态中 NFA 状态集合的子集。 参数: pDFA -- DFA 指针。 pTransform -- DFA 状态转换指针。 返回值: 如果存在返回 DFA 状态下标,不存在返回 -1。 */ int NFAStateIsSubset(DFA* pDFA, Transform* pTransform) { int i,j,k; for(i=0;i<pDFA->length;i++){ int cnt=0; for(j=0;j<pTransform->NFAStateCount;j++){ for(k=0;k<pDFA->DFAlist[i]->NFAStateCount;k++){ if(pTransform->NFAlist[j]==pDFA->DFAlist[i]->NFAlist[k]) { cnt++; break; } } } if(cnt==pTransform->NFAStateCount) { return i; } } return -1; } /* 功能: 判断某个 DFA 状态的转换链表中是否已经存在一个字符的转换。 参数: pDFAState -- DFAState 指针。 TransformChar -- 转换标识符。 返回值: Transform 结构体指针。 */ Transform* IsTransformExist(DFAState* pDFAState, char TransformChar) { Transform* current; for(current=pDFAState->firstTran;current != NULL;current=current->NextTrans) { if(current->TransformChar==TransformChar) { return current; } } return NULL; } /* 功能: 将一个 NFA 集合合并到一个 DFA 转换中的 NFA 集合中。 注意,合并后的 NFA 集合中不应有重复的 NFA 状态。 参数: NFAStateArray -- NFA 状态指针数组,即待加入的 NFA 集合。 Count -- 待加入的 NFA 集合中元素个数。 pTransform -- 转换指针。 */ void AddNFAStateArrayToTransform(NFAState** NFAStateArray, int Count, Transform* pTransform) { for (int i = 0; i < Count; i++) { // 检查数组是否已满 if (pTransform->NFAStateCount >= MAX_STATE_NUM) { fprintf(stderr, "Transform NFAlist overflow!\n"); exit(EXIT_FAILURE); } // 检查并添加元素 int found = 0; for (int j = 0; j < pTransform->NFAStateCount; j++) { if (pTransform->NFAlist[j] == NFAStateArray[i]) { found = 1; break; } } if (!found) { pTransform->NFAlist[pTransform->NFAStateCount++] = NFAStateArray[i]; } } } /* 功能: 使用二叉树的先序遍历算法求一个 NFA 状态的ε-闭包。 参数: State -- NFA 状态指针。从此 NFA 状态开始求ε-闭包。 StateArray -- NFA 状态指针数组。用于返回ε-闭包。 Count -- 元素个数。 用于返回ε-闭包中 NFA 状态的个数。 */ void DFS(NFAState* State,NFAState** StateArray,int* Count) { StateArray[*Count]=State; (*Count)++; if(State->Next1 !=NULL && State->Transform=='$') { DFS(State->Next1,StateArray,Count); } if(State->Next2 !=NULL && State->Transform =='$') { DFS(State->Next2,StateArray,Count); } } void Closure(NFAState* State, NFAState** StateArray, int* Count) { InitNFAStateStack(&StateStack); DFS(State,StateArray,Count); } /* 功能: 将解析树的后序遍历序列转换为 DFA。 参数: pDFA -- DFA 指针。 postfix -- 正则表达式的解析树后序遍历序列。 返回值: DFA 指针。 */ NFAState* Start = NULL; DFA* post2dfa(DFA* pDFA, char *postfix) { int i, j; // 游标 Transform* pTFCursor; // 转换指针 NFAState* NFAStateArray[MAX_STATE_NUM]; // NFA 状态指针数组。用于保存ε-闭包 int Count = 0; // ε-闭包中元素个数 // // 调用 post2nfa 函数将解析树的后序遍历序列转换为 NFA 并返回开始状态 // Start = post2nfa(postfix); // // 调用 Closure 函数构造开始状态的ε-闭包 // Closure(Start, NFAStateArray, &Count); // 调用 CreateDFATransform 函数创建一个转换(忽略转换字符), // 然后,调用 CreateDFAState 函数,利用刚刚创建的转换新建一个 DFA 状态 Transform* pTransform = CreateDFATransform('\0', NFAStateArray, Count); DFAState* pDFAState = CreateDFAState(pTransform); // 将 DFA 状态加入到 DFA 状态线性表中 pDFA->DFAlist[pDFA->length++] = pDFAState; // 遍历线性表中所有 DFA 状态 for(i=0; i<pDFA->length; i++) { // 遍历第 i 个 DFA 状态中的所有 NFA 状态 for(j=0; j<pDFA->DFAlist[i]->NFAStateCount; j++) { NFAState* NFAStateTemp = pDFA->DFAlist[i]->NFAlist[j]; // 如果 NFA 状态是接受状态或者转换是空转换,就跳过此 NFA 状态 if(NFAStateTemp->Transform == VoidTrans || NFAStateTemp->AcceptFlag == 1) continue; // 调用 Closure 函数构造 NFA 状态的ε-闭包 Closure(NFAStateTemp->Next1, NFAStateArray, &Count); // 调用 IsTransfromExist 函数判断当前 DFA 状态的转换链表中是否已经存在该 NFA 状态的转换 pTransform = IsTransformExist(pDFA->DFAlist[i], NFAStateTemp->Transform); if(pTransform == NULL) { // 不存在,调用 CreateDFATransform 函数创建一个转换,并将这个转换插入到转换链表的开始位置 pTransform=CreateDFATransform(NFAStateTemp->Transform,NFAStateArray,Count); pTransform->NextTrans=pDFA->DFAlist[i]->firstTran; pDFA->DFAlist[i]->firstTran=pTransform; } else { //存在,调用 AddNFAStateArrayToTransform 函数把ε-闭包合并到已存在的转换中 AddNFAStateArrayToTransform(NFAStateArray, Count, pTransform); } } // 遍历 DFA 状态的转换链表,根据每个转换创建对应的 DFA 状态 for(pTFCursor = pDFA->DFAlist[i]->firstTran; pTFCursor != NULL; pTFCursor = pTFCursor->NextTrans) { // 调用 NFAStateIsSubset 函数判断转换中的 NFA 状态集合是否为某一个 DFA 状态中 NFA 状态集合的子集 int Index = NFAStateIsSubset(pDFA, pTFCursor); if(Index == -1) { // 不是子集,调用 CreateDFAState 函数创建一个新的 DFA 状态并加入 DFA 线性表中 // 将转换的 DFAStateIndex 赋值为新加入的 DFA 状态的下标 DFAState* newState = CreateDFAState(pTFCursor); pDFA->DFAlist[pDFA->length] = newState; pTFCursor->DFAStateIndex = pDFA->length; pDFA->length++; } else { // 是子集,将转换的 DFAStateIndex 赋值为 Index pTFCursor->DFAStateIndex = Index; } } } return pDFA; }
05-23
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值