标签:
暴力解法:枚举原串起始位置,逐个匹配,复杂度O(mn)。
优化思路:失配时,前面已匹配的字符可以提供信息。
KMP算法:
对于模式串任意位置 i ,如果我们知道一个 k 使得 i 位置前的 k 个元素和模式串最开始的前 k 个元素一一相等,那么第 i 个元素失配时就可以之间从第 k + 1 个元素开始继续匹配,这就利用到了失配前的匹配信息。
于是我们想构造这样一个数组 next_val[] 使得 next_val[i] 指向第 i 元素失配时下一个匹配的位置。
例如对于模式串abcabd,它的 next_val[] 数组如下,其中 –1 表示第一个元素也无法匹配:
下标 | 0 | 1 | 2 | 3 | 4 | 5 |
字符 | a | b | c | a | b | d |
next_val | -1 | 0 | 0 | 0 | 1 | 2 |
其实这个数组还可以优化,就是要把失配提供的信息也利用上。
如果第 i 个元素已经失配,那么如果 next_val[i] 对应的元素与第 i 个元素相同,那么其实不用进行下一次比较,于是把 next_val[i] = next_val[next_val[i]]。
下面的问题就是如何求解 next_val 数组。
这是个比较简单的递推问题,即“如果已知 next_val[1,…,k-1],如何求 next_val[k]”?
结合 next_val 数组的定义和以下代码不难理解,重点是理解第6行。
void init_next() { int len = strlen(ptn); next_val[0] = -1; int i = 0, j = next_val[i]; while (i < len) { if (j == -1 || ptn[i] == ptn[j]) { next_val[i + 1] = j + 1; if (j != -1) { next_val[i] = next_val[next_val[i]]; } i++; j++; } else { j = next_val[j]; } } }
得到了 next_val 数组后,就逐个匹配,失配时模式串位置标记通过 next_val 数组跳就行了。
代码如下:
int kmp() { init_next(); int ans = 0; int str_len = strlen(str); int ptn_len = strlen(ptn); int i = 0, j = 0; while (i < str_len) { if (j == -1 || str[i] == ptn[j]) { i++; j++; } else { j = next_val[j]; } if (j == ptn_len) { ans++; } } return ans; }
吐槽:KMP算法思想虽然简单,理解起来也不复杂,但是要讲清楚太难了,不懂的话其实可以结合例子理解一下 next_val 的求法。
标签:
原文地址:http://www.cnblogs.com/xblade/p/4445177.html