如上图所示,假设现在在位置 x 处匹配失败了,如果子串 t 中在 x 前面存在与起点相同的一段字符串,分别记作 D 与 C ,又因为 AC,BD 已经匹配,所以这四个串完全相同,那么,就可以将子串向右滑动,即子串指针向前回溯一个到 C 的末尾,让 C 与 B 对齐,这样 BC 就匹配好了,所以主串指针不用回溯,继续向后比较即可。可以看出,如果存在这样的情况,则 C 的长度越长,匹配速度越快。所以在算法中,我们需要求出满足条件的最长的串 C。
看起来速度会提升不少,但是问题来了,它一步跳过那么多字符,有没有可能会漏掉中间可以成功匹配的情况呢?答案是不会。可以反证法,假如中间部分可以匹配成功,即 t 不用向右滑那么多就可以匹配成功,那么就存在更长的 C’(C向右加长)与 B’(B向左加长)相匹配,那么就有对应的 D’ (D向左加长)。这与 C 和 D 是 t 中符合条件的最长串相矛盾。
正确性没有问题了,如何实现呢?可以看出,子串 t 的回溯位置是由其本身的组成决定的,在不同位置匹配失败需要回溯的位置不同,在 t[j] 匹配失败需要回溯到的位置可以记为 next[j]。我们先不考虑 next 数组如何求,先写出算法框架。
代码示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#define Max_Strlen 1024 int next[Max_Strlen]; //已经算好 intKMP_index(String s, String t){ int i = 0, j = 0; while (i < s.length && j < t.length) { if (j == -1 || s[i] == t[j]) { i++; j++; } else j = next[j]; } if (j >= t.length) return (i - t.length); return-1; }
第六行 j == -1 是因为记 next[0] = -1
时间复杂度:O(m+n)
计算next[j]
暴力穷举法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
intnext1(String t, int j){ String str1, str2; int k = j; do { k--; str1 = subString(t, 0, k); //对t,从t[0]开始,截取长度为 k 的串 str2 = subString(t, j-k, k); } while (k && strncmp(str1, str2, k)); //strncmp比较str1与str2前k个元素是否完全相同 if (k == 0) return0; return k; }
改进穷举法
先判断尾元素是否相等,再比较串是否相等
1 2 3 4 5 6 7 8
intnext2(String t, int j){ for (int k = j - 2; k >= 0; k--) { if (t[k] == t[j-1]) if (strncmp(t, &t[j-k-1], k+1) == 0) return (k + 1); } return0; }
递推法
1 2 3 4 5 6 7 8 9 10
voidget_next(char t[], int next[]){ int j = 0, k = -1; next[0] = -1; while(j < 12) { if (k == -1 || t[j] == t[k]) next[++j] = ++k; else k = next[k]; } }