KMP字符串匹配算法

最近在学习KMP用法时观看视频中的讲解,理解更加深刻一些,做一个笔记!

视频地址:1.KMP字符串匹配算法实现原理 2.KMP字符串匹配算法实现代码

假设现在我们面临这样一个问题:有一个文本串T,和一个模式串P,现在要查找P在T中的位置,怎么查找呢?

1.暴力匹配

当a与b不能匹配上的时候,那么将P这个字符串整体往右移动一格
在这里插入图片描述
移动之后继续从这个位置开始匹配,如果不能成功,那么继续右移一格:
在这里插入图片描述
如果用暴力匹配的思路,并假设现在文本串T匹配到 i 位置,模式串P匹配到 j 位置,则有:
如果当前字符匹配成功(即S[i] == P[j]),则i++,j++,继续匹配下一个字符;
如果失配(即S[i]! = P[j]),令i = i - j+1,j = 0。相当于每次匹配失败时,i 回溯(并右移一格),j 被置为0。
暴力匹配的代码,如下:

	public static int getResult(String T,String P){
        int tLen = T.length();
        int pLen = P.length();
        int i = 0;
        int j = 0;
        while (i < tLen && j < pLen)
        {
            // 如果当前字符匹配成功(即T[i] == P[j]),则i++,j++
            if (T.charAt(i) == P.charAt(j))
            {
                i++;
                j++;
            }// 如果失配(即T[i]! = P[j]),令i = i - j + 1,j = 0
            else
            {
                i = i - j + 1;
                j = 0;
            }
        }
        // 匹配成功,i - j即i下标减去j的长度,即开始
        if (j == pLen)
            return i - j;
        else
            return -1;
    }

2.KMP字符串匹配算法

1.先计算前缀表(prefix table)即先对短的字符串(模式串P)求前缀表(假如P=ababa)

  • 第一步:写出该模式串P的所有前缀
  • 第二步:将每个前缀作为一个独立的字符串,然后找出它的最长公共前后缀(注意:比原始的字串要短)
    在这里插入图片描述
    一般来说不需要最后一段,然后在最前面补一个-1,然后写出前缀表:
    在这里插入图片描述
    寻找最长前缀后缀,模式串为"ababc",从左至右遍历整个模式串,其各个子串前后缀如表:
模式串的各个子串前缀后缀最大公共元素长度
a0
abab0
abaa,aba,ba1
ababa,ab,abab,ab,bab2
ababca,ab,aba,ababc,bc,abc,babc0

可以写出原模式串子串对应的各个前缀后缀的公共元素的最大长度及next数组,next 数组跟之前求得的最大长度表对比后,不难发现,next 数组相当于“最大长度值” 整体向右移动一位,然后初始值赋为-1。

字符ababc
下标值01234
最大长度值00120
next数组-10012

从左开始匹配,匹配失败之后将P串失配位置对应的next数组中值对应的位置拉到T串失配位置对齐,如图:

在这里插入图片描述
对齐之后,继续从这个位置开始匹配,当前位置还是错误的,那么继续同样步骤,对齐下标0(next对应位置值为0)的位置:
在这里插入图片描述
对齐0位置,继续匹配,但是当前位置任然不符合,但是前缀表(next数组)中对应值为-1,那么代表把"-1"这个位置对齐当前T中失配位置(将整个P右移一位):

在这里插入图片描述
对齐"-1"之后,直接从a开始匹配:
在这里插入图片描述
匹配成功:
在这里插入图片描述
如果在T中寻找所有P出现的次数,那么需要继续往后移动,按照前缀表,找2的位置,将c对齐2的位置:
在这里插入图片描述
从2位置继续往后匹配:
在这里插入图片描述

KMP字符串匹配算法代码:

	public static void main(String[] args) {
        // kmp测试
        kmpSearch("ABABABCABAABABCABAAB","ABABCABAA");
    }
	public static void kmpSearch(String text,String pattern){
        int []prefix=new int[pattern.length()];
        // 创建prefix前缀表
        createPreFixTable(pattern,prefix);
        // 将prefix后移一个位置
        movePrefixTable(prefix);
        int m=text.length();
        int n=pattern.length();
        int i=0;
        int j=0;
        while (i<m){
            if (j==n-1&&text.charAt(i)==pattern.charAt(j)){
                // 匹配成功
                System.out.println("已找到,下标开始:"+(i-j));
                // 右移继续匹配 找到prefix[j]的值对齐
                j=prefix[j];
            }
            if (text.charAt(i)==pattern.charAt(j)){
                i++;
                j++;
            }
            // 当不相等的时候,去找prefix数组里面的值
            else {
                j=prefix[j];
                // 如果移动至某一个位置 遇到-1时 统一右移,使对齐
                if (j==-1){
                    i++;
                    j++;
                }
            }
        }
    }
    // 求patter字符串ABABCABAA的prefix(前缀表)
    public static void createPreFixTable(String pattern,int []prefix){
        prefix[0]=0;
        int len=0;
        // 从第二个数开始比较(因为第一个数的对应的prefix为0)
        int i=1;
        while (i<pattern.length()){
            // 如果第i个位置的字符等于前缀长度相同位置
            if (pattern.charAt(i)==pattern.charAt(len)){
                len++;
                prefix[i]=len;
                i++;
            }else { // 不相等的时候
                // 限制len必须大于0
                if (len>0){
                    len=prefix[len-1];
                }// len已经无法向后面退了
                else { // 关键!!!  len==0
                    prefix[i]=len;
                    i++;
                }
            }
        }
    }
    // 将前缀表依次后移最左位置补-1
    public static void movePrefixTable(int []prefix){
        for (int i=prefix.length-1;i>=1;i--){
            prefix[i]=prefix[i-1];
        }
        prefix[0]=-1;
    }

效果:
在这里插入图片描述

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值