KMP字符串匹配

一般思路

        判断s中是否含有字符串t。

        一般思路为:从s中首字符开始,依次与t中进行比对,直到t结尾或者某一个位置两者不同 。如果到t的结尾,则表示s中含有t。如果有一个位置不相同,那么从s中下一个字符开始,再次与t中字符比对。如下:

    i = 0,j = 0;
    for(;i<pl;i++){//pl为p字符串的长度,依次遍历它的每一个字符串
        int start = i;//记录此时开始比较的字符串的下标
        while(i<pl && j<len){//len子串的长度
            if(p[i] == s[j]){
                i++;
                j++;
            }else{
                i = start;
                j = 0;
                break;
            }
        }
        if(j >= len){
            printf("%d ",start);
            j = 0;
            break;
        }
    }

        这样的比较,每一次遇到不同的时候都需要从t串的第一个字符再次开始比较,因为最坏的时间复杂度为O(m*n)+——其中m,n分别表示s,t两个字符串的长度。KMP算法避免了每一次都从t串的第一个字符开始比较。

kmp思路

        如下图红框部分——上面为s串,下面为t串:


图一

        运行到红框位置时,t的下标为6,s的下标为8,s与t的字符不相同。但在此之前的字符串"RUQWPR",s与t是相同的。KMP算法正是利用了这一点对字符串匹配进行了优化。

        对于字符串中的每一个字符(记下标为x),都会存在一截字符串,它的长度为n(可以为0),并且满足条件一:

t[0]...t[n-1]与t[x-n+1]...t[x]相同

其中t[x-n+1]...t[x]表示从下标为x(含)处往前数n个字符组成的字符串。

        当找到满足条件一的字符串时,记n为0,此时表示不存在相应的字符串;并且对字符串的首个字符来说,n也是0。我们可以将所有的n值存储于int[] next中。

        对于某两个字符串的s与t来说,假设有s[i]!=t[j],那么下一次可直接进行判断s[i]与t[next[j-1]]是否相等。因为next数组保证了s[i](不含)前面的next[j-1]个字符组成的字符串与t[0]...t[next[j-1]-1]是完全相同的。例如ABCDGABCDH,当匹配到H时,源串的格式必定是...ABCDGABCD...,因此可以将ABCDGABCDH移到成如下格式:


下一步要匹配的就是G是否与s[i]相等,而G的下标必定是next[j-1]。

        上述的移动就是KMP算法相对于普通匹配方法进行的优化地方。

next数组求解

        代码如下:

private static void next(int[] n, String p) {
        n[0] = 0;
        for (int i = 1; i < p.length(); i++) {
            int k = n[i - 1];//当前位置的前一个字符的n值
            while (p.charAt(i) != p.charAt(k) && k > 0) {//k值会随着循环的变化而不断变小,当k变为0时,会出现异常,所以加个k>0
                k = n[k - 1];
            }
            if (p.charAt(i) == p.charAt(k)) {
                n[i] = k + 1;
            } else {
                n[i] = k;
            }
        }
    }
        具体的求解思路与kmp思路这一节完全一样。

匹配代码

        其具体思路与next数组求解一样。都是在某一个字符不匹配时,将模板字符移动到next[pi-1]。
private static boolean kmp(String s, String p) {
        int[] n = new int[p.length()];
        next(n, p);//求解next数组
        int pi = 0;//p的下标
        for (int x = 0; x < s.length() && pi < p.length(); ) {
            if (s.charAt(x) == p.charAt(pi)) {//如果s与p中当前字符相同,则进行下一个字符的比较
                x++;
                pi++;
                if (pi >= p.length()) {
                    out("index = "+(x-p.length()));//此处可以获取p在s中的下标
                    return true;
                }
                continue;
            }
            if (pi == 0) {//p中的第一个字符都匹配不成功,则需要从s的下一个字符开始匹配
                x++;
            }else {//某一个字符时s与p不相同,则p中下一个要匹配的字符下标为n[pi-1]
                pi = n[pi - 1];
            }
        }
        return false;
    }



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值