字符串匹配问题-KMP算法,哈希,AC自动机

KMP算法
首先要说的一个问题是关于单个模板串在文本串中的匹配问题。给定一个模板串S,和一个文本串P,我们怎么得到S在P中出现的位置呢?

我们关键要解决的问题是,当模板串与文本串中的某一子串不匹配时,模板串下个要比较的子串应该在哪个位置?这里便有了next数组的出现。

我们用next[i]表示子串s[0…i]有多大长度的相同前缀后缀。下面是next函数得到的代码。具体流程如下:
1.在一个循环中以i=1到i=n-1的顺序计算next函数next[i]的值。
2.为了计算当前的next函数值next[i],我们令变量j表示右端点位于i-1的最好的后缀的长度。初始时j=next[i-1]。
3.通过比较s[j]和s[i]来检查长度为j+1的后缀是否同时也是一个前缀。如果两者相等,那么我们置next[i]=j+1,否则我们减少j至next[j-1]并且重复该过程。
4.如果j=0并且仍没有任何一次匹配,则置next[i]=0并且至下一个下标i+1.

void Get_Next(char* s){
   int i, j;
   n = strlen(s);
   next[0] = 0;
   for(i = 1;i < n;i++){
      j = next[ i - 1];
     while(j > 0 && s[i] != s[j]) j = next[j - 1];
        if(s[i] == s[j]) j++;
        next[i] = j;
 }
   return ;
}

我们会到问题本身,我们要注意一下几点:
1.我们的失配数组应当建立在模式串意义下,而不是文本串意义下。因为显然模式串要更加灵活,在失配后换位时,更灵活简便地处理。
2.我们的操作只是针对模式串的前缀−−–−−毕竟是失配函数,失配之后只有可能是某个部分前缀需要“快速移动”。
具体代码如下:

   lens = strlen(s+1), lenp = strlen(p+1);//s是文本串,p是模板串
   for(i = 2;i <= lenp;i++){
      j = next[i-1];
      while(j && p[i] != p[j+1]) j = next[j];
        if(p[j+1] == p[i]) j++;
        next[i] = j;
 }//得到模板p的next数组
   j = 0;
   for(i = 1;i <= lens;i++){
      while(j > 0 && p[j+1] != s[i]) j = next[j];
      if(p[j+1] == s[i]) j++;
      if(j == lenp) printf("%d\n",i - lenp + 1), j = next[j];
 }
 
   for(i = 1;i <= lenp;i++) printf("%d ",next[i]);

哈希
字符串哈希的用处在于给定一堆长度不一的字符串,区别出其中互异的元素。
具体做法见下:

int idx(char x){
   return x - 'a' + 1; 
}//这里主要是为了得到每个字符所代表的值。

ll hash(char* s){
   ll Hash = 0;
   int i, len = strlen(s);
   for(i = 0;i < len;i++){
       Hash = (Hash * p + idx(s[i]))%mod;
 } //其中p和mod取得尽量大可以保证冲突概率小
   return Hash;
}//求解字符串的哈希值

//子串哈希值:hash(l,r)=((hash[r]-hash[l-1]*pow(p,r-l+1))%mod+mod)%mod

求解出的哈希值一般存在一个set集合中,使用set中的count函数即可判断是否有重复了。
AC自动机
由于KMP算法只能实现单个模板串在文本串中的匹配,无法高效的完成多个模板串在文本串中的匹配,因此AC自动机应运而生了。
AC自动机主要分为三部分:
1.用模式串构建字典树

int trie[maxn][26];//字典树
int cntword[maxn];//单词出现的个数
int fail[maxn];

void insertWords(char* s){
   int i, root = 0, l = strlen(s);
   for(i = 0;i < l;i++){
     int next = s[i] - 'a';
     if(!trie[root][next]) trie[root][next] = ++cnt;
     root = trie[root][next];
 }
   cntword[root]++;
}

2.找fail指针
当发现失配的字符失配的时候,跳转到fail指针指向的位置,然后再次进行匹配操作,AC自动机之所以能实现多模式匹配,就归功于Fail指针的建立。当前节点t有fail指针,其fail指针所指向的节点和t所代表的字符是相同的。因为t匹配成功后,我们需要去匹配t->child,发现失配,那么就从t->fail这个节点开始再次去进行匹配。

void getFail(){
   queue q;
   int i;
   for(i = 0;i < 26;i++){
      if(trie[0][i]){
      fail[trie[0][i]] = 0;
      q.push(trie[0][i]);
  }
 }
   while(!q.empty()){
      int now = q.front();
      q.pop();
      for(i = 0;i < 26;i++){
      if(trie[now][i]){
      fail[trie[now][i]] = trie[fail[now]][i];
      q.push(trie[now][i]);
   }
     else trie[now][i] = trie[fail[now]][i];
  }
 }
}

3.文本串的匹配

int query(char* s){
   int now = 0, ans = 0, i, j, l = strlen(s);
   for(i = 0;i < l;i++){
      now = trie[now][s[i] - 'a'];
    for(j = now;j && cntword[j] != -1;j = fail[j]){
      ans += cntword[j];
      cntword[j] = -1;
  }
 }
   return ans;
}//这里实现的是,有多少模板串在文本串中出现,当然AC自动机还可以实现模板串出现次数的统计,暴力跳fail的代码和这个差不多。

关于AC自动机用拓扑排序优化的坑后面再补吧。

你可能感兴趣的:(字符串)