KMP字符串匹配算法中部分匹配值的高效计算方法

最近看了KMP算法,算法很巧妙,但觉得讲解中的用前缀后缀法求部分匹配值效率有些低,在网上搜索一下,发现讲解的都是前缀后缀方法,经过我冥思苦想,想出了一个更高效的方法,在这里分享一下。

看本文前需先了解KMP算法,关于KMP的完整算法,这个讲得很不错http://www.ruanyifeng.com/blog/2013/05/Knuth–Morris–Pratt_algorithm.html
这里就不再赘述。

部分匹配值本质是字符串最后一段与字符串开头最多能有多少个连续字符相同(原因从上述教程的图中不难看出),所以只要从第二个字符开始与前面整个字符串进行对比,判断到当前字符前有多少个相同字符即可。


方法过程: 以上述教程中的用例“ABCDABD”为例子,

第一个字符的部分匹配值永远都是0,第二个字符B!=A,字符匹配值也为0;
在这里插入图片描述

下面的字符串后移,比较第3个字符C!=A,部分匹配值为0,同理第4个字符也为0,直到第5个字符A==A,部分匹配值为到此为止从第一个字符开始连续相同的字符数1;
在这里插入图片描述

相同不需要移动,继续比较下一个字符B==B,第6个字符的部分匹配值为2;
在这里插入图片描述

继续对比,第7个字符D!=C,出现不同不代表前面一部分子串也不相同,一般做法是下面的字符串后移一位,再从第7个字符向前对比,直到第一个字符都相同才能确定部分匹配值,但一位一位移效率太低,KMP算法也正是通过部分匹配值判断直接移多少位可以得到字符串完全重合的结果,所以这里也使用KMP的这个思想,对于下面一个字符串,字符C前面一个字符B对应的部分匹配值是0(注意:下面的字符中B是第二个字符,而不是上面的第6个字符,使用部分匹配值的都是要移动的字符串的),

在这里插入图片描述

所以后移 [相同子串长度2]-[部分匹配值0]=2个字符;移动后继续对比上面第7个字符D!=A,仍不相同,但已经是下面第一个字符,无法再移,部分匹配值为0;
在这里插入图片描述

最后得出部分匹配值[0,0,0,0,1,2,0]。


用java实现:

/**
 * 求KMP的部分匹配值
 * @Author drawepen
 * @Date 2020/3/10
 * @Version 1.0
 */
private static int[] getKmpPm(String str){
    int[] pm=new int[str.length()];//部分匹配值
    int i0=1,i=1;
    while (i<str.length()) {
        if (str.charAt( i ) == str.charAt( i - i0 )) {
            pm[i] = i - i0 + 1;
            ++i;
        } else {
            if (i == i0) {
                ++i;
                ++i0;
            } else {
                i0 = i - pm[i -i0 - 1];//i0=i0+((i-i0)-pm[i-i0-1])
            }
        }
    }
    return pm;
}
public static void main(String[] args) {
    Scanner in=new Scanner( System.in );
    String str2=in.nextLine();
    System.out.println( Arrays.toString( getKmpPm(str2)));
}

时间复杂度:
可以看出这个过程就是用字符串和字符串本身进行一次KMP算法过程,KMP的时间复杂度是O(n+m),所以这个方法的时间复杂度就是O(n+n),也就是O(n)。


正确性:
可以想象求解上述用例ABCDABD的第7个字符B的部分匹配值,
1、对于每个字符,前面相同的子串能找到的最长的能与某个后缀子串相同的前缀子串的长度就是它的部分匹配值,
从文章开头推荐的KMP教程中的前缀后缀法中可以看出,每个长度值最多存在一对前后缀,前后缀相同的个数不会超过最长的相同前后缀的长度,而且其他相同的前后缀都是这两个的子串,长度变化间隔也是1,所以个数刚好相等。
2、从前向后一次对比,到某字符为止第一次出现连续相同的字符个数一定是最大的,
因为如果不是,则一定存在另一个更长的前缀子串到该字符为止连续相同,但该子串的头更早出现,这个就应该是第一次出现的了。
也就是第一次确定的”部分匹配值“就是该字符的真正部分匹配值。
3、设上图中上面字符串为α,下面的字符串为β,求α中第i个字符时一定可以使用KMP算法,
因为i之前的部分匹配值一定都已经算出了,算法是从α串头开始的,如果i-k(k>0)不能得出部分匹配值,会移动β串,不会直接求后面的部分匹配值;而β串中对应的字符一定比i小,因为β的头是从对应α开头第二个字符开始的,且只会向后移动。
所以整方法是符合动态规划的,可以得出部分匹配值,中间可以使用KMP的移动方法。


如有错误,还望指出(^o^)/。

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值