因为 NFA 的状态转移不确定,不适合直接做词法分析器的识别,在写算法时往往需要使用回溯。所以我们一般使用子集构造算法,将 NFA 转换成 DFA,得到确定的状态转移,再转化成一个词法分析器的代码。
下面给出一个关于 NFA 到 DFA 转化的例子,我们使用 a(b|c)* 做例:
对于 ϵ \epsilon ϵ的边表示一种零代价的转换, n 1 n_1 n1可以在没有任何输入操作的情况下直接滑动到 n 2 n_2 n2
所以 n 0 n_0 n0通过 a 可以走到 n 1 , n 2 , n 3 , n 4 , n 6 , n 9 n_1, n_2, n_3, n_4, n_6,n_9 n1,n2,n3,n4,n6,n9。我们可以将这样的 6 个元素记为一个集合 q 1 q_1 q1。 q 1 = { n 1 , n 2 , n 3 , n 4 , n 6 , n 9 } q_1 = \lbrace n_1, n_2, n_3, n_4, n_6, n_9\rbrace q1={n1,n2,n3,n4,n6,n9}。
q 1 q_1 q1 通过 b 可以得到: n 5 , n 8 , n 9 , n 3 , n 4 , n 6 n_5,n_8,n_9,n_3,n_4,n_6 n5,n8,n9,n3,n4,n6 ,记为 q 2 q_2 q2。
q 2 q_2 q2继续通过某一节点得到 q 3 q_3 q3
继续重复该步骤,得到所有的子集。
所以 q 0 q_0 q0通过 a 得到 q 1 , q 1 q_1, q_1 q1,q1通过 b 得到 q 2 q_2 q2…,最终可以将 NFA 转化为一个 DFA。
新的DFA中的状态只要包含原NFA中任意一终结状态,其就是终结状态,例如原NFA中终结状态有 n 9 n_9 n9,则 q 1 , q 2 q_1,q_2 q1,q2在DFA中都是终结状态
对于子集的求解,首先我们先看出在 NFA 中的下一状态,如 q 1 q_1 q1的下一状态记为 δ ( q 1 ) \delta(q_1) δ(q1), 在这里是 { n 5 n_5 n5} 。之后求它的边界, 即每一个元素都通过 ϵ \epsilon ϵ能走到的所有状态,记为 δ ( q 1 ) \delta(q_1) δ(q1)的 ϵ \epsilon ϵ闭包。
/* ε-closure: 基于深度优先遍历的算法 */
set closure = {};
void eps_closure(x){
closure += {x}; // 集合的加法, 并
foreach(y: x --ε--> y){ // y 是 x 通过 ε 转换到的 y。
if(!visited(y)){ // 如果 y 还没有访问过,就访问 y
eps_closure(y);
}
}
}
算法的时间复杂度是O(N)
/* ε-closure: 基于宽度优先遍历的算法 */
set closure = {};
Q = []; // quenu 基于队列的概念,
void eps_closure(x) =
Q = [x];
while(Q not empty)
q <- deQueue(Q)
closure += q
foreach(y: q --ε--> y) // 将所有的从 Q 开始可以走到的 y 都加到 Q 里
if(!visited(y))
enQueue(Q,y)
对于第一个例子,算法执行过程如下:
q0 <- eps_closure(n0) // q0 = {n0}
Q <- {q0} // Q = {q0}
workList <- q0 // workList = [q0, ...]
while(workList != [])
remove q from workList // workList = [...]
foreach(character c) // c = a
t <- e-closure(delta(q,c)) // delta(q0, a) = {n1}, t = {n1, n2, n3, n4, n6, n9}
D[q,c] <- t // q1 = t
if(t not in Q) // Q = {q0, q1} , workList = [q1]
add t to Q and workList
重复运行上式代码,运行内层循环可以得到
q 1 = { n 1 , n 2 , n 3 , n 4 , n 6 , n 9 } q_1 = \lbrace n_1,n_2,n_3,n_4,n_6,n_9\rbrace q1={n1,n2,n3,n4,n6,n9}
q 2 = { n 5 , n 8 , n 9 , n 3 , n 4 , n 6 } q_2 = \lbrace n_5, n_8, n_9, n_3, n_4, n_6\rbrace q2={n5,n8,n9,n3,n4,n6}
q 3 = { n 7 , n 8 , n 9 , n 3 , n 4 , n 6 } q_3 = \lbrace n_7,n_8,n_9,n_3,n_4,n_6\rbrace q3={n7,n8,n9,n3,n4,n6}
回到外层循环后,再次运行, 不能得到新的子集。