天天看点

自动机初步之DFA

自动机是一种非常有力的工具,其完备的理论可以参考编译原理或者形式语言与自动机等相关教材。从某种定义角度而言,图灵机也是自动机的一种。

这里提到的自动机特指有限状态自动机,简称为FA,根据状态转移的性质又分为确定的自动机(DFA)和非确定的自动机(NFA)。FA的表达能力等价于正规表达式或者正规文法。

FA可以看做是一个有向带权图,图的顶点集合称为自动机的状态集合,图的权值集合为自动机的字母集合,图的边代表了自动机中状态变化的情况。此外,根据需要,自动机还需指定初始状态和终态。

FA最基本的作用就是形式化描述,而且有利于编程实现。以下提到自动机全部指的是DFA。

考虑仅由字母{a, b}组成的字符串,要求字符串中字母b必须成对出现,否则字符串非法。这个规则实现起来其实非常简单,不需要自动机也完全可以。但是我们考虑使用自动机来进行判断。顺便,该规则的正规表达式描述是:(a|bb)*。星号运算代表重复若干次,包括零次。

我们考虑做一个图来表示描述该规则的DFA。

令状态1为初始状态,显然在状态1上,我们还没有违反规则。因此,经过字母a以后我们还可以回到状态1。经过字母b就不能回到状态1了,此时需要一个新状态,令其为2。

状态2表示“待定的”状态,在这个状态时不能肯定字符串是非法的,但也不是合法的。在状态2上,如果经过字母b,就回到了合法的状态也就是状态1,;如果经过字母a,则该字符串肯定是非法的。建立一个新状态即状态3,用于表示非法状态。

状态3比较简单,已经到了非法状态,其后的任何字母都不会改变这个状态了。

因此,该DFA可以表示如下。

自动机初步之DFA

程序实现也非常简单,状态和字母都被编码成整数,使用一个矩阵表示状态转移,再写一个函数表示自动机的运行,对每一个字符串,从状态1开始运行,运行完毕进行状态判断即可。最后能停留在状态1的字符串才是符合规则的,其余都是非法的。

#include <cstdio>

int DFA[][] = {
    {0},
    {,},
    {,},
    {,}
};

int START_STATE = ;

int run(char const word[]){
    int state = START_STATE;
    for(char const*p=word;*p;++p){
        state = DFA[state][*p-'a'];
        if (  == state ) return state;     
    }
    return state;
}

int main(){
    char a[] = "abbaaa";
    char b[] = "aabbba";
    char c[] = "aaab";

    printf("%d\n",run(a));
    printf("%d\n",run(b));
    printf("%d\n",run(c));  

    return ;
}
           

DFA的使用,在一定程度上可以简化程序设计,将程序设计的关键步骤变为了自动机的设计。

考虑hdu2206,判断给定的字符串是否符合IPv4的规则。

为了方便起见,首先设计一个简单一点的DFA,可以判断出该字符串是否由4节组成,每节1到3位数字组成,节与节由小数点分开。

自动机初步之DFA

其中d表示0-9的十个数字。如果是小数点或者数字以外的字母全部去到错误状态,错误状态记作0,在图中未画出。

当格式符合该DFA标准后,再将数值超过255的剔除即可。使用DFA也可以直接判断诸如256、3xx等串不符合IPv4的格式标准,但是那样的话DFA就会更加复杂,不如直接使用数值判断方便。

#include <cstdio>
#include <algorithm>
using namespace std;

int const T[][] = {
//     d  .  other
/*0*/  , , ,//Error state
/*1*/  , , ,
       , , ,
       , , ,
       , , ,
/*5*/  , , ,
       , , ,
       , , ,
       , , ,
/*9*/  ,, ,
       ,,,
       ,,,
       , ,,
/*13*/ ,, ,
       ,, ,//final state 
       ,, ,//final state 
       , ,  //final state                
};

inline int tr(char ch){
    if ( '0' <= ch && ch <= '9' ) return ;
    if ( '.' == ch ) return ;
    return ;
}

int run(char const word[]){
    int state = ;
    for(char const*p=word;*p;++p){
        state = T[state][tr(*p)];
        if (  == state ) return state;
    }
    return state;
}

inline bool isFinal(int state){
    return  == state ||  == state ||  == state;       
}

char S[];
int main(){
    while ( gets(S) ){       
        if ( !isFinal(run(S)) ){
            printf("NO\n"); 
            continue;   
        }

        int a,b,c,d;
        sscanf(S,"%d.%d.%d.%d",&a,&b,&c,&d);
        if ( a >  || b >  || c >  || d >  )
            printf("NO\n"); 
        else
            printf("YES\n"); 
    }
    return ;
}
           

再考虑POJ3332,判断是否为合法的数字。根据题目描述的规则,可以设计如下的DFA。其中前导空格事先排除,后续空格放入了DFA进行判断。

自动机初步之DFA
#include <cctype>
#include <cstdio>

inline int tr(char ch){
    if ( isdigit(ch) ) return ;
    if ( '.' == ch ) return ;
    if ( 'E' == ch || 'e' == ch ) return ;
    if ( '+' == ch || '-' == ch ) return ;
    if ( ' ' == ch ) return ;
    return ;   
}

int DFA[][] = {
    {0},
    ,,,,,,
    ,,,,,,
    ,,,,,,//final
    ,,,,,,
    ,,,,,,//final
    ,,,,,,
    ,,,,,,
    ,,,,,,//final
    ,,,,, //final
};

int run(char const word[]){
    int state = ;
    for(char const*p=word;*p;++p){
        state = DFA[state][tr(*p)];
        if (  == state ) return state;
    }
    return state;
}

inline bool isFinal(int state){
    return  == state ||  == state ||  == state ||  == state;
}

char S[];
int main(){
    int kase;
    scanf("%d",&kase);
    gets(S);
    while(kase--){
        gets(S);
        char *p = S;
        while( ' ' == *p ) ++p;

        printf(isFinal(run(p))?"LEGAL\n":"ILLEGAL\n");
    }
    return ;
}