字符串匹配算法(BF算法和KMP算法)

字符串匹配算法(BF算法和KMP算法)

  • BF算法
    当我们进行字符串的匹配时,最直接的方法就是主串字符与模式串字符逐个比较,如图所示:
    在这里插入图片描述

教材指出,BF算法低效且会导致主串指针回溯,可上述例子中主串指针并未发生回溯,我在学习中也产生了疑惑,究其原因,还是例子具有偶然性
看如下例子:
在这里插入图片描述
指针发生了回溯。但对于上述例子我又产生了疑问,为什么模式串不能直接后移到如下图所示位置?
在这里插入图片描述
这样不仅主串不用回溯,效率还高,这个问题需要结合代码来看

    public static int indexOf_bf(String target, String pattern, int begin){
        int n=target.length();
        int m=pattern.length();

        if (begin<0){
            begin=0;
        }
        if (n==0 || n<m || begin>=n){
            return -1;
        }

        int i=begin;
        int j=0;
        while(i<n && j<m){
            if (target.charAt(i)==pattern.charAt(j)){
                i++;
                j++;
            }
            else {
                i=i-j+1;       //回溯
                j=0;
                if(i>n-m){
                    break;
                }
            }
        }
        return j==m ? i-m : -1;
    }

上述代码中,i=i-j+1实际上就是执行主串回溯,可如果要实现上图假设,让主串指针i不发生回溯,则应去掉i=i-j+1这行代码,才能满足假设。else内语句变为:

		else {
                j=0;
                if(i>n-m){
                    break;
                }
            }

此时指针位不动,j=0,将会陷入死循环。


上述的假设已经有KMP算法的影子,我们怎么能让主串指针不回溯从而进行字符串匹配呢?

  • KMP算法
    通过上述的假设,我们发现,在匹配时主串”abccabcd“前三个元素和模式串”abcd“前三个元素相等,当第四个元素不相等时,我们可以直接将模式串后移到如下图所示位置,而不需要仅仅向BF算法一样仅移动一位
    在这里插入图片描述
    而KMP算法就是这个作用,KMP算法中的next数组,则是用来注明当主串与模式串特定位置的字符发生不匹配后,模式串应该移动到什么位置

在KMP算法中,引入了最长前缀和最长后缀的概念,如下图,当主串4号位与模式串4号位不匹配时,我们需要后移模式串,因为在模式串的子串”abca“中,最长前缀=最长后缀的情况是:模式串[0]=模式串[3],所以模式串可直接后移到如下图所示位置
在这里插入图片描述
此时next[4]=1,思考时,我们可以把这个过程想象为模式串后移,可在计算机中,next[4]=1的含义是当模式串4号位与主串不匹配时,主串当前位与模式串1号位比较

同理,当主串5号位与模式串5号位不匹配时,最长前缀=最长后缀的情况是:模式串[0],模式串[1] = 模式串[3],模式串[4],next[5]=2,后移结果如图所示
在这里插入图片描述
以下是KMP算法以及求next数组的代码实现

public static int indexOf_kmp(String target, String pattern, int begin){
        int n=target.length();
        int m=pattern.length();
        int[] next;

        if (begin<0){
            begin=0;
        }
        if (n==0 || n<m || begin>=n){
            return -1;
        }

        next=getNext(pattern);
        int i=begin;
        int j=0;

        while(i<n && j<m){
            if (j==-1 || target.charAt(i)==pattern.charAt(j)){
                i++;
                j++;
            }
            else{
                j=next[j];
                if (n-i+1<m-j+1){
                    break;
                }
            }
        }
        return j==m ? i-m : -1;
    }

求next数组

public static int[] getNext(String pattern){
        int j=0;
        int k=-1;
        int[] next=new int[pattern.length()];
        next[0]=-1;

        while(j<pattern.length()-1){
            if (k==-1 || pattern.charAt(j)==pattern.charAt(k)){
                j++;
                k++;
                next[j]=k;
            }
            else{
                k=next[k];
            }
        }
        return next;
    }

想要理解求next数组的实现代码,我们首先要清楚在求next数组时,使用了递归的思想。
算法过程:
1> 约定next[0]=-1,-1表示下次匹配从主串当前位的下一位开始比较,则有next[1]=0
2> 对模式串中j号位字符,设next[j]=k,表示在 模式串[0]~模式串[j-1] 中存在长度为k的相同前后字符串,即 模式串[0]~模式串[k-1] = 模式串[j-k]~模式串[j-1] ,k取得是最大值
2>对于next[j+1]而言,求 模式串[0]~模式串[j] 中相同前后缀子串的长度k,需要比较前缀子串 模式串[0]~模式串[k] 与后缀子串 模式串[j-k]~模式串[j] 是否匹配,这又是一个模式匹配问题
1.如果 模式串[k]=模式串[j]模式串[0]~模式串[k-1],模式串[k] = 模式串[j-k]~模式串[j-1],模式串[j] 存在相同前后缀子串,长度为k+1,则 模式串[j+1] 的next[j+1]=k+1=next[j]+1
2.如果 模式串[k] != 模式串[j] ,在 模式串[0]~模式串[j] 中寻找较短的相同前后缀子串,较短前后缀子串长度为next[k],则k=next[k]


但是,在实际计算过程中,next数组的计算算法还是有些问题,如下图所示,会出现某些不必要的比较
在这里插入图片描述
改进next数组算法如下:

public static int[] getNext_improve(String pattern){
        int j=0;
        int k=-1;
        int next[]=new int[pattern.length()];
        next[0]=-1;

        while(j<pattern.length()-1){
            if (k==-1 || pattern.charAt(j)==pattern.charAt(k)){
                j++;
                k++;
                if (pattern.charAt(j)==pattern.charAt(k)){	//增加部分
                    next[j]=next[k];
                }
                else{
                    next[j]=k;
                }
            }
            else{
                k=next[k];
            }
        }
        return next;
    }

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值