DFA算法的实现与最小化

有限自动机分为确定有限自动机(DFA)和不确定有限自动机(NFA),这里介绍DFA,即确定有限自动机。

1. DFA的形式定义

从形式上说,一个有限状态自动机可以用下面的5个参数来定义:

  • Q: 状态q0, q1, ... , qN的有限集合
  • Σ: 有限的输入符号字母表
  • q0: 初始状态
  • F: 终极状态的集合, F∈Q
  • δ(q, i): 状态之间的转移函数或转移矩阵。给定一个状态q∈Q和一个输入符号i∈Σ, δ(q, i)返回一个新的状态q'∈Q。

2.DFA的java实现

       这里定义了一个抽象父类FA,DFA和NFA继承了FA类。

public abstract class FA {
    protected List<FAState> acceptingStates;        //可接收状态(结束状态)
    protected List<FAState> states;                    //所有状态
    protected List<String> alphabet;                //符号字母表
    //状态转移矩阵(使用邻接链表表示)
    protected List<List<TransitMatElement>> stateTransitionMat; 

   //....
}
public class DFA extends FA {        
    protected FAState startState;       //开始状态
}
以上就是自己实现的DFA对应的类实例变量的定义。其中 FAState 类表示一个状态节点,包含一个实例属性id,

用于唯一区分一个FAState。

       构造DFA对象时,需要传入一个特定格式的文本文件的路径。

       文件的格式,举例如下:
                         5 3
                         1 2 3 4 5
                         b a !
                         1
                         5
                         1 -1 -1
                         -1 2 -1
                         -1 3 -1
                         -1 3 4
                         -1 -1 -1
       其中,第一行的两个数字分别表示自动机状态的数目和符号字母表中符号的数目
                    第二行的数字为每一个状态的id
                    第三行为符号串(符号串间以空格隔开)
                    第四行的数字为开始状态的id
                    第五行的数字为结束状态的id
                    最后几行为状态转换矩阵(其中数字表示跳转到的状态在状态列表中的下标,-1表示一个状态不能遇到相应的符号)

          上面的例子表示的DFA对象如下图所示:
                      


3. 使用DFA识别符号串

        DFA构造好了之后就可以用来识别符号串。这里引用了NFA/DFA算法 - 于公摊的杂货铺中的一个例子:

      如何通过设计状态及其转移方法来实现一个分析器呢?当然,如果一个字符串仅仅包含a或者b的话,那么分析器的状态只有四种:

 “奇数a奇数b”、“奇数a偶数  b”、“偶数a奇数b”、“偶数a偶数 b”。我们把这些状态依次命名为aa、aB、Ab、AB。大写代表偶数,

 小写代表奇数。当工作还没开始的时候,分析器已经读入的字符串是空串,那么理所当然的起始状态应当是AB。当分析器读完所有

 字符的时候,我们期望读入的字符串的a和b的数量都是偶数,那么结束的状态也应该是AB。于是我们给出这样的一个状态图:

            clip_image001

                                                              图3.1  检查一个字符串是否由偶数个a和偶数个b组成的状态图

       在这个状态图里,有一个短小的箭头指向了AB,代表AB这个状态是初始状态。AB状态有粗的边缘,代表AB这个状态

 是结束的可接受状态。 一个状态图的结束状态可以是一个或者多个。在这个例子里,起始状态和结束状态刚好是同一个状态。

标有字符”a”的箭头从AB指向aB, 代表如果分析器处于状态AB并且读入的字符是a的话,就转移到状态aB上。我们把这个状态图

应用在两个字符串上,分别是”abaabbba”和”aababbaba”。

      其中,第一个字符串是可以接受的,第二个字符串是不可接受的(因为有5个a和4个b)。

      分析第一个字符串的时候,状态机所经过的状态是:

                 AB[a]aB[b]ab[a]Ab[a]ab[b]aB[b]ab[b]aB[a]AB

      分析第二个字符串的时候,状态机所经过的状态是:

                AB[a]aB[a]AB[b]Ab[a]ab[b]aB[b]ab[a]Ab[b]AB[a]aB

      第一个字符串”abaabbba”让状态机在状态AB上停了下来,于是这个字符串是可以接受的。第二个字符串”aababbaba”让状态机

 在状态aB上停了下来 ,于是这个字符串是不可以接受的。

        DFA识别符号串的核心算法实现如下:        

        /**
	 * 使用自动机识别符号串
	 * @param words 待匹配符号串
	 * @return 如果接受,则返回true,否则,返回false 
	 */
	public boolean recognize(String[] words) {
		FAState currentState = this.startState;
		int countOfWordsRecognized = 0;
		while(countOfWordsRecognized <= words.length) {
			if(isAccepted(currentState, countOfWordsRecognized, words.length)) {  //接收
				return true;
			} else if(wordsTerminatedButNotAccepted(currentState, words.length,
					countOfWordsRecognized)) {
				return false;
			}				
			// 当前待识别的单词在alphabet中的下标
			int indexOfAlpha = alphabet.indexOf(words[countOfWordsRecognized]);
			//查找状态转移矩阵,获取将要跳转到的状态的下标
			int transition = 
				getIndexOfStateToSwitch(states.indexOf(currentState), indexOfAlpha);
			if(transition == -1) {			//不能匹配当前符号串,拒绝
				return false;
			} else {
				currentState = this.states.get(transition);   //进行下一个符号串的接收				
				countOfWordsRecognized++;								
			}								
		}		
		return false;
	}

4.DFA最小化

4.1 最小状态DFA的含义

1.没有多余状态(死状态)

2. 没有两个状态是互相等价(不可区别)

4.2 两个状态s和t等价的条件

兼容性(一致性)条件——同是终态或同是非终态
传播性(蔓延性)条件——从s出发读入某个a和从t出发经过某个a并且经过某个b到达的状态等价。

4.3 举例说明

下面就以如下图所示的DFA对象为例说明最小化DFA的操作

                                                   

DFA的最小化

1. 将状态列表S中的状态分为两个子集:终结状态s1(也叫可接受状态)和非终结状态s2

    对于上图中的DFA, s1 = {4, 5, 6, 7}, s2 = {1, 2, 3}

2. 查看s2 = {1, 2, 3}是否可以继续划分

    状态2遇到a转向s1中的状态4,不在s2中,于是将s2继续划分,分为{1, 3}和{2}这两个集合。

    继续查看{1, 3}是否可以继续划分。由于3遇到b转向s1中的状态5,于是将{1, 3}继续划分,分为{1}和{3}这两个集合。

3. 查看 s1= {4, 5, 6, 7}是否可以继续划分

     因为4, 5, 6, 7经过a和b跳转到的状态都在s1中,不可再分。因而这四个节点可以用一个节点来表示。

需要注意的是,当最终所以的子集不可再分时,如果一个子集内的状态不止一个,则由于同一子集内的状态等价,

同一子集内的节点之间的状态跳转可以不用考虑。但是如果这个子集内的某一状态遇到一个符号转向本身时,

这种向自身的转移要体现在新的替换的状态上。例如4,5,6,7最终属于同一子集,最终用节点4来替换这4个节点时,

需要在状态转移矩阵中加上遇到a跳转到节点4,与遇到b跳转到节点4的情况。

      最终得到的最小化DFA如下图所示:

                             

4.4 具体实现

DFA最小化的核心算法如下:

/**
	 * 最小化当前DFA对象
	 */
	public void simplify() {
		//用于存放最小化的过程中产生的状态划分
		List<List<FAState>> stateLists = new ArrayList<List<FAState>>();
		// phrase 1: 将化简前的DFA的状态分为非可接受状态和可接受状态两部分
		List<FAState> nonTerminalStates = new ArrayList<FAState>();
		List<FAState> copyOfOriginalState = deepCloneOfFAStateList(this.states);
		for(FAState state : copyOfOriginalState) {
			if(!this.acceptingStates.contains(state)) {
				nonTerminalStates.add(state);
			}
		}
		List<FAState> terminalStates = deepCloneOfFAStateList(this.acceptingStates);
		stateLists.add(nonTerminalStates);
		stateLists.add(terminalStates);
		
		// phrase 2: 看nonTerminalStates能否再分,如果可以,则进行划分
		splitStateListIfCould(stateLists, nonTerminalStates);		
		
		// phrase 3: 看terminalStates能否再分,如果可以,则进行划分
		int leftMostEndStateIndex = splitStateListIfCould(stateLists, terminalStates);
		
		// phrase 4: 根据存储状态列表的列表的每一个元素作为一个状态,构造最小化DFA
		rebuildDFAWithSimplifiedStateList(stateLists, leftMostEndStateIndex);
	}
        DFA的构造以及用于识别符号串,最小化的完整代码可以到http://download.csdn.net/detail/xxc1605629895/7093157下载。

5. 参考资料

1. 正规式->最小化DFA说明 - 碧远|bystander, http://leaver.me/archives/369.html

2. NFA/DFA算法 - 于公摊的杂货铺, http://blog.csdn.net/yukuninfoaxiom/article/details/6057736

  • 11
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
以下是一个简单的DFA最小化的Python代码实现: ```python def dfa_minimization(dfa): # 初始化等价类集合和未划分等价类队列 P = [set(dfa.final_states), set(dfa.states) - set(dfa.final_states)] W = [set(dfa.final_states)] while W: A = W.pop() for x in dfa.input_symbols: X = set(y for y in dfa.states if (y, x) in dfa.transitions and dfa.transitions[(y, x)] in A) if not X: continue for Y in P: if X & Y and Y - X: P.remove(Y) P.extend([X & Y, Y - X]) if Y in W: W.remove(Y) W.extend([X & Y, Y - X]) else: if len(X & Y) <= len(Y - X): W.append(X & Y) else: W.append(Y - X) break # 构建新的状态转移表和状态集合 new_states = [] new_transitions = {} new_start_state = None new_final_states = set() for i, p in enumerate(P): state_name = ','.join(sorted(p)) new_states.append(state_name) if dfa.start_state in p: new_start_state = state_name if p & dfa.final_states: new_final_states.add(state_name) for x in dfa.input_symbols: X = set(y for y in dfa.states if (y, x) in dfa.transitions and dfa.transitions[(y, x)] in p) if X: new_transitions[(state_name, x)] = ','.join(sorted(X)) return DFA(new_states, dfa.input_symbols, new_transitions, new_start_state, new_final_states) ``` 其,`dfa`是输入的DFA对象,`DFA`是一个自定义的DFA类,包含DFA的基本信息,如状态集合、输入符号集合、状态转移表、起始状态和终止状态集合。在代码实现,使用了Hopcroft算法,采用了等价类划分的方式进行DFA最小化处理。最终返回一个最小化后的新DFA对象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值