【数据结构】KMP算法

1.朴素算法

朴素算法即暴力匹配算法,对于长度为n的文本串S和长度为m模式串P,在文本串S中是否存在一个有效偏移i,其中 0≤ i < n – m + 1,使得 S[i… i+m – 1] = P[0 … m-1](注:下标从0开始),如果存在则匹配成功,否则匹配失败。由于在匹配过程中一旦不匹配,就要让模式串P相对于文本串S右移1,即i需要进行回溯,其时间复杂度为O(n*m)。

代码实现

class ViolentStringMatcher implements StringMatcher {

        public int indexOf(String source, String pattern) {
            int i = 0, j = 0;
            int sLen = source.length(), pLen = pattern.length();
            char[] src = source.toCharArray();
            char[] ptn = pattern.toCharArray();
            while (i < sLen && j < pLen) {
                if (src[i] == ptn[j]) {
                    // 如果当前字符匹配成功,则将两者各自增1,继续比较后面的字符
                    i++;
                    j++;
                } else {
                    // 如果当前字符匹配不成功,则i回溯到此次匹配最开始的位置+1处,也就是i = i - j + 1
                    // (因为i,j是同步增长的), j = 0;
                    i = i - j + 1;
                    j = 0;
                }
            }
            // 匹配成功,则返回模式字符串在原字符串中首次出现的位置;否则返回-1
            if (j == pLen)
                return i - j;
            else
                return -1;
        }
    }

2.KMP算法

(1)定义

  • Knuth-Morris-Pratt 字符串查找算法,简称为 “KMP算法”,常用于在一个文本串S内查找一个模式串P 的出现位置,这个算法由Donald Knuth、Vaughan Pratt、James H. Morris三人于1977年联合发表,故取这3人的姓氏命名此算法。

  • KMP算法是当遇到不匹配字符时,不是简单的向后移一位字符,而是根据前面已匹配的字符数和模式串前缀和后缀的最大相同字符串长度数组next的元素来确定向后移动的位数,

  • KMP算法的时间复杂度比朴素算法的要少,并且是线性时间复杂度,即预处理时间复杂度是O(m),匹配时间复杂度是O(n)。

(2)Next数组求解

  • next数组含义:代表在模式串P中,当前下标对应的字符之前的字符串中,有多大长度的相同前缀后缀。例如如果next [j] = k,代表在模式串P中,下标为j的字符之前的字符串中有最大长度为k 的相同前缀后缀。

  • KMP算法的核心就是求next数组,在字符串匹配的过程中,一旦某个字符匹配不成功,next数组就会指导模式串P到底该相对于S右移多少位再进行下一次匹配,从而避免无效的匹配。

  • next数组求解方法:next[0] = -1。

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

(3)代码实现

public class KMP {

    private String text;
    private String pattern;

    public void setText(String text) {
        this.text = text;
    }

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }

    public void KMPMatcher() {
        int n = text.length();
        int m = pattern.length();

        int prefix[] = computePrefix();
        int q = 0;

        int count = 0;
        for(int i = 0; i < n; i++) {

            while(q > 0 && pattern.charAt(q)!= text.charAt(i)) {
                q = prefix[q -1];
            }

            if(pattern.charAt(q) == text.charAt(i))
                q++;

            if(q == m) {
                System.out.println("Pattern occurs with shift " + ++count + "times");
                q = prefix[q - 1];
            }
        }

        if(count == 0) {
            System.out.println("There is no matcher!");
        }
    }

    //求解Next数组
    private int[] computePrefix() {
        int length = pattern.length();
        int[] prefix = new int[length];

        prefix[0] = 0;

        int k = 0;
        for(int i = 1; i < length; i++) {
            while(k > 0 && pattern.charAt(k) != pattern.charAt(i)) {
                k = prefix[k -1];
            }
            if(pattern.charAt(k) == pattern.charAt(i))
                k++;
            prefix[i] = k;
        }

        return prefix;
    }

    //求解Next数组的另一种方法
    protected int[] getNext(char[] p) {
        // 已知next[j] = k,利用递归的思想求出next[j+1]的值
        // 如果已知next[j] = k,如何求出next[j+1]呢?具体算法如下:
        // 1. 如果p[j] = p[k], 则next[j+1] = next[k] + 1;
        // 2. 如果p[j] != p[k], 则令k=next[k],如果此时p[j]==p[k],则next[j+1]=k+1,
        // 如果不相等,则继续递归前缀索引,令 k=next[k],继续判断,直至k=-1(即k=next[0])或者p[j]=p[k]为止
        int pLen = p.length;
        int[] next = new int[pLen];
        int k = -1;
        int j = 0;
        next[0] = -1; // next数组中next[0]为-1
        while (j < pLen - 1) {
            //k表示前缀的单个字符,j表示后缀的单个字符
            if (k == -1 || p[j] == p[k]) {
                k++;
                j++;
                next[j] = k;
            } else {
                //k回溯
                k = next[k];
            }
        }
        return next;
    }

    //优化KMP算法
    protected int[] OgetNext(char[] p) {
        // 已知next[j] = k,利用递归的思想求出next[j+1]的值
        // 如果已知next[j] = k,如何求出next[j+1]呢?具体算法如下:
        // 1. 如果p[j] = p[k], 则next[j+1] = next[k] + 1;
        // 2. 如果p[j] != p[k], 则令k=next[k],如果此时p[j]==p[k],则next[j+1]=k+1,
        // 如果不相等,则继续递归前缀索引,令 k=next[k],继续判断,直至k=-1(即k=next[0])或者p[j]=p[k]为止
        int pLen = p.length;
        int[] next = new int[pLen];
        int k = -1;
        int j = 0;
        next[0] = -1; // next数组中next[0]为-1
        while (j < pLen - 1) {
            if (k == -1 || p[j] == p[k]) {
                k++;
                j++;
                // 修改next数组求法
                if (p[j] != p[k]) {
                    next[j] = k;// KMPStringMatcher中只有这一行
                } else {
                    // 不能出现p[j] = p[next[j]],所以如果出现这种情况则继续递归,如 k = next[k],
                    // k = next[[next[k]]
                    next[j] = next[k];
                }
            } else {
                k = next[k];
            }
        }
        return next;
    }

    public int indexOf(String source, String pattern) {
        int i = 0, j = 0;
        char[] src = source.toCharArray();
        char[] ptn = pattern.toCharArray();
        int sLen = src.length;
        int pLen = ptn.length;
        int[] next = getNext(ptn);
        while (i < sLen && j < pLen) {
            // 如果j = -1,或者当前字符匹配成功(src[i] = ptn[j]),都让i++,j++
            if (j == -1 || src[i] == ptn[j]) {
                i++;
                j++;
            } else {
                // 如果j!=-1且当前字符匹配失败,则令i不变,j=next[j],即让pattern模式串右移j-next[j]个单位
                j = next[j];
            }
        }
        if (j == pLen)
            return i - j;
        return -1;
    }



    public static void main(String[] args) {

        KMP kmp = new KMP();

        kmp.setText("ababacabacbababababacabcbabababaca");
        kmp.setPattern("ababaca");

        kmp.KMPMatcher();

    }

}

本人才疏学浅,若有错,请指出
谢谢!

参考资料:从头到尾彻底理解KMP

    原文作者:KMP算法
    原文地址: https://blog.csdn.net/baiye_xing/article/details/75186701
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞