KMP算法

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)

首先我们明确问题,然后我们提出一种最直观的可行解法,最后改进这种解法。

问题描述

假设我们有一个字符串,称之为主串S,我们想要求主串P中是否含有模式串P,并返回匹配成功的起始位置。
举个例子,S = ababa ,P = aba ,求S中是否包含P,并返回起始位置。
答案是: 0 2

暴力解法

暴力解法是最直观的做法,暴力做法很多时候效率很低,但这正是算法改进的地方。所以遇到问题想一想暴力解法并不是一件多余的事。
最直接的做法,就是将模式串P和主串S从头开始匹配,一个字符一个字符的匹配,全部匹配成功就输出当前的主串起点,如果失败,模式串后移一位,进行下一次匹配。
这样的话,假设len(P=m,len(S)=n,时间复杂度是O(mn)
KMP算法_第1张图片

KMP算法

那么如何改进呢,刚才的例子比较简单,也比较理想,我们可能看不出来,我们再接着看下面这个例子。例子来源知乎。
KMP算法_第2张图片

我们这里把模式串所处的所有位置分为两类,必不可能成功的和有可能成功的,2、3就是必不可能成功的,因为开头就不匹配;而1、4有可能成功,因为开头匹配。
问题来了,如何从1快速找到4,而略过2、3呢?
观察一下,发现4的前缀(开头几个字符)和1的后缀(最后的几个字符)是相同的,也就是说,1已经不能完全匹配了,但是1存在匹配的一部分,我们想利用这一部分,接着往下匹配。怎么利用呢?需要探索模式串的性质,我们需要找前缀后缀相等的最大值。abcab中,前缀集合是a、ab、abc、abca;后缀集合是b、ab、cab、bcab;(这里前缀后缀不包括字符串本身,那样的话前缀必等于后缀,是没有意义的)前缀后缀相等的最大值是ab。找到之后,可以用前缀来替代后缀进行下一轮匹配。
算法思路是:
1、s[i]是否等于p[j],相等的话继续往下匹配,否则到2
2、s[i]、p[j]失配,将p回滚,接着进行匹配
3、如果p匹配完全,则输出起始下标。

        for(int i = 0,now = 0; i < m; i++){
     
            while(now!=0&&pat[now]!=s[i]){
     
                now = next[now-1];
            }
            if(pat[now]==s[i]) now++;

            if(now==n){
     
                writer.write(i-now+1+" ");
                now = next[now-1];
            }
        }

最后一个问题是构建next数组,来记录模式串在不同的长度下,对应的最长的公共前后缀。
这个问题相当于模式串和本身做匹配。主串(也是模式串)从1开始(0没有意义),模式串从0开始,如果匹配,则下标继续往下走,否则模式串下标回滚,直到两者匹配或者模式串下标变为0退无可退。

import java.io.*;
class Main{
     
    public static void main(String[]args)throws IOException{
     
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
        int n = Integer.parseInt(reader.readLine());
        String pattern = reader.readLine();
        int m = Integer.parseInt(reader.readLine());
        String str = reader.readLine();

        char[]pat = new char[n];
        char[]s = new char[m];
        for(int i = 0; i < n; i++){
     
            pat[i] = pattern.charAt(i);
        }
        for(int i = 0; i < m; i++){
     
            s[i] = str.charAt(i);
        }
        // 构建next数组
        int[]next = new int[n];
        for(int i = 1,now = 0; i < n; i++){
     
            while(now!=0&&pat[now]!=pat[i]){
     
                now = next[now-1];
            }
            if(pat[now]==pat[i]){
     
                now++;
                next[i] = now;
            }
        }
        //匹配
        for(int i = 0,now = 0; i < m; i++){
     
            while(now!=0&&pat[now]!=s[i]){
     
                now = next[now-1];
            }
            if(pat[now]==s[i]) now++;

            if(now==n){
     
                writer.write(i-now+1+" ");
                now = next[now-1];
            }
        }
        writer.flush();
        writer.close();
    
    }
}


你可能感兴趣的:(算法)