浅浅分享关于KMP的理解

KMP算法用于优化字符串匹配

首先想到的应当是暴力匹配字符串,具体操作如下:

输入一个待匹配字符串S,一个用于匹配的模式串P,对于S字符串中的每一个字符,都用字符串P去匹配,若其中出现了不一致,则移动继续匹配从S的下一个字符开始匹配。简单来说就是用两个for循环去判断。

由于暴力算法去匹配字符串存在许多冗余操作,其时间复杂度达到了O(n^2),因此由 D.E.Knuth、J,H,Morris 和 V.R.Pratt 共同研究出了KMP算法。KMP即分别取了三人的姓氏首字母。KMP算法的核心思想是用next数组跳过那些不可能的S[i],而直接从部分成功匹配的S[i]开始。其时间复杂度优化到了O(n)

概念解释:

字符串前缀:举例"ababc",当i = 4时(即考虑长度小于等于3的子串),其前缀分别为"a","ab","aba"。(假设下标从1开始)

字符串后缀:举例"ababc",当i = 3时(即考虑长度小于等于2的字串),其后缀分别为"c","bc"。(假设下标从1开始)

最大公共子串:举例"abab",最大公共字串即在字符串中满足:最大公共字串 = 某个前缀 = 某个后缀,且满足长度为最大。在举例中,"ab" = 前缀 = 后缀成立。因此最大公共字串长度 = 2。

下附完整代码(详解在注释中):

#include

using namespace std;

const int N = 100010; // 题目要求 1e5 的数据大小
const int M = 1000010; // 题目要求 1e6 的数据大小

int n, m;  //n, m 分别记录 p s 字符串的长度
char p[N], s[M]; //存储 p s 字符串
int ne[M]; //ne 数组与字符串 s 对应

int main()
{
	//输入
	cin.tie(0); //加快读入速度
	cout.tie(0); //加快输出速度
	cin >> n >> p + 1 >> m >> s + 1; // 从下标为 1 处开始输入

	//构建 ne 数组 ne 数组只与模式串 p 有关 存储的是模式串 p 在长度为 i 的情况下前缀与后缀相同的最大长度(最大不超过 i - 1)
	//ne[1] 默认值为1(由于长度为 1 时不存在长度小于 1 前后缀) 因此从 2 开始
	for (int i = 2, j = 0; i <= m; i++)
	{
		//每一次循环保证 p[1, j] == p[i - j + 1, i] && p[i] == p[j + 1] 成立
		while (j && p[i] != p[j + 1]) j = ne[j]; //不断寻找能够使得上面性质成立的 最大值
		if (p[i] == p[j + 1]) j++; //由于又查找到p[i] == p[j + 1], 最大公共字串长度延长一位
		ne[i] = j;
	}
	//KMP匹配过程 与构造 ne 数组过程类似
	for (int i = 1, j = 0; i <= m; i++)
	{
		while (j && s[i] != p[j + 1]) j = ne[j];
		if (s[i] == p[j + 1]) j++;
		if (j == n)
		{
			cout << i - j << " "; //输出成功匹配的子串的第一个字符下标
			j = ne[j];
		}
	}
}

值得一提的是,KMP算法主要由构造ne数组以及字符串匹配两个部分,这两个部分的代码几乎一样。这是因为对于ne数组的构造过程其实就是模式串P 与模式串P自身的匹配,使得对于每一个不同的(i, j)求得其最大公共字符串的长度 ne[j]。因此,如果弄懂了其中一个部分,另一个部分也就可以类似的进行理解了。

你可能感兴趣的:(算法,算法,c++,开发语言)