KMP算法

KMP算法

参考:如何更好地理解和掌握 KMP 算法?

模式串匹配算法,在一个主串(文本串s)中查找子串(模式串p)第一次匹配的位置

算法两个关键操作

  • 根据模式串建立next数组
  • 根据next数组进行子串匹配

1 Brute-Force算法(暴力求解)

两层for循环,遍历主串,从当前位置出发检查是否能与子串完全匹配。

如果匹配失败,检查主串下一个位置开始是否与子串匹配。

时间复杂度O(m*n)

没有利用到上一次匹配失败的信息以提升算法效率

2 KMP算法理解

Key Point:当匹配失败时,匹配失败位置之前的部分主串和子串是匹配的,利用这个信息,跳过中间不可能匹配成功的若干情况,以提升算法效率。

如果用上这个信息?

若已经匹配的部分有相同的前后缀,可以直接把子串移动多个位置,主串不动,使得子串的前缀和主串刚刚已经匹配部分的后缀相对应,继续比较主串和子串。

若相同的前后缀长度为已匹配部分的最大值,即最大相同前后缀,此时,最大限度的跳过了中间不可能匹配成功的情况,同时不会漏掉可能匹配成功的情况。

next数组:next[i]为模式串0~i最长相同前后缀长度(下标从0开始)

3 求next数组

难理解

初始next[0] = 0,串只有一个字符时,没有前缀后后缀,故最大相同前后缀长度为0

如果next[0], next[1], ... next[x-1]均已知,那么如何求出 next[x] 呢?

首先已经知道了next[x-1],即x之前的模式串的最大相同前后缀长度,设now = next[x-1]

则now即为最长相同前缀之后的一个字符,x为最长相等后缀之后的一个字符

  • 若p[now] = p[x]

    此时是最佳的情况,即最长相等前后缀在next[x-1]的基础上增长1位,next[x] = now + 1

    如下图所示:

    image-20230103152645708

  • 若p[now] != p[x]

    此时不是最佳情况,要检查次优情况,缩短当前最长相同前后缀长度

    注意到此时now-1=2,说明子串A最长相同前后缀长度为2,ab,子串A=子串B

    故子串A前缀ab,与子串B后缀ab为次优最长前后缀长度,更新now,再检查p[now]==p[x]

    如下图所示:

    img

实现代码:


void buildNext(int *next, char *p, int lenp){
    next[0] = 0;    //初始next[0] = 0
    int now = 0;    //now = x-1
    int x = 1;      //初始x = 1
    while(x < lenp){    //递推求next[x]
        if(p[x] == p[now]){     //p[x]=p[now]则在原来最大前后缀长度的基础上加1
            next[x++] = ++now;
//            等价于
//            now++;
//            next[x] = now;
//            x++;
        }
        else{   //p[x]!=p[now]
            if(now == 0){   //now=0,不能再缩小,next[x] = 0
                next[x++] = 0;
            }
            else{   //缩小now
                now = next[now-1];
            }
        }
    }
}

4 KMP算法实现

构建好next数组之后,使用next数组进行匹配的过程中,根据失配位置和next数组的指示调整模式串指针,主串指针不回退。

匹配过程中的所有可能情况如下:

  • 当前主串指针和模式串指针指向的字符相同(当前字符匹配成功)

    两个指针均后移一位

  • 当前字符匹配不成功

    • 模式串第一位就匹配失败(p[0]适配)

      模式串指针不动,主串指针后移一位

    • 除p[0]之外的失配,模式串指针j(j != 0)

      主串指针不动,模式串指针设为next[j-1]

图示理解如下

image-20230103164218065

实现代码

int kmp(char *s, int lens, char *p, int lenp){
    int *next = (int *)malloc(sizeof(int) * lenp);
    buildNext(next, p, lenp);
    int i = 0;	//主串指针
    int j = 0;	//模式串指针
    while(i < lens){	//遍历主串
        if(s[i] == p[j]){	//当前字符匹配成功
            i++;
            j++;
        }
        else{  //失配
            if(j == 0){		//p[0]失配,子串指针不动,主串指针后移1
                i++;
            }
            else{		//非p[0]失配,根据next数组调整模式串指针
                j = next[j-1];
            }
        }
        
        if(j == lenp){	//成功匹配模式串
            return i - j;	//返回主串中匹配成功的起点
        }
    }
    return -1;	//匹配失败
}
posted @ 2023-01-03 17:27  dctwan  阅读(26)  评论(0编辑  收藏  举报