给定一个目标串,一个模式串。判断目标串是否包含模式串,返回目标串开始匹配的地址。

BF算法

BF算法采用穷举,每次不等时目标串 i加1,模式串j回退到0。平均时间复杂度为O(M*N)

static int bf(String yuan, String target){
        char[] yuanChar = yuan.toCharArray();
        char[] targetChar = target.toCharArray();
        int i = 0, j = 0;

        while (i < yuan.length() && j < target.length()){
            if (yuanChar[i] == targetChar[j]){
                i++;
                j++;
            }else {
                // i-j表示之前已经发生过的不相等情况
                i = i-j +1;
                j = 0;
            }
        }
        if (j == target.length()){
            return i-target.length();
        }else {
            return -1;
        }
    }

KMP

KMP算法是BF算法的优化,主要减少了在目标串中下标i的比较次数。模式串下标j还是会每次归零。
例如:
S串 a a a a b
T串 a a a b

第一轮比较从S[0]、T[0]开始,比较到S[3]、T[3]时,值不相等。

字符串 匹配前缀相同 Java java字符串模式匹配_后缀


按照BF算法,这时要从S[1]、T[0]再次开始比较。

可以看到S串、T串前三位是相等的。
S[0] = T[0] S[1] = T[1] S[2] = T[2]

在T[3] = b 的前面的三个字符是a a a,a a a 的前两位是 a a,后两位也是a a。
根据T[0] = T[1] = T[2]还有上面得到的相等信息,可以得到如下图所示的信息。

字符串 匹配前缀相同 Java java字符串模式匹配_kMP算法 Java_02


按照BF算法,就要拿S[1]和T[0]开始比较。由上图,完全可以从S[3]、T[2]开始下一轮的比较。

KMP算法核心:next数组

KMP使用next[j]来保存在T[j]字符前有多少个字符和T串开头的多少个字符相等,也就是最大前后缀的长度next数组是KMP算法的核心

例如 a a a的最大前后缀是 a a
a b a b的最大前后缀是 a b

默认next[0] = -1 ,表示不保存匹配信息。

T串 a a a b的next数组如下:

字符串 匹配前缀相同 Java java字符串模式匹配_后缀_03


在有了next[j]数组后,当S[i]和T[j]不匹配时,拿S[i]和T[next[j]]进行比较,这就是KMP算法主体。

先看下KMP算法、求next数组的代码实现:

static int is(String yuan, String target){
        int[] next = getNext(target);
        char[] yuanChar = yuan.toCharArray();
        char[] targetChar = target.toCharArray();
        int i = 0, j = 0;

        while (i < yuan.length() && j < target.length()){
            if (j == -1 || yuanChar[i] == targetChar[j]){
                i++;
                j++;
            }else {
                j = next[j];
            }
        }
        if (j == target.length()){
            return (i - target.length());
        }else {
            return -1;
        }
    }


    public static int[] getNext(String target)
    {
        char[] targetChar = target.toCharArray();
        int[] next = new int[targetChar.length];

        // 初始条件
        int j = 0;
        int k = -1;
        next[0] = -1;

        // 根据已知的前j位推测第j+1位,length需要-1 防止越界
        while (j < targetChar.length - 1)
        {
            if (k == -1 || targetChar[j] == targetChar[k])
            {
                next[++j] = ++k;
            }
            else
            {
                k = next[k];
            }
        }

        return next;
    }

给出一个新的模式串并结合代码来看next数组的实现:
a b a c a b a b d
首先 初始化k = -1,k这个变量的意义就是最大前后缀的大小。

next数组代码if分支next[++j] = ++k的意义

求next[3]

字符串 匹配前缀相同 Java java字符串模式匹配_字符串 匹配前缀相同 Java_04


字符串 匹配前缀相同 Java java字符串模式匹配_kMP算法 Java_05


进入while循环时,k = 0 ,j = 2。k是next[2]的值,是ab的最大前后缀。

字符串 匹配前缀相同 Java java字符串模式匹配_next数组_06


k = 0 表示ab还没有匹配到,当下一个字符T[2] = a过来时,拿T[K]也就是开头还没有匹配到的字符和T[j]判断。如果相等,显然aba就有了最大前后缀 为1。所以next[3] = 1;

if分支k=next[k]的意义

求next[8]

字符串 匹配前缀相同 Java java字符串模式匹配_后缀_07

(1)进入while循环时,k = 3, j = 7。

T[3] != T[7] c != b

字符串 匹配前缀相同 Java java字符串模式匹配_next数组_08


(2)

在j = 7 时,next[7] = 3; 开头三位是匹配的①等于②

在j = 3 时,next[3] = 1;开头一位是匹配的③等于④

根据以上条件可以得到⑤等于⑥ ,③等于⑥。这时候有一位匹配了,也就是next[k =3]的值,只需要拿③下一个字符去匹配T[7]的b。经过k=next[k],k 值修改为1并再次回到while循环判断。最后T[1] = T[7],所以next[8] = 2

字符串 匹配前缀相同 Java java字符串模式匹配_next数组_09

字符串 匹配前缀相同 Java java字符串模式匹配_kmp 字符串 匹配算法_10