KMP算法图文详解_JAVA

KMP算法图文详解

这几天学习KMP算法,一时不得解,查阅了很多资料,看了些视频,总算是学了点东西。小小的理解了KMP思想方法。下面是我的详细理解过程。

什么是KMP算法

KMP算法是广泛说是一个子字符串匹配方法,
主要来说是 主字符串中查找模式字符串的位置
是对暴力求解的优化
时间复杂度是O(n)

基本概念说明

s[ ]是模式串,通俗来说是较长的字符串
p[ ]是模板串,通俗来说是较短的字符串

“非平凡前缀”:指除了最后一个字符以外,一个字符串的全部头部组合。
“非平凡后缀”:指除了第一个字符以外,一个字符串的全部尾部组合。(后面会有例子,均简称为前/后缀)

部分匹配值”:前缀和后缀的最长共有元素的长度。(后面作详细讲解)。

next[ ]是“部分匹配值表”,即next数组,它存储的是每一个下标对应的“部分匹配值”,是KMP算法的核心。(后面作详细讲解)。

朴素算法实现

现在取两个数组(下标从1开始):
s[11] ,p[5]
现在要进行匹配找到 s 中的 p 字符串
原始位置如下:
在这里插入图片描述

如果匹配成功,则匹配下一位
在这里插入图片描述

如果匹配失败,则p数组后移一位
在这里插入图片描述

重新从第一个位置开始匹配。
这样的时间复杂度O(MN),字符串短影响不大,一旦字符串很大了,就必须换一种算法了

KMP在何处优化

我们可以看到,在朴素算法里面匹配时,若遇到匹配失败的,只能是 后移一位 模板串
这里可不可以进行优化呢,答案是可以的
接着朴素算法,我们看第一次匹配失败时的情况

在这里插入图片描述
在朴素算法里面,是将p数组 后移一位,重新匹配
在这里插入图片描述

我们仔细研究已匹配的字符
在这里插入图片描述
我们会发现 第一位第三位 字符是相同的
这个是KMP算法的核心,如果在已匹配的字符串中,首尾有相同的元素,我们可以直接移动 k 位 使得每次匹配失败不是只能后移一位
在这里插入图片描述
如果用 i 表示 s数组 的下标,用 j 表示 p数组 的下标
我们会发现i = 4 不会变,,j = 4变成了j = 2第二位
j变成了第二位
有什么依据吗?为什么会变成第二位呢?第二位从哪来的呢?
答:依据是,我们比对s和p,数组前面已经成功匹配的部分发现 第一位与第三位相同。
因为,在相同的字符串中,如果有首位相同的最大字符串可以将坐标j变成最大相同字符串的下标+1
从哪来?从next[]数组中来。(next[]数组是我们必须要求出来的,KMP核心数组)

求next数组

直接看案例
因为next数组只与p数组有关(s与p相同的部分,只看p即可)
所以我们直接取一个数组p[]= {‘A’,‘B’,‘C’,‘A’,‘B’,‘C’};
在这里插入图片描述
这里已将next数组给出,接下来我们一起求next数组。
先将代码放出

for (int i = 2,j = 0; i <= m; i++) {
    while(j != 0 && p[i] != p[j+1]) j = next[j];
    if (p[i] == p[j + 1]) j ++;
    next[i] = j;
}

我们知道
对next[ j ] ,是p[ 1, j ]串中前缀和后缀相同的最大长度(部分匹配值),即 p[ 1, next[ j ] ] = p[ j - next[ j ] + 1, j ]
如何理解呢?

ii下标包括的元素个数元素可能前缀可能后缀最长前后缀大小next
i = 11个AAA不比较next[1]=0
i = 22个AAB不同next[2]=0
i = 33个A,B,CA,ABC,BC0next[3]=0
i = 44个A,B,C,AA,AB,ABCA,CA,BCA1next[4]=1
i = 55个A,B,C,A,BA,AB,ABC,ABCAB,AB,CAB,BCAB2next[5]=2
i = 66个A,B,C,A,B,CA,AB,ABC,ABCA,ABCAB,ABCABAC,BC,ABC,CABC,BCABC3next[5]=3

通过表格与代码可知,next[k]存储的是,p数组中,k下标前面的(包括K)的字符串的可能的前缀组合,与后缀组合相同的最大个数。

再看代码当i = 1时只有一个字符,没有比对的意义,所以,next[1] = 0,
i = 2开始,每次比较

 while(j != 0 && p[i] != p[j+1]) j = next[j];

j代表着最大的前缀与后缀相同时的大小
当匹配到下一个字符时,发现不相同,那么j应该等于,上一个循环时的j

if (p[i] == p[j + 1]) j ++;
next[i] = j;

每一次循环将 next[i] = j; 记录着i下标下的最大匹配值

s与p匹配代码

for (int i = 1,j = 0; i <= n; i++) {

            while (j != 0 && s[i] != p[j+1]) j = next[j];

            if (s[i] == p[j+1]) j++;

            if (j == m) {
                //匹配成功
            }

如果,s[i] != p[j+1] 说明两个字符串已经不匹配了,j 要退回到最大字符串相同的位置,再将s[i] 与新的 p[j+1] 比对,如果不相同,再次退回。
说明
s[i] 是 p[j+1] 匹配,所以在 j 退回的时候,可能出现,j+1 不匹配的问题。
所以要用while循环

题目

判断两个字符串第一次匹配的位置

输入格式
第一行输入整数M,表示字符串 P 的长度。
第二行输入字符串 P。
第三行输入整数 N,表示字符串 S 的长度。
第四行输入字符串 S。
输入样例:
3
aba
5
ababa

输出样例:
0

代码

import java.io.*;

public class KMP{
    final static int N = 100010;
    final static int M = 1000010;
    static char[] p = new char[N];
    static char[] s = new char[M];
    static int[] next = new int[N];
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int m = Integer.parseInt(br.readLine()); // 模板串的长度
        String pStr = br.readLine();
        for (int i = 1; i <= m; i++) p[i] = pStr.charAt(i - 1);

        int n = Integer.parseInt(br.readLine()); // 被匹配字符串的长度
        String sStr = br.readLine();
        for (int i = 1; i <= n; i++) s[i] = sStr.charAt(i - 1);

        //求next[]数组
        for (int i = 2,j = 0; i <= m; i++) {
            while(j != 0 && p[i] != p[j+1]) j = next[j];

            if (p[i] == p[j + 1]) j ++;
            next[i] = j;
        }
		//匹配
        for (int i = 1,j = 0; i <= n; i++) {

            while (j != 0 && s[i] != p[j+1]) j = next[j];

            if (s[i] == p[j+1]) j++;

            if (j == m) {
                 System.out.println((i - m) + " ");
                 break;
            }
        }
        br.close();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值