自动机理论

定义

  • 非确定型有穷自动机(NFA),在某状态下,对于指定的输入,存在多个转移状态。一般情况下,通过某种算法可转换为 DFA

  • 上下文无关文法 描述程序设计语言的结构以及相关集合的重要记号,用来构造编译器的语法分析部件。

  • 字母表 符号的有穷非空集合。用Σ\Sigma表示

  • 串(有时候称为单词)是从某个字母表中选择的符号的有穷序列。例如 01101 是从二进制字母表Σ={0,1}\Sigma = \lbrace 0,1\rbrace中选出的串

  • 空串 出现 0 次符号的串,记做ε\varepsilon

  • 串的长度 这个串中的符号数,记做ω\vert \omega \vert,例如ε=0\vert \varepsilon \vert = 0

  • 字母表的幂 定义Σk\Sigma^{k}是长度为 k 的串的集合。串的每个符号都属于Σ\Sigma。无论是什么字母表Σ0={ε}\Sigma^{0}=\lbrace\varepsilon\rbrace。字母表上所有的串的集合约定为Σ\Sigma^{*},排除空串的集合约定为Σ+\Sigma^{+}

  • 串的连接 设定 x 和 y 都是串,于是 xy 表示 x 和 y 的连接

  • 集合表示法 {ωω\lbrace\omega\vert\omega的语义描述}\rbrace。比如{ωω\lbrace\omega\vert\omega包含相同个数的 0 或 1}\rbrace,还可以把ω\omega换成某个带参数的表达式{0n1nn1}\lbrace0^n1^n\vert n \ge1\rbrace

  • 当两个状态机交互时,当其状态处于(i,x)(i,x),对于一个合法的输入ZZ,可使iji \rightarrow jxyx \rightarrow y,那么我们可以认为(i,x)(j,y)(i,x) \rightarrow (j,y)是可达的

确定型有穷自动机(DFA)

确定型有穷自动机 包含若干个状态的集合,若干个输入符,当有输入时,控制权将由一个状态转移到另一个状态。在任意状态下,对于指定的输入,其转移是唯一的。

  1. 一个有穷状态集合,记做QQ

  2. 初始状态记做q0q_0,

  3. 终止状态记做FF

  4. 一个有穷输入集合,记做Σ\Sigma

  5. 状态转移函数,记做δ\delta,以一个状态qq和一个输入符号aa作为参数,返回一个状态ppδ(q,a)=p;pQ\delta(q,a) = p;p\in Q

  6. 扩展状态转移函数,记做δ^\hat{\delta},以一个状态qq和一个输入串ω\omega作为参数,返回一个状态pp
    • δ^(q,ε)=q\hat{\delta}(q,\varepsilon)=q
    • 假定ω=xa\omega = xa,a 是最后的输入,δ^(q,ω)=δ(δ^(q,x),a)\hat{\delta}(q,\omega)=\delta(\hat{\delta}(q,x),a)

DFA 的定义: A=(Q,Σ,δ,q0,F)A = (Q,\Sigma,\delta,q_0,F)

对于所有 DFA,我们可以定义为 L(A)={ωδ^(q0,ω)F}L(A) = \lbrace \omega|\hat{\delta}(q_0,\omega) \in F\rbrace

例如{ωω\lbrace\omega|\omega出现 01 字符串}\rbrace可能出现三个状态

  1. q0q_0 未遇到 01,且最后一个不为 0,δ(q0,0)=q1,δ(q0,1)=q0\delta(q_0,0)=q_1,\delta(q_0,1)=q_0
  2. q1q_1 未遇到 01,且最后一个为 0,δ(q1,0)=q1,δ(q0,1)=q2\delta(q_1,0)=q_1,\delta(q_0,1)=q_2
  3. q2q_2 已遇到 01,可接受任意 0 或 1,δ(q2,0)=q2,δ(q2,1)=q2\delta(q_2,0)=q_2,\delta(q_2,1)=q_2

其 DFA 表达式: A=({q0,q1,q2},{0,1},δ,q0,q2)A = (\lbrace q_0,q_1,q_2\rbrace,\lbrace 0,1\rbrace,\delta,q_0,{q_2})

对 DFA 的细节描述难以阅读,通常使用两种方式来更好的描述

  1. 状态转移图

       graph LR;
    begin( )-->|Start|q0
    q0-->|0|q1
    q0-->|1|q0
    q1-->|0|q1
    q1-->|1|q2
    q2-->|0,1|q2
    style begin color:white,fill:white,stroke:white
  2. 状态转移表,一个状态和输入组成的δ\delta的表

    0 1
    q0\rightarrow q_0 q1q_1 q0q_0
    q1q_1 q1q_1 q2q_2
    q2q_2 q2q_2 q2q_2

使用扩展转移函数,对一个串 0011 进行计算

  1. δ^(q0,ε)=q0\hat{\delta}(q_0,\varepsilon)=q_0

  2. δ^(q0,0)=δ(δ^(q0,ε),0)=δ(q0,0)=q1\hat{\delta}(q_0,0)=\delta(\hat{\delta}(q_0,\varepsilon),0) =\delta(q_0,0) = q_1

  3. δ^(q0,00)=δ(δ^(q0,0),0)=δ(q1,0)=q1\hat{\delta}(q_0,00)=\delta(\hat{\delta}(q_0,0),0) =\delta(q_1,0) = q_1

  4. δ^(q0,001)=δ(δ^(q0,00),1)=δ(q1,1)=q2\hat{\delta}(q_0,001)=\delta(\hat{\delta}(q_0,00),1) =\delta(q_1,1) = q_2

  5. δ^(q0,0011)=δ(δ^(q0,001),1)=δ(q2,1)=q2\hat{\delta}(q_0,0011)=\delta(\hat{\delta}(q_0,001),1) =\delta(q_2,1) = q_2

这种方式的处理,非常适合我们用递归函数进行计算。

非确定型有穷自动机(NFA)

与确定型有穷自动机类似,包含若干个状态,若干个输入符,状态转移函数,终止状态。唯一不同的是,δ\delta的结果可能是零个、一个、或多个状态的集合。

  1. 一个有穷状态集合,记做QQ

  2. 初始状态记做q0q_0,

  3. 终止状态记做FF

  4. 一个有穷输入集合,记做Σ\Sigma

  5. 状态转移函数,记做δ\delta,以一个状态qq和一个输入符号aa作为参数,返回一个状态ppδ(q,a)={p1,p2,,pk};{p1,p2,,pk}Q\delta(q,a) = \lbrace p1,p2,\cdots,p_k \rbrace;\lbrace p1,p2,\cdots,p_k \rbrace\in Q

  6. 扩展状态转移函数,记做δ^\hat{\delta},以一个状态qq和一个输入串ω\omega作为参数,返回一个状态pp
    • δ^(q,ε)=q\hat{\delta}(q,\varepsilon)=q
    • 假定ω=xa\omega = xaδ^(q,x)={p1,p2,,pk}\hat{\delta}(q,x)=\lbrace p_1,p_2,\cdots,p_k \rbrace,那么δ^(q,ω)=i=1kδ(pi,a)={r1,r2,,rm}\hat{\delta}(q,\omega)=\displaystyle\bigcup_{i=1}^k\delta(p_i,a)=\lbrace r_1,r_2,\cdots,r_m \rbrace

对于 NFA,可以定义为 L(A)={ωδ^(q0,ω)F}L(A) = \lbrace \omega|\hat{\delta}(q_0,\omega) \cap F\rbrace \ne \emptyset

例如,{{0,1}\lbrace \lbrace0,1\rbrace|以 01 结尾}\rbrace

其状态转移图如下

graph LR;
begin( )-->|Start|q0
q0-->|0|q1
q0-->|0,1|q0
q1-->|1|q2
style begin color:white,fill:white,stroke:white

当接受到 0 时,NFA 会猜测最后的 01 已经开始了,一条弧线从q0q_0指向q1q_1,我们可以看到 0 有两条弧线,另一条指向q0q_0。NFA 会同时走这两条线。当在q1q_1状态时,它会检查下一个符号是否为 1,如果是则会进入状态q2q_2。当在q2q_2状态时,如还有其他输入,这条路线就终结掉了。

00101 的处理过程如下 自动机理论_NFA.png

其状态转移表如下

0 1
q0\rightarrow q_0 {q0,q1}\lbrace q_0,q_1\rbrace {q0}\lbrace q_0\rbrace
q1q_1 \emptyset {q2}\lbrace q_2\rbrace
* q2q_2 \emptyset \emptyset

使用扩展转移函数的处理过程如下

  1. δ^(q0,ε)={q0}\hat{\delta}(q_0,\varepsilon)=\lbrace q_0\rbrace

  2. δ^(q0,0)=δ(q0,0)={q0,q1}\hat{\delta}(q_0,0)=\delta(q_0,0) = \lbrace q_0,q_1\rbrace

  3. δ^(q0,00)=δ(q0,0)δ(q1,0)={q0}{q2}={q0,q1}\hat{\delta}(q_0,00)=\delta(q_0,0) \cup \delta(q_1,0)= \lbrace q_0\rbrace \cup \lbrace q_2\rbrace=\lbrace q_0,q_1\rbrace

  4. δ^(q0,001)=δ(q0,1)δ(q1,1)={q0}{q2}={q0,q2}\hat{\delta}(q_0,001)=\delta(q_0,1) \cup \delta(q_1,1)= \lbrace q_0\rbrace \cup \lbrace q_2\rbrace=\lbrace q_0,q_2\rbrace

  5. δ^(q0,0010)=δ(q0,0)δ(q2,0)={q0,q1}={q0,q1}\hat{\delta}(q_0,0010)=\delta(q_0,0) \cup \delta(q_2,0)= \lbrace q_0,q_1\rbrace \cup \emptyset=\lbrace q_0,q_1\rbrace

  6. δ^(q0,00101)=δ(q0,1)δ(q1,1)={q0}{q2}={q0,q2}\hat{\delta}(q_0,00101)=\delta(q_0,1) \cup \delta(q_1,1)= \lbrace q_0\rbrace \cup \lbrace q_2\rbrace=\lbrace q_0,q_2\rbrace

NFA 与 DFA 的转换

通常来说,构造 NFA 比构造 DFA 更容易,每一个用 NFA 描述的语言也能用 DFA 来描述。这个可以用子集构造来证明。

子集构造从一个 NFA N=(Qn,Σ,δn,q0,Fn)N = (Q_n,\Sigma,\delta_n,q_0,F_n)开始,转换为 D=(Qd,Σ,δd,{q0},Fd)D = (Q_d,\Sigma,\delta_d,\lbrace q_0 \rbrace,F_d)

  1. 两个自动机的输入字母表是相同的
  2. D 的起始状态为仅包含 N 的起始状态的长度为 1 的集合
  3. QdQ_dQnQ_n子集的集合,即幂集合。假如QnQ_n有 n 个状态,那么QdQ_d2n2^n状态,通常不是所有的状态都是从q0q_0可达的,这些状态可以丢弃,所以实际上QdQ_d的状态要远远小于2n2^n
  4. FDF_D所有满足SFnS\cap F_n \neq \emptysetQnQ_n的子集的集合 S,也就是说,FDF_DQNQ_N状态子集中至少包含一个FNF_N的集合。
  5. 对于SQnS \subseteq Q_n的集合 S 中每个状态来说,其对应的每个属于Σ\Sigma的输入符号aa的转移函数为: δD(S,a)=pSδN(p,a)\delta_D(S,a)= \bigcup_{p \in S} \delta_N(p,a)

示例,我们以接受所有 01 结尾的串的 NFA 向 DFA 转换,由于Qn={q0,q1,q2}Q_n = \lbrace q_0,q_1,q_2\rbrace,所以子集构造产生一个带有23=82^3 = 8(并非所有状态都是有意义的)种状态的 DFA

0 1
\emptyset \emptyset \emptyset
{q0}\rightarrow \lbrace q_0\rbrace {q0,q1}\lbrace q_0,q_1\rbrace {q0}\lbrace q_0\rbrace
{q1}\lbrace q_1\rbrace \emptyset {q2}\lbrace q_2\rbrace
{q2}*\lbrace q_2\rbrace \emptyset \emptyset
{q0,q1}\lbrace q_0,q_1\rbrace {q0,q1}\lbrace q_0,q_1\rbrace {q0,q2}\lbrace q_0,q_2\rbrace
{q0,q2}*\lbrace q_0,q_2\rbrace {q0,q1}\lbrace q_0,q_1\rbrace {q0}\lbrace q_0\rbrace
{q1,q2}*\lbrace q_1,q_2\rbrace \emptyset {q2}\lbrace q_2\rbrace
{q0,q1,q2}*\lbrace q_0,q_1,q_2\rbrace {q0,q1}\lbrace q_0,q_1\rbrace {q0,q2}\lbrace q_0,q_2\rbrace

上述表格的详细证明如下(列举 2、5 两行):

  • δD({q0},0)=δN(q0,0)={q0,q1}\delta_D(\lbrace q_0\rbrace,0) = \delta_N(q_0,0) = \lbrace q_0,q_1\rbrace

  • δD({q0},1)=δN(q0,1)={q0}\delta_D(\lbrace q_0\rbrace,1) = \delta_N(q_0,1) = \lbrace q_0\rbrace

  • δD({q0,q1},0)=δN(q0,0)δN(q1,0)={q0,q1}={q0,q1}\delta_D(\lbrace q_0,q_1\rbrace,0) = \delta_N(q_0,0) \cup \delta_N(q_1,0) = \lbrace q_0,q_1\rbrace \cup \emptyset = \lbrace q_0,q_1\rbrace
  • δD({q0,q1},1)=δN(q0,1)δN(q1,1)={q0}{q2}={q0,q2}\delta_D(\lbrace q_0,q_1\rbrace,1) = \delta_N(q_0,1) \cup \delta_N(q_1,1) = \lbrace q_0\rbrace \cup \lbrace q_2\rbrace= \lbrace q_0,q_2\rbrace

我们给这 8 中状态设计新的名字,如AA表示\emptysetBB表示{q0}\lbrace q_0 \rbrace等。

0 1
A A A
B\rightarrow B E B
C A D
D*D A A
E E F
F*F E B
G*G A D
H*H E F

从状态 B 开始,只能到达状态 B、E 和 F。其余五种状态都是从初始状态不可达的,也可以不出现在表中。如果向下面这样在子集合中执行惰性求值,通常就能避免以指数时间步骤为每个状态子集合构造转移表项目。一般情况下,我们可以从初始状态 A 开始计算可到达状态,若有新的可达状态,我们继续计算,直到没有新的可达状态。

graph LR;
begin( )-->|Start|B
B-->|0|E
B-->|1|B
E-->|0|E
E-->|1|F
F-->|0|E
F-->|1|B
style begin color:white,fill:white,stroke:white

我们需要证明这个子集构造是正确的。

在读入输入符号序列ω\omega后,所构造的 DFA 处于这样一个状态,这个状态时 NFA 在读ω\omega后所处的状态的集合。由于 DFA 的接受状态是至少包含一个 NFA 接受状态FnF_n的集合,因为包含 NFA 的可接受状态FnF_n,因此这个集合也是被 NFA 接受的。于是我们可以得出结论,这个 DFA 和 NFA 接受完全相同的语言。

我们来证明 若D=(Qd,Σ,δd,{q0},Fd)D = (Q_d,\Sigma,\delta_d,\lbrace q_0 \rbrace,F_d)是从N=(Qn,Σ,δn,q0,Fn)N = (Q_n,\Sigma,\delta_n,q_0,F_n)子集构造出来的,那么L(D)=L(N)L(D) = L(N),这也就是证明,对于输入字母表ω\omegaδ^({q0},ω)=δ^(q0,ω)\hat{\delta}(\lbrace q_0\rbrace,\omega) = \hat{\delta}(q_0,\omega)

  1. ω=0|\omega| =0,即ω=ε\omega = \varepsilon,根据δ^\hat{\delta}的定义,δ^({q0},ε)\hat{\delta}(\lbrace q_0\rbrace,\varepsilon) δ^(q0,ε)\hat{\delta}(q_0,\varepsilon)的结果都为{q0}\lbrace q_0 \rbrace

  2. 假定ω=n+1|\omega| = n + 1ω=xa\omega = xax=n|x| = nδ^({q0},x)=δ^(q0,x)\hat{\delta}(\lbrace q_0\rbrace,x) = \hat{\delta}(q_0,x)成立,两个集合状态 N 为{p1,p2,,pk}\lbrace p_1,p_2,\cdots,p_k\rbrace

    那么对于 NFA:

    δ^N(q0,ω)=i=1kδN(pi,a)\hat{\delta}_N(q_0,\omega)=\displaystyle\bigcup_{i=1}^k\delta_N(p_i,a)

    通过子集构造的定义我们可以得出

    δD({p1,p2,,pk},a)=i=1kδN(pi,a)\delta_D(\lbrace p_1,p_2,\cdots,p_k\rbrace,a)=\displaystyle\bigcup_{i=1}^k\delta_N(p_i,a)

    又因 δD({q0},x)={p1,p2,,pk}\delta_D(\lbrace q_0\rbrace,x)= \lbrace p_1,p_2,\cdots,p_k\rbrace

    δD^({q0},w)=δD(δD^({q0},x),a)=δD({p1,p2,,pk},a)=i=1kδN(pi,a)\hat{\delta_D}(\lbrace q_0\rbrace,w) = \delta_D(\hat{\delta_D}(\lbrace q_0\rbrace,x),a) =\delta_D( \lbrace p_1,p_2,\cdots,p_k\rbrace,a)=\displaystyle\bigcup_{i=1}^k\delta_N(p_i,a)

文本搜索

识别 web 和 ebay 出现

通过 NFA 来实现 自动机理论_web_ebay.png

对终止状态[4,8]增加了回到1的转移,以支持 web 和 ebay 出现在中间位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//识别单词web和ebay出现的NFA

//状态转移函数
var delta = {
1: (a) => {
if (a == "w") return [1, 2];
else if (a == "e") return [1, 5];
else return [1];
},
2: (a) => (a === "e" ? [3] : []),
3: (a) => (a === "b" ? [4] : []),
4: (a) => [1, 4],
5: (a) => (a === "b" ? [6] : []),
6: (a) => (a === "a" ? [7] : []),
7: (a) => (a === "y" ? [8] : []),
8: (a) => [1, 8],
};

//所有状态
Qn = [1, 2, 3, 4, 5, 6, 7, 8];
//初始状态
q0 = 1;
//终止状态
F = [4, 8];

//扩展状态转移函数
function hat_delat(q0, w) {
if (typeof w === "string") {
w = w.split("");
}
//ε的总是返回当前状态
if (w.length === 0) {
return [q0];
}
let result = [];
//ω=xa,将结果并集
hat_delat(q0, w.slice(0, -1)).forEach((q) => {
let a = w[w.length - 1];
let next = delta[q](a);
//去重合并
next
.filter((item) => !result.includes(item))
.forEach((item) => result.push(item));
});
return result;
}

console.log(hat_delat(q0, "webay"));
console.log(hat_delat(q0, "aebay"));
console.log(hat_delat(q0, "abc"));

通过子集构造的方式来转换为 DFA,省略[4,8]1出口 自动机理论_ndf_dfa.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//状态转移函数
var delta = {
1: (a) => {
if (a == "w") return [1, 2];
else if (a == "e") return [1, 5];
else return [1];
},
2: (a) => (a === "e" ? [3] : []),
3: (a) => (a === "b" ? [4] : []),
4: (a) => [],
5: (a) => (a === "b" ? [6] : []),
6: (a) => (a === "a" ? [7] : []),
7: (a) => (a === "y" ? [8] : []),
8: (a) => [],
};

//所有状态
Qn = [1, 2, 3, 4, 5, 6, 7, 8];
//初始状态
q0 = 1;
//终止状态
F = [4, 8];

//由NFA状态子集构成的DFA状态集合
DFA = {};

//输入字母表为26个字母
E = "qwertyuiopasdfghjklzxcvbnm".split("");

//为Array添加equal方法,判断内部元素是否相同
Array.prototype.equal = function (other) {
if (this.length == other.length) {
let equal = true;
for (let i = 0; i < other.length; i++) {
if (this[i] !== other[i]) {
equal = false;
break;
}
}
return equal;
} else {
return false;
}
};
//为Array添加去重函数,若内部元素为数组,则根据内部数组元素是否完全相同来去重
Array.prototype.unique = function () {
let temp = [];
for (let i = 0; i < this.length; i++) {
let insert = true;
var item = this[i];
if (Array.isArray(item)) {
for (let j = 0; j < temp.length; j++) {
if (temp[j].equal(item)) {
insert = false;
}
}
} else {
insert = temp.indexOf(item) == -1;
}
if (insert) {
temp.push(item);
}
}
return temp;
};

//子集构造
function sub_construct(state_arr) {
if (state_arr.length === 0) {
return;
}
let trans = DFA[state_arr];
//已经计算过的子集不在计算
if (trans == undefined) {
trans = [];
E.forEach((a) => {
//根据子集构造的定义,DFA子集构造的转移函数,对于特定输入,state_arr的所有状态的转移函数结构的集合

let dfa_of_state_arr = [];
state_arr.forEach((state) => {
dfa_of_state_arr = dfa_of_state_arr.concat(delta[state](a));
});
trans.push(dfa_of_state_arr.sort().unique());
});

trans = trans.sort().unique();
DFA[state_arr] = trans;
//检查是否触发了新的DFA状态,如果有,则继续向前查找
trans.forEach((dfa_of_state_arr) => {
sub_construct(dfa_of_state_arr);
});
}
}

sub_construct([q0]);
console.log(DFA);

//子集构造完成后,我们就可以构建出扩展转移函数
function hal_delta(q_arr, w) {
if (typeof w === "string") {
w = w.split("");
}
if (w.length === 0) {
return q_arr;
}
let result = [];
// ω=xa,将结果并集
hal_delta(q_arr, w.slice(0, -1)).forEach((q) => {
let a = w[w.length - 1];
let next = delta[q](a);
//去重合并
next.forEach((item) => result.push(item));
});
return result.sort().unique();
}

console.log(hal_delta([q0], "webay"));

上述输出结果为,与例图完美对应

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"1": [[1], [1, 2], [1, 5]],
"1,2": [[1], [1, 2], [1, 3, 5]],
"1,3,5": [[1], [1, 2], [1, 4, 6], [1, 5]],
"1,4,6": [[1], [1, 2], [1, 5], [1, 7]],
"1,5": [[1], [1, 2], [1, 5], [1, 6]],
"1,6": [[1], [1, 2], [1, 5], [1, 7]],
"1,7": [[1], [1, 2], [1, 5], [1, 8]],
"1,8": [[1], [1, 2], [1, 5]]
}

//扩展转移函数的输出
[(1, 8)]