KMP && exkmp && manacher

1.KMP

kmp是用来字符串匹配的一种算法,核心是next数组的使用,如果暴力去匹配,假设a[n], b[m], 那么复杂度为o(nm)的,但其实可以通过next数组(前缀与后缀的最大匹配值)来降低到o(n +m), 意思就是比如a[i + 1] != b[j + 1], 那么暴力来说,就只在从0重新开始匹配,但是其实不是每一次都返回到0的,只要返回到前缀的后一个等于b[j+1], 因为b[n ~ j] 和b[0 ~ n -1]是一样的,若b[j + 1]不匹配则,返回到b[n]看看b[n + 1]是否匹配。也就是next数组的含义。

            for (int i = 2, j = 0; i <= n; i ++)
            {
            while(j && a[i] != a[j + 1]) j = ne[j];
            if(a[i] == a[j + 1]) j ++;
            ne[i] = j;
            }
            
    
        for (int i = 1, j = 0; i <= m; i ++)
        {
            while(j && b[i] != a[j + 1])    j = ne[j];
            if(b[i] == a[j + 1])    j++;
            if(j == n)
            {
                
            }
        }
View Code

Kmp有一个循环节,就是周期通过len - next[len]求得。

2.EXKMP

exkmp是kmp的拓展,用来判断s1[k...n]与s2[0...m]的前缀最大匹配度,使用ne数组和ex数组,分别保存的是s2[k...]与s2[0...m]的前缀最大匹配度,ex是s1[k...n]与s2[0....m]的前缀最大匹配度,再用po和j代表匹配最大的起点和终点,第一种其中匹配的ne[i-po] + i小于ne[po]+po说明匹配的还在最大段里,那么ne[i-po]等于ne[i],若大于等于了则要手动匹配过去j = ne[po] + po - i与i + j开始匹配,然后更新po。然后同样的方法得到ex数组。

void getne(char *str)
{
    int i = 0, j, po, len = strlen(str);
    ne[0] = len;
    while(str[i] == str[i + 1] && i + 1 < len)
        i++;
    ne[1] = i;
    po = 1;
    for (int i = 2; i < len; i ++)
    {
        if(ne[i - po] + i < ne[po] + po)
            ne[i] = ne[i - po];
        else
        {
            j = ne[po] + po - i;
            if(j == -1) j = 0;
            while(i + j < len && str[j] == str[i + j])
            j ++;
            ne[i] = j;
            po = i;
        }
    }
}

void exkmp(char *s1, char *s2)
{
    int i = 0, j, po, len = strlen(s1), l2 = strlen(s2);
    getne(s2);
    while(s1[i] == s2[i] && i < len && i < l2)
    i ++;
    ex[0] = i;
    po = 0;
    for (int i = 1; i < len; i ++)
    {
        if(ne[i - po] + i < ex[po] + po)
        {
            ex[i] = ne[i - po];
        }
        else
        {
            j = ex[po] + po - i;
            if(j == -1) j = 0;
            while(i + j < len && j < l2 && s1[j + i] == s2[j])
            j ++;
            ex[i] = j;
            po = i;
        }
    }
}
View Code

3.MANACHER

用来判断一个字符中的最大回文串,利用回文串的性质,降低复杂度。用p[i]表示以s[i]为中心的最大回文串半径,p[i] - 1就是原来的原串匹配长度。那么问题就变成了求p[i],首先字符有奇偶,偶数不好操作,在每个字符中间加入‘#’,s[0]加入‘@’(防止越界)。

同意用id来表示最大回文串的中心,p[id] + id就是要匹配的字符。那么产生的那种情况,一种是mx大于i 那么根据回文串就会有一个对称的点 2 * id - i 比较p[2 * id ] - i 与mx - i的大小,然后手动匹配,若mx小于等于i,那么就要手动更新i + p[i] 和i - p[i]。再更新id和mx。

        int len = strlen(str);
        for (int i = len; i >= 0; i --)
        {
            str[(i << 1) + 1] = '#';
            str[(i << 1) + 2] = str[i];
        }
        str[0] = '@';
        len = len * 2 + 2;
        manacher(str, len);

void manacher(char *s, int len)
{
    p[0] = 1;
    int mx = 0, id = 0;
    for (int i = 1; i < len; i ++)
    {
    
        p[i] = mx > i ? min(mx - i, p[id * 2 - i]) : 1;
        while(s[i + p[i]] == s[i - p[i]])
            p[i] ++;
        if(i + p[i] > id + p[id])
        {
            id = i;
            mx = i + p[i];
        }
    }
}
View Code

猜你喜欢

转载自www.cnblogs.com/xwdzuishuai/p/11246780.html