KMP算法

kmp算法用于解决字符串匹配问题。初学者学习中,一些不太成熟的见解。若有错误,请指出。

一、暴力解法

假设文本串haystack(即被匹配字符串)为 aabaabaaf

        模式串needle(即匹配字符串)为aabaaf

i指向文本串,j指向模式串

暴力解法就是i和j指向的字符若相等,二者同时++,若不相等,就让i回退到开始位置+1,j回到字符串开始位置,再次匹配。(注意添加flag来实现找到第一个匹配位置后停止寻找)

 public int strStr(String haystack, String needle) {
        int ans=-1;
        boolean flag=false;
        for(int i=0;i<haystack.length();i++) {
            if(flag) {
                break;
            }
            for(int j=0,k=i;j<needle.length();j++,k++) {
                if(k>=haystack.length()||haystack.charAt(k)!=needle.charAt(j)) {
                    break;
                }
                if(j==needle.length()-1) {
                    ans=i;
                    flag=true;
                }
            }
        }
        return ans;

    }

优化思路:在暴力解法中,i指针本质上是在不断回头,我们可以让i指针一直向前走(上面的暴力解法中,是k代替i指针回头了)

(注:当然我们也希望j指针也不回头,但是它是要匹配的字符串,总是要试错的,所以只能尽量减少它回头的次数和范围)

如何做到让i一直往前走呢?

我们可以让模式串的指针j移动,让其指向与当前i指针指向位置匹配的地方

为什么可以这样移动呢?

因为模式字符串有相同的部分(没有的话就更简单啦)

可以让文本串中原来要当“尾”的部分现在变成“头”

那移动多少呢?又如何实现呢?

用前缀表实现 

前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。

二、KMP算法

要知道前缀表是什么,首先要清楚前缀和后缀。

1.前缀和后缀

前缀是包含首字母,不包含尾字母的所有子串

如:aabaaf

前缀:a  aa  aab  aaba  aabaa  

后缀是包含尾字母,不包含首字母的所有子串

如:aabaaf

后缀:f  af  aaf  baaf  abaaf

求出最长相等前后缀 

字符串最长相等前后缀
a0
aa1
aab0
aaba1
aabaa2
aabaaf0

那么我们就得到了aabaaf的前缀表了

aabaaf
010120

使用前缀表

我们可以发现,a对应的前缀表是2,而j指针刚好也跳转到了2坐标,是因为这是前缀表,所以2代表的长度一定是从第一个字符数起的,那么要跳转到对应部分的下一个坐标,就是2,因为坐标是从0开始的。

我们通常用next数组来存前缀表,为什么叫next数组就是因为它也代表了j要退回的位置.

next[i]含义:1.存的是以i为终点的字符串中的最长相同的前缀和后缀的长度

                    2.也代表了下一次要跳转的位置

2.实现next

首先我们先看看我们有了next数组后,字符串是怎么匹配的吧

代码:

        int j=0;
        for(int i=0;i<haystack.length();i++) {
            while(j>0&&needle.charAt(j)!=haystack.charAt(i)) { //不匹配j回退 此处j>0代表退无可退
                j=next[j-1];
            }
            if(needle.charAt(j)==haystack.charAt(i)) { 匹配成功j++
                j++;
            }
            if(j==needle.length()) {
               //匹配成功
            }
        }

其实next数组的求法就是模式串与自己匹配出来的,所以代码是一样的

代码:

        next[0]=0;
        int j=0;
        for(int i=1;i<needle.length();i++) {
            while(j>0&&needle.charAt(i)!=needle.charAt(j)) {
                j=next[j-1];
            }
            if(needle.charAt(i)==needle.charAt(j)) {
                j++;
            }
            next[i]=j;
        }
    }

我们仔细想想next数组要怎么求

3.全部代码

class Solution {
    int[] next;
    public void strStr(String haystack, String needle) {
        next=new int[needle.length()];
        getNext(needle);
        int j=0;
        for(int i=0;i<haystack.length();i++) {
            while(j>0&&needle.charAt(j)!=haystack.charAt(i)) {
                j=next[j-1];
            }
            if(needle.charAt(j)==haystack.charAt(i)) {
                j++;
            }
            if(j==needle.length()) {
               //匹配成功
            }
        }

    }
    public void getNext(String needle) {
        next[0]=0;
        int j=0;
        for(int i=1;i<needle.length();i++) {
            while(j>0&&needle.charAt(i)!=needle.charAt(j)) {
                j=next[j-1];
            }
            if(needle.charAt(i)==needle.charAt(j)) {
                j++;
            }
            next[i]=j;
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值