有限自动机字符串匹配


引言:


本文参考自《算法导论》中 “32.3 利用有限自动机进行字符串匹配” ,其目的不仅仅是为了改善常规算法的时间复杂度问题,更是为了给在解决类似情况提供一个有限自动机方案的参考。


很多字符串匹配算法都要建立一个有限自动机,它是一个处理信息的简单机器,通过对文本字符串 T 进行扫描,找出模式 P 的所有出现位置。这些字符串匹配的自动机都非常有效:它们只对每个文本字符检查一次,并且检查每个文本字符时所需的时间为常数。因此,在模式预处理完成并建立好自动机后进行匹配所需要的时间为 O(n) 。



1、朴素字符串匹配算法:


朴素字符串匹配算法时通过一个循环找到所有有效偏移,
该循环对 n-m+1 个可能的 s 进行检测,看是否满足条件 P[1…m] = T[s+1…s+m]。


朴素字符串匹配算法伪代码:

NAIVE-STRING-MATCHER(T,P)
    n=T.length
    m=P.length
    for s = 0 to n-m
        if p[1...m] == T[s+1,s+m]
            print "Pattern occurs with shift" s

最坏的情况下,朴素字符串匹配算法运行时间为 O((n-m+1)m)。



2、有限自动机:


一个有限自动机 M 是一个 5 元组(Q,q, A, ∑,δ)

Q 是状态的有限集合
q∈Q 是一个初始状态
A 包含于 Q 是一个特殊的接受状态集合
∑ 是有限输入字母表
δ 是一个从 Q✖∑ 到 Q 的函数,称为 M 的转移函数


下图为有限自动机的状态转移图,模式串为 ababaca 。
在这里插入图片描述

转移函数算法伪代码:

其中, Pk ⊇ Pqa 表示 Pk是 Pqa的后缀。

COMPUTE-TRANSITION-FUNCTION(P,∑)
    m = P.length
    for q = 0 to m
       for each charater a∈∑
          k = min(m+1,q+2)
          repeat
              k = k - 1
          until Pk ⊇ Pqa
          δ(q,a) = k
    return δ

有限自动机算法伪代码:

FINITE-AUTOMATON-MATCHER(T,δ,m)
    n = T.length
    q = 0
    for i = 1 to n
        q = δ(q,t[i])
        if q == m
            print "Pattern occurs with shift" i-m


3、详细代码:


转载自 https://blog.csdn.net/giftedpanda/article/details/86774815


#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<vector>

using namespace std;

bool Matching_Prefix_Suffix(char* P,int k,int q,char c)
{                               //P为模式串 K为要验证的前缀和后缀的字符串长度
    if(k==0)                    //q为当前自动机主线长度
    return true;              //k=0 空字符串 前缀和后缀肯定相等
    if(k==1){                //只有一个字符串 证明自动机刚好开始创建
        return P[0]==c;      //如果模式串的第一个和其中的c相等 前缀等于后缀
    }
    return P[k-1]==c&& (!strncmp(P,P+q-k+1,k-1));    //检验P[0...k-1]==P[q-k+1...q]
}
 
vector<map<char,int> > Compute_Transition_Function( char *P,const char* input_character)
{                                //计算转移函数的值
    int m=strlen(P);                //模式串的长度
    int j=0,k;
    printf("The main length of Finite_Automaton_Matcher is %d\n",m);
    vector<map<char,int> >transition_map(m+1);         //创建一个vector 一共有m+1个数据
    for(int i=0;i<m;i++){                   //对于模式串的长度
        j=0;
        while(input_character[j]!='\0'){      //对于输入串的每一种可能字符
         k= min(m+1,i+2);               //因为对于长度为i的字符串 它的转移函数最大值为i
        do{                          //数组下标从0开始 再加上后面k一来就减1  所以为i+2
            k=k-1;                  //找到一个最大值k使得模式串的P[0...k]==P[...n-1]
    }while(!Matching_Prefix_Suffix(P,k,i,input_character[j]));
        transition_map[i][input_character[j]]=k;
        j++;
        }
    }
    return  transition_map;           //返回一个vector 每一个元素为 map<char,int>
}                                     //char 为自动机中的字符 int 为转移函数值
 
void Finite_Automaton_Matcher(char* T,char* P,vector<map<char,int> >transition_map)
{
    int n=strlen(T);                           //文本串长度
    int m=strlen(P);                           //模式串长度
    int q=0;                                   //转移函数的值
     for(int i=0;i<n;i++){                    //对于文本串中的每一个字符
     q = transition_map[q][T[i]];             //迭代 前一个字符的转移函数值
     if(q==m)                                //转移函数的值等于模式串的长度
     printf("Pattern occurs with shift %d\n",i+1-m);   //模式串的有效位移为i-m+1
    }
}

int main()
{
    const char* input_character="abc";        //输入字母表
    char T[]="abababacaba";                  //文本串
    char P[]="ababaca";                      //模式串
    vector<map<char,int> >transition_map=Compute_Transition_Function(P,input_character);
    Finite_Automaton_Matcher(T,P,transition_map);
    return 0;
}

此外,有关字符串匹配的 KMP 算法,请参见:

https://blog.csdn.net/qq_30534935/article/details/100713917

猜你喜欢

转载自blog.csdn.net/qq_30534935/article/details/103694920