RE转NFA转DFA
创始人
2024-01-28 10:23:30

https://github.com/Nightmare4214/re_nfa_dfa

前置知识

ϵ\epsilonϵ代表空串

语言

某个给定字母表上一个任意的可数的串集合

正则语言/正则表达式

正则语言(regular language)/正则表达式(regular expression)
每个正则表达式rrr表示一个语言L(r)L\left(r\right)L(r)

归纳基础:
1)ϵ\mathbf{\epsilon}ϵ是一个正则表达式,L(ϵ)={ϵ}L\left(\mathbf{\epsilon}\right)=\left\{\epsilon\right\}L(ϵ)={ϵ},即该语言只包含空串
2)如果aaa是Σ\SigmaΣ上的一个符号,那么a\mathbf{a}a是一个正则表达式,并且L(a)={a}L\left(\mathbf{a}\right)=\left\{a\right\}L(a)={a}

归纳步骤:
假定r,s\mathbf{r},\mathbf{s}r,s都是正则表达式,分别表示语言L(r),L(s)L\left(\mathbf{r}\right),L\left(\mathbf{s}\right)L(r),L(s),那么:
1)(r)∣(s)\left(\mathbf{r}\right)|\left(\mathbf{s}\right)(r)∣(s)是一个正则表达式,表示语言L(r)∪L(s)L\left(\mathbf{r}\right)\cup L\left(\mathbf{s}\right)L(r)∪L(s)
2)(r)(s)\left(\mathbf{r}\right)\left(\mathbf{s}\right)(r)(s)是一个正则表达式,表示语言L(r)L(s)L\left(\mathbf{r}\right)L\left(\mathbf{s}\right)L(r)L(s)
3)(r)∗\left(\mathbf{r}\right)^*(r)∗是一个正则表达式,表示语言(L(r))∗\left(L\left(\mathbf{r}\right)\right)^*(L(r))∗
4)(r)\left(\mathbf{r}\right)(r)是一个正则表达式,表示语言L(r)L\left(\mathbf{r}\right)L(r)

有穷自动机

有穷自动机是识别器,他们只能对每个可能的输入串简单地回答“是”或“否”

有穷自动机分为不确定的有穷自动机和确定的有穷自动机

不确定的有穷自动机

不确定的有穷自动机(Nondeterministic Finite Automata, NFA)对其边上的标号没有任何限制。一个符号标记离开同意状态的多条边,并且空串ϵ\epsilonϵ也可以作为标号
NFAA=(Q,Σ,δ,q0,F)NFA\ A=\left(Q,\Sigma,\delta,q_0,F\right)NFA A=(Q,Σ,δ,q0​,F)
1)一个有穷的状态集合QQQ
2)一个输入的符号集合Σ\SigmaΣ,即输入字母表。我们假定空串ϵ\epsilonϵ不是Σ\SigmaΣ中的元素
3)一个转换函数,它为每个状态和Σ∪{ϵ}\Sigma\cup \left\{\epsilon\right\}Σ∪{ϵ}中的每个符号都给出了相应的后继状态的集合,
即δ:Q×(Σ∪{ϵ})↦P(Q)\delta: Q \times \left(\Sigma\cup \left\{\epsilon\right\}\right) \mapsto \mathcal{P}\left(Q\right)δ:Q×(Σ∪{ϵ})↦P(Q),其中P(Q)\mathcal{P}\left(Q\right)P(Q)代表QQQ的所有子集组成的集合
4)QQQ中的一个状态s0s_0s0​被指定为开始状态,或者说初始状态
5)QQQ的一个子集FFF被指定为接收状态(或者说终止状态)的集合
在这里插入图片描述

确定的有穷自动机

确定的有穷自动机(Deterministic Finite Automata, DFA),有且只有一条离开该状态、以该符号为标号的边
DFA是NFA的一个特例,其中
δ:Q×Σ↦Q\delta: Q \times \Sigma \mapsto Qδ:Q×Σ↦Q
即:
1)没有输入ϵ\epsilonϵ之上的转换动作
2)对每个状态sss和每个输入符号aaa,有且只有一条标号为aaa的边离开sss
在这里插入图片描述

自动机中输入字符串的接受

一个NFA接受输入字符串xxx,当且仅当对应的转换图中存在一条从开始状态到某个接收状态的路径,使得该路径中各条边上的标号祖传符号串xxx(路径中的ϵ\epsilonϵ标号将被忽略)

正则表达式转NFA

McNaughton–Yamada–Thompson algorithm算法
**输入:**字母表Σ\SigmaΣ上的一个正则表达式r\mathbf{r}r
**输出:**一个接受L(r)L\left(\mathbf{r}\right)L(r)的NFANNFA\ NNFA N
**方法:**首先对r\mathbf{r}r进行语法分析,分解出组成它的子表达式。
**基本规则:**对于表达式ϵ\mathbf{\epsilon}ϵ,构造下面的NFANFANFA
在这里插入图片描述
对于字母表Σ\SigmaΣ中的子表达式a\mathbf{a}a,构造下面的NFANFANFA
在这里插入图片描述
**归纳规则:**假设正则表达式s\mathbf{s}s和t\mathbf{t}t的NFANFANFA分别为N(s)N\left(\mathbf{s}\right)N(s)和N(t)N\left(\mathbf{t}\right)N(t)
1)假设r=s∣t\mathbf{r}=\mathbf{s}|\mathbf{t}r=s∣t则如图构造N(r)N\left(\mathbf{r}\right)N(r)
在这里插入图片描述

2)假设r=st\mathbf{r}=\mathbf{s}\mathbf{t}r=st则如图构造N(r)N\left(\mathbf{r}\right)N(r)
在这里插入图片描述
3)假设r=s∗\mathbf{r}=\mathbf{s}^*r=s∗则如图构造N(r)N\left(\mathbf{r}\right)N(r)
在这里插入图片描述
(不过我上课教的是下面这种
在这里插入图片描述
只是代码可能不好写)
4)假设r=(s)\mathbf{r}=\left(\mathbf{s}\right)r=(s),那么N(s)=N(r)N\left(\mathbf{s}\right)=N\left(\mathbf{r}\right)N(s)=N(r)

性质:
1)N(r)N\left(\mathbf{r}\right)N(r)状态数最多为r\mathbf{r}r中的运算符和运算分量的总数的2倍(因为每一步构造最多多2个状态)
2)N(r)N\left(\mathbf{r}\right)N(r)有且有只有一个开始状态和一个接受状态。接受状态没有出边,开始状态没有入边
3)N(r)N\left(\mathbf{r}\right)N(r)除接受状态之外,每个状态要么有一条标号为Σ\SigmaΣ中符号的出边,有么有两条标号为ϵ\epsilonϵ的出边(?)

代码

Trans.h

#pragma once
extern char const EPSILON = '$';
class Trans {
public:int vertex_from;int vertex_to;char trans_symbol;Trans(int vertex_from = 0, int vertex_to = 1, char trans_symbol = EPSILON) :vertex_from(vertex_from), vertex_to(vertex_to), trans_symbol(trans_symbol) {}
};

NFA.h
这里没定义Σ\SigmaΣ你可以自己写一下
默认0是起始状态

其他的都是一些get和set

#pragma once
#include"Trans.h"
#include
#include
#include
#include
class NFA {
public://0 is the only start stateint vertex_cnt;//Qstd::vector transitions;//delta,transition graphstd::unordered_set final_states;//FNFA(int vertex_cnt = 0) :vertex_cnt(vertex_cnt) {}NFA(int vertex_cnt, const std::vector& transitions, const std::unordered_set& final_states) :vertex_cnt(vertex_cnt), transitions(transitions), final_states(final_states) {}int get_vertex_count()const {return vertex_cnt;}void set_vertex_cnt(int vertex_cnt) {this->vertex_cnt = vertex_cnt;}std::vector get_transition()const {return transitions;}void add_transition(int vertex_from, int vertex_to, char trans_symbol) {transitions.emplace_back(vertex_from, vertex_to, trans_symbol);}std::unordered_set get_final_states()const {return final_states;}void add_final_state(int state) {final_states.insert(state);}void clear() {vertex_cnt = 0;transitions.clear();final_states.clear();}void display()const {printf("-------------------------\n");for (Trans temp : transitions) {printf("q_%d -> q_%d: %c\n", temp.vertex_from, temp.vertex_to, temp.trans_symbol);}printf("\n");printf("\n");printf("final_states: ");for (int f : final_states) {printf("%d ", f);}printf("\n");printf("-------------------------\n");}
};

Re2NFA.h
主要就是类似中缀表达式那样解析,碰到∗*∗要立即结算

#pragma once
#include
#include
#include
#include
#include"Trans.h"
#include"NFA.h"//re: a.b
NFA concat(NFA a, NFA b) {int a_vertex_cnt = a.get_vertex_count();int b_vertex_cnt = b.get_vertex_count();int total_states = a_vertex_cnt + b_vertex_cnt;NFA result(total_states, a.get_transition(), { total_states - 1 });result.add_transition(a_vertex_cnt - 1, a_vertex_cnt, EPSILON);for (const Trans& trans : b.get_transition()) {result.add_transition(trans.vertex_from + a_vertex_cnt,trans.vertex_to + a_vertex_cnt,trans.trans_symbol);}return result;
}//re: a*
NFA kleene(NFA a) {int a_vertex_cnt = a.get_vertex_count();NFA result(a_vertex_cnt + 2, {Trans(0,1,EPSILON),Trans(a_vertex_cnt,1,EPSILON),Trans(a_vertex_cnt,a_vertex_cnt + 1,EPSILON),Trans(0,a_vertex_cnt + 1,EPSILON)},{ a_vertex_cnt + 1 });for (const Trans& trans : a.get_transition()) {result.add_transition(trans.vertex_from + 1, trans.vertex_to + 1, trans.trans_symbol);}return result;
}//re: a|b
NFA or_selection(NFA a, NFA b) {int a_vertex_cnt = a.get_vertex_count();int b_vertex_cnt = b.get_vertex_count();NFA result(a_vertex_cnt + b_vertex_cnt + 2, {Trans(0,1,EPSILON),Trans(0,a_vertex_cnt + 1,EPSILON),Trans(a_vertex_cnt,a_vertex_cnt + b_vertex_cnt + 1,EPSILON),Trans(a_vertex_cnt + b_vertex_cnt,a_vertex_cnt + b_vertex_cnt + 1,EPSILON)},{ a_vertex_cnt + b_vertex_cnt + 1 });for (const Trans& trans : a.get_transition()) {result.add_transition(trans.vertex_from + 1, trans.vertex_to + 1, trans.trans_symbol);}for (const Trans& trans : b.get_transition()) {result.add_transition(trans.vertex_from + a_vertex_cnt + 1, trans.vertex_to + a_vertex_cnt + 1, trans.trans_symbol);}return result;
}int level(const char& op) {if (op == '#') {return 0;}else if (op == '(' || op == ')') {return 1;}else if (op == '|') {return 2;}//.return 3;
}/*** compare right operator with left operator* @param left_operator left operator* @param right_operator right operator* @return right operator>left operator return 1,= return 0,< -1*/
int cmp(const char& left_operator, const char& right_operator) {if (left_operator == '(' && right_operator == ')') {return 0;}else if (right_operator == '(') {return 1;}if (level(left_operator) < level(right_operator)) {return 1;}return -1;
}bool is_operator(const char& op) {return op == '|' || op == '.' || op == '*' || op == '(' || op == ')';
}NFA calculate(const NFA& left_operand, const NFA& right_operand, const char& op) {if (op == '|') {return or_selection(left_operand, right_operand);}return concat(left_operand, right_operand);
}//McNaughton–Yamada–Thompson algorithm
NFA re2nfa(const std::string& expression) {std::stack operands;std::stack operators;operators.push('#');for (std::string::const_iterator it = expression.begin(); it != expression.end(); ++it) {while (it != expression.end() && isspace(*it)) {++it;}if (it == expression.end()) {break;}char right_op = *it;if (is_operator(right_op)) {if (right_op == '*') {NFA temp = operands.top();operands.pop();operands.push(kleene(temp));}else {char left_op = operators.top();int cmp_result = cmp(left_op, right_op);//left_op >= right_opwhile (cmp_result != 1) {//left_op=='(' && right_op== ')'if (cmp_result == 0) {operators.pop();break;}else {NFA right_nfa = operands.top();operands.pop();NFA left_nfa = operands.top();operands.pop();operands.push(calculate(left_nfa, right_nfa, left_op));operators.pop();left_op = operators.top();cmp_result = cmp(left_op, right_op);}}if (right_op != ')') {operators.push(right_op);}}}else {operands.push(NFA(2, { Trans(0,1,*it) }, { 1 }));}}char op = operators.top();while (op != '#') {operators.pop();NFA right_nfa = operands.top();operands.pop();if (op == '*') {operands.push(kleene(right_nfa));}else {NFA left_nfa = operands.top();operands.pop();operands.push(calculate(left_nfa, right_nfa, op));}op = operators.top();}return operands.top();
}

NFA转DFA

子集构造法(subset construction)
输入: NFAN=(Q,Σ,δ,q0,F)NFA\ N=\left(Q,\Sigma,\delta,q_0,F\right)NFA N=(Q,Σ,δ,q0​,F)
输出: DFAD=(Q′,Σ,δ′,q0′,F′)DFA\ D=\left(Q',\Sigma,\delta',q_0',F'\right)DFA D=(Q′,Σ,δ′,q0′​,F′)
其中Q′=P(Q),q0′=ϵ−closure(q0),F′={q′∈Q′∣q′∩F≠∅}Q'=\mathcal{P}\left(Q\right),q_0'=\epsilon-closure\left(q_0\right),F'=\left\{q'\in Q'|q'\cap F\neq \empty\right\}Q′=P(Q),q0′​=ϵ−closure(q0​),F′={q′∈Q′∣q′∩F​=∅}

方法: 我们的算法为DDD构造一个转换表DtranDtranDtran。DDD的每一个状态时一个NFANFANFA状态的集合,
我们将构造DtranDtranDtran,使得DDD能够并行地模拟NNN在遇到一个给定串时可能执行的所有动作。

定义如下操作

操作描述
ϵ−closure(s)\epsilon-closure\left(s\right)ϵ−closure(s)能够从NFANFANFA的状态sss开始只通过ϵ\epsilonϵ转换到达的NFANFANFA状态合集
ϵ−closure(T)\epsilon-closure\left(T\right)ϵ−closure(T)能够从TTT中某个NFANFANFA的状态sss开始只通过ϵ\epsilonϵ转换道道的NFA状态集合,即⋃s∈Tϵ−closure(s)\bigcup\limits_{s\in T}\epsilon-closure\left(s\right)s∈T⋃​ϵ−closure(s)
move(T,a)move\left(T,a\right)move(T,a)能够从TTT中某个状态sss出发通过标号为a的转换到达的NFANFANFA状态的集合

我们必须找到当NNN读入某个输入串之后可能位于的所有状态集合。

1)首先读入第一个输入符号之前,N可以位于集合ϵ−closure(s0)\epsilon-closure\left(s_0\right)ϵ−closure(s0​)中的任何状态上,其中s0s_0s0​时NNN的开始状态
2)下面进行归纳,假定NNN在读入输入串xxx之后,可以位于集合TTT中的状态上。
如果下一个输入符号是aaa,那么NNN可以立即移动到集合move(T,a)move\left(T,a\right)move(T,a)中的任何状态。
然而,NNN可以在读入aaa后,再执行几个ϵ\epsilonϵ转换,因此NNN在读入xaxaxa后可能位于ϵ−closure(move(T,a))\epsilon-closure\left(move\left(T,a\right)\right)ϵ−closure(move(T,a))中的任何状态上

子集构造法伪代码如下

Dstates={epsilon-closure(s_0)}; //epsilon-closure(s_0) is unmarked
for(T in Dstates){mark T;for(a in Sigma){//Sigma is the input alphabetU=epsilon-closure(move(T,a));if(U not in Dstates){Dstates.add(U);//U is unmarked}Dtran[T,a]=U;}
}

ϵ−closure(T)\epsilon-closure\left(T\right)ϵ−closure(T)伪代码如下

//push all states in T to the stack
for(t in T){statck.push(t);
}epsilon-closure(T)=T;
while(!stack.empty()){t = stack.top();stack.pop();for((t,epsilon,u) in delta){ //t can move to u by epsilonif(u not in epsilon-closure(T)){epsilon-clousre(T).add(u);stack.push(u);}
}

最终构造的转换表
在这里插入图片描述
最后,DFA所代表的状态集合,包含原来NFA的终态的,作为DFA的终态

有效性证明

NFAN=(Q,Σ,δ,q0,F)NFA\ N=\left(Q,\Sigma,\delta,q_0,F\right)NFA N=(Q,Σ,δ,q0​,F)
DFAD=(Q′,Σ,δ′,q0′,F′)DFA\ D=\left(Q',\Sigma,\delta',q_0',F'\right)DFA D=(Q′,Σ,δ′,q0′​,F′)
其中Q′=P(Q),q0′=ϵ−closure(q0),F′={q′∈Q′∣q′∩F≠∅}Q'=\mathcal{P}\left(Q\right),q_0'=\epsilon-closure\left(q_0\right),F'=\left\{q'\in Q'|q'\cap F\neq \empty\right\}Q′=P(Q),q0′​=ϵ−closure(q0​),F′={q′∈Q′∣q′∩F​=∅}

假设w∈Σ∗w\in \Sigma^*w∈Σ∗,我们要证明w∈L(N)⇔L(N)w\in L(N)\Leftrightarrow L(N)w∈L(N)⇔L(N)

实际上可以证明,假设q,p∈Qq,p\in Qq,p∈Q,
NNN中存在从qqq到ppp的路径表示www,当且仅当DDD中存在从ϵ−closure(q)\epsilon-closure(q)ϵ−closure(q)到P的路径表示www(p∈Pp\in Pp∈P)

证明:
用数学归纳法,
当w=ϵw=\epsilonw=ϵ时,DDD只能直接接受ϵ\epsilonϵ,即P=ϵ−closure(q)P=\epsilon-closure(q)P=ϵ−closure(q)
而NNN,路径只能包含ϵ\epsilonϵ,即p∈ϵ−closure(q)=Pp\in\epsilon-closure(q)=Pp∈ϵ−closure(q)=P

假设∣w∣≤k\left|w\right|\le k∣w∣≤k时成立(字符串www长度小于等于kkk)

当∣w∣=k+1\left|w\right|=k+1∣w∣=k+1时,设w=va,∣v∣=kw=va,\left|v\right|=kw=va,∣v∣=k,且v∈Σ∗,a∈Σv\in \Sigma^*,a\in\Sigmav∈Σ∗,a∈Σ

如图
假设NNN中存在从qqq到ppp的路径表示www
这条路径可以表示为,qqq存在路径表示vvv,到达r1r_1r1​,经过aaa,到达r2r_2r2​,再经过ϵ∗\epsilon^*ϵ∗到达ppp
那么由归纳,PPP中存在从E(q)E(q)E(q)到RRR表示vvv的路径
r2∈move(R,a),p∈ϵ−closure(move(R,a))r_2\in move(R,a),p \in \epsilon-closure\left(move(R,a)\right)r2​∈move(R,a),p∈ϵ−closure(move(R,a)),令P=ϵ−closure(move(R,a))P=\epsilon-closure\left(move(R,a)\right)P=ϵ−closure(move(R,a)),就可以表示www了

假设DDD中存在从ϵ−closure(q)\epsilon-closure(q)ϵ−closure(q)到P的路径表示www,同样的NNN中存在从qqq到ppp的路径表示www
在这里插入图片描述

代码

NFA.h
与上面不同的是,多了ϵ−closure(T),move(T,a)\epsilon-closure(T),move(T,a)ϵ−closure(T),move(T,a)
还写了个find_traverse_symbols,因为我没有定义Σ\SigmaΣ,所以需要找到所有的边上的字符

#pragma once
#include"Trans.h"
#include
#include
#include
#include
class NFA {
public://0 is the only start stateint vertex_cnt;//Qstd::vector transitions;//delta,transition graphstd::unordered_set final_states;//FNFA(int vertex_cnt = 0) :vertex_cnt(vertex_cnt) {}NFA(int vertex_cnt, const std::vector& transitions, const std::unordered_set& final_states) :vertex_cnt(vertex_cnt), transitions(transitions), final_states(final_states) {}int get_vertex_count()const {return vertex_cnt;}void set_vertex_cnt(int vertex_cnt) {this->vertex_cnt = vertex_cnt;}std::vector get_transition()const {return transitions;}void add_transition(int vertex_from, int vertex_to, char trans_symbol) {transitions.emplace_back(vertex_from, vertex_to, trans_symbol);}std::unordered_set get_final_states()const {return final_states;}void add_final_state(int state) {final_states.insert(state);}void clear() {vertex_cnt = 0;transitions.clear();final_states.clear();}void display()const {printf("-------------------------\n");for (Trans temp : transitions) {printf("q_%d -> q_%d: %c\n", temp.vertex_from, temp.vertex_to, temp.trans_symbol);}printf("\n");printf("\n");printf("final_states: ");for (int f : final_states) {printf("%d ", f);}printf("\n");printf("-------------------------\n");}std::unordered_set epsilon_closure(const std::unordered_set& T)const {std::stack st;for (const int& state : T) {st.push(state);}std::unordered_set closure = T;while (!st.empty()) {int t = st.top();st.pop();for (const Trans& trans : transitions) {if (trans.trans_symbol == EPSILON && closure.find(trans.vertex_from) != closure.end() &&closure.find(trans.vertex_to) == closure.end()) {closure.insert(trans.vertex_to);st.push(trans.vertex_to);}}}return closure;}std::unordered_set move_symbol(const std::unordered_set& T, const char& symbol)const {std::unordered_set result;for (const int& state : T) {for (const Trans& trans : transitions) {if (trans.vertex_from == state && trans.trans_symbol == symbol) {result.insert(trans.vertex_to);}}}return result;}//u in states, (u,symbol,t) in transition, result={all symbol}std::unordered_set find_traverse_symbols(const std::unordered_set& states)const {std::unordered_set result;for (const int& state : states) {for (const Trans& trans : transitions) {if (trans.vertex_from == state && trans.trans_symbol != EPSILON) {result.insert(trans.trans_symbol);}}}return result;}
};

DFA.h
这里的转换函数,用的是邻接表
起始状态也是0

#pragma once
#include
#include
#include
#include
class DFA {
public://0 is the only start stateint vertex_cnt;//Qstd::vector > transitions;//delta,transition graphstd::unordered_set final_states;//FDFA(int vertex_cnt = 0) :vertex_cnt(vertex_cnt) {}DFA(int vertex_cnt, const std::vector >& transitions, const std::unordered_set& final_states) :vertex_cnt(vertex_cnt), transitions(transitions), final_states(final_states) {}int get_vertex_cnt()const {return vertex_cnt;}std::vector > get_transitions()const {return transitions;}std::unordered_set get_final_states()const {return final_states;}void display()const {printf("-------------------------\n");for (int i = 0; i < transitions.size(); ++i) {for (const auto& temp : transitions[i]) {printf("q_%d -> q_%d: %c\n", i, temp.second, temp.first);}}printf("\n");printf("\n");printf("final_states: ");for (int f : final_states) {printf("%d ", f);}printf("\n");printf("-------------------------\n");}
};

NFA2DFA.h

#pragma once
#include
#include
#include
#include
#include"Trans.h"
#include"NFA.h"
#include"DFA.h"//subset construction
DFA nfa2dfa(const NFA& nfa) {std::vector > idx2state;std::vector > transitions;std::queue q;q.push(0);idx2state.push_back(nfa.epsilon_closure({ 0 }));transitions.emplace_back(std::unordered_map());while (!q.empty()) {int cur = q.front();q.pop();//for(a in Sigma)for (const char& symbol : nfa.find_traverse_symbols(idx2state[cur])) {std::unordered_set temp = nfa.epsilon_closure(nfa.move_symbol(idx2state[cur], symbol));int i = 0;while (i < idx2state.size()) {if (idx2state[i] == temp) {break;}++i;}//mark unseen stateif (i == idx2state.size()) {q.push(i);idx2state.emplace_back(temp);transitions.emplace_back(std::unordered_map());}transitions[cur][symbol] = i;}}std::unordered_set F = nfa.get_final_states();std::unordered_set final_states;for (int i = 0; i < idx2state.size(); ++i) {for (const int& state : idx2state[i]) {//set which contains final is a final setif (F.find(state) != F.end()) {final_states.insert(i);break;}}}return DFA(transitions.size(), transitions, final_states);
}

NFA转正则表达式

Kleene’s algorithm
GNFA:与NFA类似,但是边是正则表达式

第一步:创建一个唯一的开始状态和接受状态
开始状态用ϵ\epsilonϵ连接原来的开始状态
所有接受状态用ϵ\epsilonϵ连接新的接受状态
在这里插入图片描述
第二步:依次消除非初始状态和非接受状态

假设消除S2S_2S2​
S0,S1S_0,S_1S0​,S1​到S2S_2S2​有边
且S2S_2S2​到S3,S4S_3,S_4S3​,S4​有边
则产生4条边S0→S3S_0\to S_3S0​→S3​,S0→S4S_0\to S_4S0​→S4​,S1→S3S_1\to S_3S1​→S3​,S1→S4S_1\to S_4S1​→S4​,边上的正则表达式为原来的边的连接
在这里插入图片描述

如果一个状态有2条边到另一个状态,则合并
在这里插入图片描述
最后产生
在这里插入图片描述

最复杂的情况之一
在这里插入图片描述

代码

再说

Kleene’s Theorem

正则语言等价于可以被有限状态自动机接受

证明:
正则语言转有限状态自动机:McNaughton–Yamada–Thompson algorithm算法
有限状态自动机转正则语言:Kleene’s algorithm

Myhill–Nerode theorem

可区分字符串

设LLL是一个语言x,y∈Σ∗x,y\in \Sigma^*x,y∈Σ∗
如果∃z\exists z∃z使得xz∈L,yz∉Lxz\in L,yz\notin Lxz∈L,yz∈/​L,则称 x,yx,yx,y在LLL上可区分(distinguishable to L)

引理1

设LLL是一个语言,DFAMDFA\ MDFA M可以识别LLL,x,y∈Σ∗x,y\in \Sigma^*x,y∈Σ∗在L上可区分
则MMM输入xxx到达的状态和输入yyy到达的状态不同

证明:
假设x,yx,yx,y到达的状态一样
则输入xz,yzxz,yzxz,yz到达的状态也一样,
即xz∈Lxz\in Lxz∈L且yz∈Lyz \in Lyz∈L或者xz∉Lxz \notin Lxz∈/​L且yz∉Lyz\notin Lyz∈/​L,与x,yx,yx,y在LLL上可区分矛盾

可区分字符串集合

可区分字符串集合(Distinguishing Set of Strings)
设LLL是一个语言,S={x1,⋯,xk}S=\left\{x_1,\cdots,x_k\right\}S={x1​,⋯,xk​}
如果∀xi,xj∈S,xi≠xj\forall x_i,x_j\in S, x_i\neq x_j∀xi​,xj​∈S,xi​​=xj​,则SSS是L上的可区分字符串集合

引理2

设L⊆Σ∗L\subseteq \Sigma^*L⊆Σ∗是任意语言,SSS是LLL上的可区分字符串集合
则识别LLL的DFADFADFA至少有∣S∣\left|S\right|∣S∣个状态

证明:
设S={x1,⋯,xk}S=\left\{x_1,\cdots,x_k\right\}S={x1​,⋯,xk​}
根据Kleene’s Theorem,对于非正则语言,不存在有限自动机识别,即状态数是无限的

如果LLL是正则语言,设DFAMDFA\ MDFA M可以识别LLL
∀i≠j\forall i\neq j∀i​=j,xi,xjx_i,x_jxi​,xj​是可区分的,则根据引理1,输入MMM,他们将到达不同状态,也就是说至少有∣S∣\left|S\right|∣S∣个状态

定义:≈L\approx_L≈L​, 如果x,yx,yx,y在LLL上不可区分,则x≈Lyx \approx_{L} yx≈L​y,容易验证这是一个等价关系
有了等价关系就可以划分等价类,记为[x]\left[x\right][x]
引理2也可以写作识别正则语言LLL的DFADFADFA的状态数至少为等价类的数量

定理

LLL是正则语言当且仅当LLL有有限个根据≈L\approx_L≈L​划分等价类,并且LLL可以被DFA识别,这个DFA的状态数为等价类的数量

证明:
现在要定义一个DFAMDFA\ MDFA M识别LLL,并且状态数为等价类的数量
显然起始状态为[ϵ]\left[\epsilon\right][ϵ],接受状态为[x](x∈L)\left[x\right](x\in L)[x](x∈L)
设a∈Σa\in\Sigmaa∈Σ,定义 δ([x],a)=[xa]\delta\left(\left[x\right],a\right)=\left[xa\right]δ([x],a)=[xa],容易验证一个等价类的任意字符串,经过aaa,会到达相同的状态
根据数学归纳法,容易验证MMM可以识别LLL

举个例子
(a∣b)∗bbb(a∣b)\left(a|b\right)^*bbb\left(a|b\right)(a∣b)∗bbb(a∣b),可以划分为4个等价类[ϵ],[b],[bb],[bbb][\epsilon],[b],[bb],[bbb][ϵ],[b],[bb],[bbb]
在这里插入图片描述
另一个例子
L={0n1n∣n≥0}L=\left\{0^n 1^n|n\ge0\right\}L={0n1n∣n≥0}不是正规语言,因为可以划分等价类[0],[00],⋯[0],[00],\cdots[0],[00],⋯,有无穷个等价类,所以不是正则语言
(当然也可以用pumping lemma,不过并不是所有非正则语言都可以用pumping lemma验证)

DFA最小化

不可达状态:DFA在任意输入串下都无法到达的状态
等价状态/不可分状态(indistinguishable):同一输入串下不产生区别的状态

消除不可达状态

其实就是根据Σ\SigmaΣ执行bfs

合并等价状态

Hopcroft算法

输入: 一个DFADDFA\ DDFA D,其状态集合为SSS,输入字母表为Σ\SigmaΣ,开始状态为s0s_0s0​,接受状态集为FFF
输出: 一个DFAD′DFA\ D'DFA D′,它和DDD接受相同的语言,且状态最少
方法:
1)首先构造包含两个组FFF和S−FS-FS−F的初始划分Π\PiΠ,这两个组分别是DDD的接受状态组和非接受状态组
2)
在这里插入图片描述
3)如果Πnew=Π\Pi_{new}=\PiΠnew​=Π,令Πfinal=Π\Pi_{final}=\PiΠfinal​=Π并接着执行步骤4;否则,用Πnew\Pi_{new}Πnew​替换Π\PiΠ并重复步骤2
4)在分划Πfinal\Pi_{final}Πfinal​的每个组中选取一个状态作为该组的代表,这些代表构成D′D'D′的状态
a)D′D'D′的开始状态是包含了DDD的开始状态的组的代表
b)D′D'D′的接受状态是那些包含了DDD的接受状态的组的代表
c)令sss是Πfinal\Pi_{final}Πfinal​中某个组GGG的代表,并令DFADDFA\ DDFA D中在输入aaa上离开sss的转换到达状态ttt。
令rrr为ttt所在组HHH的代表。那么在D′D'D′中存在一个从sss到rrr在输入aaa上的转换。

(其实4就是把原来的边拼上去而已)

伪代码
在这里插入图片描述

最小化DFA唯一证明

假设所有的状态都可以到达
设最小化DFAA=(QA,Σ,δA,q0,A,FA)DFA\ A=\left(Q_{A},\Sigma,\delta_A,q_{0,A},F_A\right)DFA A=(QA​,Σ,δA​,q0,A​,FA​)
以及最小化DFAB=(QB,Σ,δB,q0,B,FB)DFA\ B=\left(Q_{B},\Sigma,\delta_B,q_{0,B},F_B\right)DFA B=(QB​,Σ,δB​,q0,B​,FB​)

由最小化,∣QA∣=∣QB∣=k\left|Q_A\right|=\left|Q_B\right|=k∣QA​∣=∣QB​∣=k

S={x1,x2,⋯,xk}S=\left\{x_1,x_2,\cdots,x_k\right\}S={x1​,x2​,⋯,xk​}是LLL上的可区分字符串集合
显然δ(q0,A,x1)≠δ(q0,A,x2)\delta\left(q_{0,A},x_1\right)\neq \delta\left(q_{0,A},x_2\right)δ(q0,A​,x1​)​=δ(q0,A​,x2​)
设qA=δ(q0,A,x1),qB=δ(q0,B,x1)q_A=\delta\left(q_{0,A},x_1\right),q_{B}=\delta\left(q_{0,B},x_1\right)qA​=δ(q0,A​,x1​),qB​=δ(q0,B​,x1​)
即QAQ_AQA​和QBQ_BQB​内的状态可以一一对应

qA=δ(q1,A,x1),qB=δ(q1,B,x1)q_A=\delta\left(q_{1,A},x_1\right),q_{B}=\delta\left(q_{1,B},x_1\right)qA​=δ(q1,A​,x1​),qB​=δ(q1,B​,x1​)也应该一一对应(即边也是一样的
所以A=BA=BA=B

代码

DFAMinimal.h

#pragma once
#include
#include
#include
#include
#include
#include
#include"DFA.h"//bfs
DFA remove_unreachable_state(const DFA& dfa, const std::vector& symbols) {std::queue q;q.push(0);std::unordered_set reachable_state = { 0 };int vertex_cnt = dfa.get_vertex_cnt();std::vector > transitions = dfa.get_transitions();std::unordered_set final_states = dfa.get_final_states();while (!q.empty()) {int cur = q.front();q.pop();std::unordered_map temp = transitions[cur];//traverse symbols (Sigma)for (const char& c : symbols) {if (temp.find(c) != temp.end()) {int state = temp[c];if (reachable_state.find(state) == reachable_state.end()) {q.push(state);reachable_state.insert(state);}}}}std::vector idx2state(vertex_cnt);std::vector > new_transitions;std::unordered_set new_final_states;int cnt = 0;//relabel and find new final statesfor (int i = 0; i < vertex_cnt; ++i) {if (reachable_state.find(i) != reachable_state.end()) {idx2state[i] = cnt;if (final_states.find(i) != final_states.end()) {new_final_states.insert(cnt);}++cnt;}}for (int i = 0; i < vertex_cnt; ++i) {if (reachable_state.find(i) != reachable_state.end()) {std::unordered_map temp;for (const auto& transition : transitions[i]) {if (reachable_state.find(transition.second) != reachable_state.end()) {temp[transition.first] = idx2state[transition.second];}}new_transitions.push_back(temp);}}return DFA(cnt, new_transitions, new_final_states);
}DFA hopcroft(const DFA& dfa, const std::vector& symbols) {int vertex_cnt = dfa.get_vertex_cnt();std::vector > transitions = dfa.get_transitions();std::unordered_set final_states = dfa.get_final_states();std::unordered_set start_states;for (int i = 0; i < vertex_cnt; ++i) {if (final_states.find(i) == final_states.end()) {start_states.insert(i);}}std::vector > partition = { start_states,final_states };//Q'std::vector > work_list = { final_states };//Wwhile (!work_list.empty()) {std::unordered_set q_prime = work_list.back();work_list.pop_back();for (const char& symbol : symbols) {std::unordered_set x;//predecessorfor (int i = 0; i < vertex_cnt; ++i) {if (transitions[i].find(symbol) != transitions[i].end()&&q_prime.find(transitions[i][symbol]) != q_prime.end()) {x.insert(i);}}if (!x.empty()) {std::vector > temp;for (const auto& y : partition) {std::unordered_set y_cap_x;//y cap xstd::unordered_set y_diff_x;//y-x//splitfor (const int& state : y) {if (x.find(state) != x.end()) {y_cap_x.insert(state);}else {y_diff_x.insert(state);}}if (!y_cap_x.empty() && !y_diff_x.empty()) {temp.push_back(y_cap_x);temp.push_back(y_diff_x);auto it = std::find(work_list.begin(), work_list.end(), y);if (it != work_list.end()) {work_list.erase(it);work_list.push_back(y_cap_x);work_list.push_back(y_diff_x);}else if (y_cap_x.size() < y_diff_x.size()) {work_list.push_back(y_cap_x);}else {work_list.push_back(y_diff_x);}}else {//indistinguish, don't split ytemp.push_back(y);}}partition = temp;}}}int cnt = partition.size();//let the partition which contains 0 also be 0 in the new dfafor (int i = 0; i < cnt; ++i) {bool flag = false;for (const int& state : partition[i]) {if (state == 0) {std::swap(partition[i], partition[0]);flag = true;break;}}if (flag) {break;}}std::vector > new_transitions(cnt);std::unordered_set new_final_states;std::vector idx2state(vertex_cnt);//relabel and find new final statesfor (int i = 0; i < cnt; ++i) {for (const int& state : partition[i]) {idx2state[state] = i;if (final_states.find(state) != final_states.end()) {new_final_states.insert(i);}}}for (int i = 0; i < transitions.size(); ++i) {for (const auto& p : transitions[i]) {new_transitions[idx2state[i]][p.first] = idx2state[p.second];}}return DFA(cnt, new_transitions, new_final_states);
}

完整代码

https://github.com/Nightmare4214/re_nfa_dfa

参考
https://en.wikipedia.org/wiki/Regular_language
http://cgosorio.es/Seshat/thompsonForm
https://courses.cs.washington.edu/courses/cse322/01sp/subset.pdf
https://gist.github.com/fonlang/f712e1463bf276b389e27164b60bd023
https://www.omegaxyz.com/2019/02/01/hopcroft-min-dfa/
https://en.wikipedia.org/wiki/DFA_minimization
https://codeantenna.com/a/36ziXkYcjM

https://people.csail.mit.edu/rrw/6.045-2019/notemindfa.pdf
https://cse.sc.edu/~fenner/csce551/minimization.pdf
https://neuraldump.net/2017/11/proof-of-kleenes-theorem/
https://en.wikipedia.org/wiki/Kleene%27s_algorithm
https://inside.mines.edu/~ndantam/csci-561/L11-min-prelecture.pdf

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
不用电脑怎么设置苹果铃声 ip... 需要一个app,加上苹果自带的app即可1、手机下载爱思极速版。因为之前换手机搬移数据时店员帮忙整理...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
才高八斗的主人公 三顾茅庐的主... 古代君王在建立自己的政治体系之时,大多会采取一些礼贤下士的手段,对于一些有卓越才能的人放低姿态。因为...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...