算法1:RK算法

算法描述:

(1)计算模式串的Hashcode

方式1:按位相加;

方式2:看成26进制数转化为十进制,如abc = 1x26^2 + 2x26^1 + 4x26^0;

方式2缺点:字符串很长时,对应的十进制数会非常大

(2)主串采用增量计算

例如:主串:abbcefg;模式串bce

第一次计算abb,第二次计算bbc时:新Hahcode = 旧Hashcode - 'a' + 'c'

(3)检查 Hash Collision

rk平台模型推理设置gpu和cpu_字符串

rk平台模型推理设置gpu和cpu_后缀_02

//20200306
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN = 1000;
char pattern[MAXN],major[MAXN];
int hashcode(char* arr,int begin,int end){
    int sum = 0;
    for(int i=begin;i<end;i++){
        sum += (int)arr[i] - 'a' + 1; // ASCII of 'a' is 97
    }
    return sum;
}
int changehashcode(int sumori,int begin,int end){
    return sumori - major[begin-1] + major[end-1]; // +96-96 = 0
}
bool hashcollision(int begin,int end){
    bool state = false;
    for(int i=begin;i<end;i++){
        if(major[i] != pattern[i-begin]){
            state = true;
            break;
        }
    }
    if(state) return true;
    else return false;
}

int main(){
    int pos = -1;
    scanf("%s\n%s",pattern,major);
    int lenp = strlen(pattern);
    int lenm = strlen(major);
    int hashp = hashcode(pattern,0,lenp);
    int hashm = hashcode(major,0,lenp);
    if(hashm == hashp && !hashcollision(0,lenp)) return 0;
    for(int i=1;i<lenm-lenp;i++){
        hashm = changehashcode(hashm,i,i+lenp);
        if(hashm != hashp) continue;
        else if(hashcollision(i,i+lenp)) continue;
        else {
            pos = i;
            break;
        }
    }
    cout<<pos;
    return 0;
}
/*---Test---
bce
abbcefgh
//collision
bce
abcbefgh
*/

View Code

 

 

算法2:KMP算法

1.什么是next数组?

子串s[0...i]的 最长 相等前后缀 的 前缀最后一位 的下标。(也是最长相等前后缀的长度

  什么是前缀?子串s[0...i]中s[0...k](k<i)为前缀,s[...i]为后缀,其中前后缀长度相等。也就是从0开始往后数k个,从最后一个字符开始往前数k个(k<i);(注意k!=i,即不能是子串本身)

举例:s[] = abababc

  当i=0时,子串为a,k<i=0中k无法取值,所以next[0] = -1;

  当i=1时,子串为ab, k=0 < i=1,前缀a,后缀b,不相等,

      所以next[1] = -1;

  当i=2时,子串为aba,k=0 < i=2, 前缀a,后缀a,相等,k=0

            k=1 < i=2,前缀ab,后缀ba,不相等,

      所以最长相等前后缀(即最大的k)为0,所以next[2] = k = 0;

  当i=3时,子串为abab,k=0 < i=3,前缀a,后缀b,不相等

              k=1 < i=3,前缀ab,后缀ab,相等,k=1

              k=2 < i=3,前缀aba,后缀bab,不相等,

      所以最长相等前后缀(即最大的k)为1,所以next[3] = k = 1;

  当i=4,5,6时,同理;

  当i=7(strlen(s) = 7)时,子串为abababc,k=0 < i=7,前缀a,后缀c,不相等。OK,这个时候k就不需要继续往下取值了,因为不可能再相等了,也就是说遇到不相等的情况就可以终止了。【相等k继续递增,不相等k立即终止,next[i]=k-1】;

  最终得到next数组为[-1,-1,0,1,2,3,-1]。

 

2.如何求next数组?(上面举例时采取的求next数组算法比较繁琐,故采用下述递推方法)

我们将在next[i-1]的基础上求next[i]:

  假设我们已经求出next[i-1],不妨令它等于j(也就是说s[0...i]的最长相等前后缀的长度为j),那么当我们继续读入一位时,最长相等前后缀的长度 在原来基础上 最多增加1位,也就是说,最长相等前后缀的长度最长为next[i-1]+1;

  (理解:字符串xyx的 最长相等前后缀长度为strlen(x),那么xyxa 最长相等前后缀长度 不可能超过strlen(xa) 并且 xyxa 最长相等前后缀长度等于strlen(xa)当且仅当a == y[0])

  也就是说,我们判断新读入的这个字符a与y[0]是否相等即可;y[0]是什么?它就是下标j+1所对应的字符,即读入该字符之前的那个字符串 最长相等前后缀的前缀最后一位的下标+1 作为下标对应的字符。

  如果 a == y[0],那么next[i] = j+1即可;

  如果a != y[0],该怎么办?

字符串waz最长相等前后缀的前缀 最后一位的下标,不妨假设这个下标刚好对应字符a(如果不是我们循环地让j=next[j]即可,直到遇到a或到-1结束),那么字符串z就变成了wa,那么整个字符串wazyuwa中最长相等前后缀的长度就是strlen(wa)。

 

3.什么是KMP算法?

字符串“自匹配”的过程,也就是在给定字符串的子串s[0,1,2...i]中寻找s[0,1,...k](k<i)和s[.....,i]使得这两个子串完全相等并且k尽可能的大。那么同样的,字符串匹配问题是指给定一个模式串pattern,去和给定的主串text的子串匹配,寻找与模式串相匹配的子串,这个过程和模式串的next数组过程大同小异。

  因此,模仿求next数组的过程,我们从主串text和模式串pattern的第一个字符去匹配。如果相等,我们继续匹配text和pattern的第二个字符......如果还没有到pattern的末尾就遇到不相等的情况,这个时候我们要移动pattern位置了,如何一位一位地移动,那么就退化成BF算法了。那么究竟应该移到哪里呢?我们知道,模式串的next[i]数组记录的是模式串的子串pattern[0,1.....,i] 最长相等前后缀 的 前缀最后一位 的下标k,也就是说pattern[0,...k]和pattern[m,....,i](m = i-k)是一样的串。不匹配发生在pattern[i+1]和text[i+1],也就是说pattern[0,...,i]和text[0,....i]都匹配,那么pattern[m,...,i]和text[m,....i]匹配,那么pattern[0,...,k]和text[m,....i]匹配,那我们继续比对pattern[k+1]和text[i+1]是否相等就行了,而k+1就等于next[i+1]。

 

4.总结

next数组究竟有哪些含义?

(1)子串s[0...i]的 最长相等前后缀 的 前缀最后一位 的下标;

(2)子串s[0...i]的 最长相等前后缀的长度;

(3)主串与模式串匹配时,j+1位不能匹配时,j应该退回的位置。

 

5.KMP算法对应练习题洛谷OJ

AC代码:

rk平台模型推理设置gpu和cpu_字符串

rk平台模型推理设置gpu和cpu_后缀_02

//20200306
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN = 1000000;
char pattern[MAXN],text[MAXN];
int next[MAXN];
int value[MAXN];//store the result in LOGU OJ 
void getNext(){
    int len = strlen(pattern);
    next[0] = -1;
    int j = next[0];
    for(int i=1;i<len;i++){
        while(j!=-1 && pattern[i] != pattern[j+1]){
            j = next[j];
        }
        if(pattern[i] == pattern[j+1]) j++;
        next[i] = j;
    }
}
int KMP(){
    getNext(); 
    int lenpt = strlen(pattern);
    int lentx = strlen(text);
    int j = next[0];
    int ans = 0;
    for(int i=0;i<lentx;i++){
        while(j!=-1 && text[i] != pattern[j+1]){
            j = next[j];
        }
        if(text[i] == pattern[j+1]) j++;
        if(j == lenpt - 1){
            value[ans++] = i+1-j;
            j = next[j];
        }
    }
    return ans;
}
int main(){
    scanf("%s\n%s",text,pattern);
    int ans = KMP();
    for(int i=0;i<ans;i++) cout<<value[i]<<endl;
    int len = strlen(pattern);
    for(int i=0;i<len;i++) cout<<next[i]+1<<" "; //adjust to LOGU OJ
    
}

View Code