模式匹配(Java)——烤馍片算法(KMP算法)

模式匹配(Java)


模式匹配

模式匹配是数据结构中字符串的一种基本运算。
由于字符串我们学习过了,大部分操作都比较清楚,但是模式匹配相对来说操作稍微有些难度,所以我们在这里简单的进行讲述。
模式匹配的具体操作如下:给定一个子串(也称为模式串),要求在某个字符串中找出与该子串相同的所有子串。

我们在此讲述2种常见实现:

  1. 暴力匹配
  2. KMP算法

暴力匹配(BF算法)

主要思想:从主串的第一个元素开始,与模式串第一个元素相比较,相等则逐一比较,若有不同元素,主串回溯至下一个元素,与模式串的一个元素相比较,依次循环。
为了方便讲解,我们针对下面的案例来进行讲解:
要求在寻找模式串第一次在主串出现的位置,未找到则返回-1。


我们把这个过程分为5步:

  1. 首先,我们需要将主串进行遍历。
  2. 主串的每一次遍历中,与模式串进行比较,若相同比较下一个元素。
  3. 如果模式串比较结束,说明模式串成功匹配,返回主串当前下标。
  4. 如果两元素不同,说明此处匹配失败,主串继续遍历下一个元素。
  5. 若主串遍历结束,仍未成功匹配,则说明主串中无该模式串,返回-1。

我们举个例子演示一下,假设主串abdabcda,模式串abcd,模式匹配后应当得到3。

主串当前遍历到的元素/下标主串的元素模式串元素比较
a/0aa相等,比较下一个元素
a/0bb相等,比较下一个元素
a/0dc不相等,回溯,继续遍历主串
b/1ba不相等,回溯,继续遍历主串
d/2da不相等,回溯,继续遍历主串
a/3aa相等,比较下一个元素
a/3bb相等,比较下一个元素
a/3cc相等,比较下一个元素
a/3dd相等,比较下一个元素
a/3a\0模式串比较结束,匹配成功

完整代码如下

public static int bruteForceStringMatch(String str, String pattern) {
    //如果主串长度不小于模式串,则进入模式匹配
    if (str.length() >= pattern.length()) {
        //获取两串的字符数组,以便遍历
        char strOfChars[] = str.toCharArray();
        char patternOfChars[] = pattern.toCharArray();

        //两个循环控制变量
        int loopOfStr, loopOfPattern;
        //遍历主串,任意一串遍历结束,则匹配结束
        for (loopOfStr = 0, loopOfPattern = 0 ; loopOfStr < str.length() && loopOfPattern < pattern.length() ;) {
            //如果两元素相同,比较下一个元素
            if (strOfChars[loopOfStr] == patternOfChars[loopOfPattern]) {
                loopOfStr++;
                loopOfPattern++;
            } else {
                loopOfStr -= loopOfPattern - 1;//主串下标回溯
                loopOfPattern = 0;//模式串下标重置
            }
        }

        //模式串匹配结束,表示匹配成功
        if (loopOfPattern == pattern.length()) {
            return loopOfStr - loopOfPattern;//主串中模式串第一次出现的位置
        }
    }

    //模式匹配失败
    return -1; 
}

时间复杂度设主串和模式串的长度分别为m,n,则它在最坏情况下的时间复杂度是O(m*n)。


KMP算法

KMP算法

主要解决了BF算法的回溯问题,从而降低了时间复杂度。他的时间复杂度为O(m+n)。

主要思想:KMP算法的关键是利用匹配失败后的信息, 尽量减小两串的匹配次数,以达到快速匹配的目的。通过一个next[]数组寻找最长且相同的前缀和后缀,以减少匹配次数。


我们举个例子,来看看KMP算法是怎么工作的
主串:AAAAAB
模式串:AAAB

BF算法求解时:
在这里插入图片描述
我们在匹配时会发现,第一次中模式串与主串只有第四个元素不相同,其他元素相同。同时我们发现,模式串中前3个元素是相同的,我们不妨想想,第二次匹配时,模式串的前两个字母还有必要去比较吗?

显然这两次的比较是没有必要的,那么我们就要借助next[]数组来帮忙了。


模式串AAAB的next[]数组值为{-1,0,1,2},我们在后面会讲解next[]数组如何求取。我们在第一次匹配时,第四个元素不同,模式串下标移至next[3]的位置,即2,也就是下一次从第三个A的位置开始匹配,直接跳过了前两个A,减少了匹配时比较次数。之后是类似的操作。

我们将KMP算法匹配的过程也分为5步:

  1. 首先,我们需要将主串进行遍历。
  2. 主串的每一次遍历中,与模式串进行比较,若相同比较下一个元素。
  3. 如果模式串比较结束,说明模式串成功匹配,返回主串当前下标。
  4. 如果两元素不同,说明此处匹配失败,模式串下标更新至next[]值的位置,主串继续遍历下一个元素。
  5. 若主串遍历结束,仍未成功匹配,则说明主串中无该模式串,返回-1。:

KMP算法求解时:
在这里插入图片描述

KMP算法代码如下

public static int KMP(String str, String pattern) {
	//如果主串长度不小于模式串,则进入模式匹配
    if (str.length() >= pattern.length()) {
    	//获取next数组
    	int next[] = getNext(pattern);
    
        //获取两串的字符数组,以便遍历
        char strOfChars[] = str.toCharArray();
        char patternOfChars[] = pattern.toCharArray();

        //两个循环控制变量
        int loopOfStr, loopOfPattern;
        //遍历主串,任意一串遍历结束,则匹配结束
        for (loopOfStr = 0, loopOfPattern = 0 ; loopOfStr < str.length() && loopOfPattern < pattern.length() ;) {
            //如果两元素相同,或模式串全部匹配失败,比较下一个元素
            if (loopOfPattern == -1 || strOfChars[loopOfStr] == patternOfChars[loopOfPattern]) {
                loopOfStr++;
                loopOfPattern++;
            } else {
                loopOfPattern = next[loopOfPattern];//模式串下标置为next值
            }
        }

        //模式串匹配结束,表示匹配成功
        if (loopOfPattern == pattern.length()) {
            return loopOfStr - loopOfPattern;//主串中模式串第一次出现的位置
        }
    }

    //模式匹配失败
    return -1; 
}

next[ ]数组

经过上面的例子我们发现,next[]数组的求取,是KMP算法的最重要的一环,那么next[]数组究竟应该怎么求呢?

next[]数组实际上存储了模式串每一个元素的前缀与后缀相同的最大长度(不包括自身),因此在匹配时造成了一种跳跃式匹配。我们还是用上面的模式串来解释:AAAB

注意:我们默认把第一个元素的next值设为-1

前缀后缀最大长度
0-1
10
2AA1
3A,AAAA,A2

next[loopOfPattern] = nextValue, 我们这里利用递归的思想求出next[loopOfPattern+1]的值:

  1. 如果p[loopOfPattern] = p[nextValue],则next[nexValue+1] = next[nextValue] + 1;
  2. 如果p[loopOfPattern] != p[nextValue],则令nextValue = next[nextValue],如果此时p[loopOfPattern] == p[nextValue],则next[loopOfPattern+1] = nextValue+1;
  3. 如果不相等,则继续递归前缀索引,令nextValue=next[nextValue],继续判断,直至nextValue=-1(即nextValue=next[0])或者p[loopOfPattern]=p[nextValue]为止

国际惯例,上代码


getNext方法的实现

private static int[] getNext(String pattern)
{
	//获取两串的字符数组,以便遍历
    char patternOfChars[] = pattern.toCharArray();
    //创建next数组
    int[] next = new int[pattern.length()];
    int nextValue = -1, loopOfPattern = 0;//初始化next值及模式串下标
    next[0] = -1;//这里采用-1做标识
    while(loopOfPattern < pattern.length() -1)
    {
	    //获取next数组
        if(nextValue == -1 || patternOfChars[loopOfPattern] == patternOfChars[nextValue])
        {
            nextValue++;
            loopOfPattern++;
            next[loopOfPattern] = nextValue;
        } else {
            nextValue = next[nextValue];
        }
    }
    return next;
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值