▶ 不确定的有穷自动机(NFA)
是一个五元组 M = (K, ∑, f, S, Z)其中:K是状态集,∑是输入符号集,f是转化映像,S是初态集,Z是终态集
不确定是说下一个状态可以有多个
▶ 确定的有穷自动机(DFA)
是一个五元组 M = (K, ∑, f, S, Z)其中:K是状态集,∑是输入符号集,f是转化映像,S是唯一初态,Z是终态集
给定当前状态和输入符号,f就可以唯一确定下一个状态
DFA确定化的核心算法: 子集法
通俗的说:首先明确,
- NFA中的多个状态,共同组成了DFA中的多个状态。
- 我们先将NFA的初态集的闭包入队,然后BFS
- 每次从queue中取出一个状态集,用字母表的每个字母对其进行一次转换后取闭包(
ε-closure(move(I))
),如果产生新状态则入队,继续BFS。
其中,较为关键是基于f的一次转化函数move、以及ε-closure闭包函数。代码中专门有注释说明。
首先定义NFA和DFA的JavaBean类
// NFA类(属性与命名与五元式完全一致)
public class NFA {
private List<Integer> K; // 状态集
private char[] letters; // 字母表
private String[][] f; // 转换函数
private List<Integer> S; // 初态集
private List<Integer> Z; // 终态集
// setter和getter这里省略
}
// DFA类(属性与命名与五元式完全一致)
public class DFA {
private List<Integer> K; // 状态集
private char[] letters; // 字母表
private String[][] f; // 转换函数
private int S; // 唯一初态
private List<Integer> Z; // 终态集
// setter和getter这里省略
}
下面是子集法的核心逻辑,注意move和closure函数尤为关键 >_<
public class NFA2DFA {
/**
* definite:【子集法】将NFA确定化为DFA
* @param nfa 输入nfa
* @return 返回dfa
*
* 伪代码逻辑:
* DFA状集合C(注:C的每个成员又都是NFA的状态state的集合)
* Queue 临时队列queue;
* NFA的初态集S(K0)的闭包入队;
* while(队不空):
* 取出当前状态I;
* for 每个输入字母 in letters:
* nextI = ε-closure(move(I, letter));
* if(C 不包含 nextI) :
* 则nextI入队
*
* 最终的C即为确定化生成的DFA的状态集
*/
public DFA definite(NFA nfa){
List<Integer> K0 = nfa.getS(); // 这几行是把nfa对象的属性先拿下来
char[] letters = nfa.getLetters();
String[][] f = nfa.getF();
List<Integer> Z = nfa.getZ();
DFA dfa = new DFA();
List<Integer> K = new ArrayList<>(); // DFA状态集合(已重命名)
int k = 0; // 用于DFA状态集的重命名
List<String[]> listF = new ArrayList<>(); // 用于暂存转换函数f
List<Integer> listZ = new ArrayList<>(); // 用于暂存终态集Z
StringBuilder sb = new StringBuilder(); // 用于输出整个子集法的过程(debug用)
Set<List<Integer>> set = new HashSet<>(); // 状态集临时集合
Queue<List<Integer>> queue = new LinkedList<>(); // 状态集临时队列
Map<List<Integer>, Integer> map = new HashMap<>(); //「状态集」向「命名」的映射
List<Integer> closure_K = closure(K0, f);
K.add(k);
map.put(closure_K, k++);
queue.add(closure_K);
while(!queue.isEmpty()){ // BFS
List<Integer> I = queue.poll();
for(char letter : letters){
List<Integer> nextI = closure(move(I, letter, f), f);
if(nextI.isEmpty()) continue;
if(!containsI(set, nextI)){
// 如果产生了一个新状态,就进行接下来的诸多操作
// 给它一个新名字并维护k、确定它是不是终态(包含NFA的终态)、入集合、入队
nextI.sort(Comparator.comparing(Integer::intValue));
map.put(nextI, k);
K.add(k);
if(!Collections.disjoint(nextI, Z)){
listZ.add(k);
}
set.add(nextI);
queue.add(nextI);
k++;
}
System.out.print(I); // 未重命名输出一下(debug用)
System.out.println(nextI); // 未重命名输出一下(debug用)
listF.add(new String[]{String.valueOf(map.get(I)), String.valueOf(letter), String.valueOf(map.get(nextI))});
sb.append(map.get(I)).append(letter).append(map.get(nextI)).append('\n');
}
}
System.out.println(sb.toString()); // 重命名后的状态和转化情况(debug用)
// 下面是构造出DFA对象,作为返回值
int len = K.size();
String[][] f2 = new String[len][len];
for(String[] tmp : f2){
Arrays.fill(tmp, "");
}
for(String[] arr : listF){
f2[Integer.parseInt(arr[0])][Integer.parseInt(arr[2])] += arr[1];
}
dfa.setK(K);
dfa.setLetters(letters);
dfa.setF(f2);
dfa.setS(0);
dfa.setZ(listZ);
return dfa;
}
/**
* ε-closure闭包运算:某个状态集经过任意多个ε,得到当前的真正状态集
* @param I 当前状态集
* @return 当前真正的状态集(closureI)
*/
private List<Integer> closure(List<Integer> I, String[][] f){
// 经过任意多个ε,因此BFS ! ! !
List<Integer> closureI = new ArrayList<>();
Queue<Integer> queue = new LinkedList<>();
for(int i : I){
queue.add(i);
while (!queue.isEmpty()){
int n = queue.poll();
for(int iNext = 0; iNext < f.length; iNext++){
for(char c : f[n][iNext].toCharArray()){
if(c == 'ε' && !closureI.contains(iNext)){
closureI.add(iNext);
if(n != iNext){
queue.add(iNext);
}
}
}
}
}
}
return closureI;
}
/**
* move方法:当前状态集通过某个字母可以转化到的下一状态集(一步转换,没有进行ε-闭包运算)
* @param I 当前的状态集
* @return 返回下一状态集
*/
private List<Integer> move(List<Integer> I, char letter, String[][] f){
List<Integer> nextI = new ArrayList<>();
for(int i : I){
for(int iNext = 0; iNext < f.length; iNext++){
for(char c : f[i][iNext].toCharArray()){
if(c == letter && !nextI.contains(iNext)) {
nextI.add(iNext);
}
}
}
}
return nextI;
}
/**
* 判断一个Set<List>是否包含某个List
* @param set
* @param list
*/
private boolean containsI(Set<List<Integer>> set, List<Integer> list){
for(List<Integer> l : set){
if(listEquals(l, list)){
return true;
}
}
return false;
}
/**
* 判断两个List集合是否相等(不考虑顺序)
* @param list1
* @param list2
*/
private boolean listEquals(List<Integer> list1, List<Integer> list2){
list1.sort(Comparator.comparing(Integer::intValue));
list2.sort(Comparator.comparing(Integer::intValue));
return list1.toString().equals(list2.toString());
}
}