KMP算法——字符串模式匹配

写在前面

有这样一类问题:在主串T中找到子串(模式串)P的起始位置,那么暴力算法的思想非常简单
暴力算法思想:
从主串T的开头(T[i])进行匹配,若遇到不匹配的字符,则移动到主串开头的下一个位置(T[i + 1]),再从子串的开头开始匹配
评论:暴力思想有个问题,每一次发生不匹配时,主串都要进行回溯,那么如何保证主串不进行回溯呢?这就是KMP算法

KMP算法

基本思想

  • 模式串从主串的开头字符进行匹配
  • 如果当前两字符相等,模式串和主串继续判断下一个字符的匹配情况
  • 如果当前连字符不想等,指向主串的指针不动,指向模式串的指针 j 移动到 next[j]
int KMP(string ts, string ps){
	//ts是主串,ps是模式串

	//求模式串的next数组
	vector<int> next(GetNext(ps));
	//匹配过程
	int i = 0;//指向主串的指针
	int j = 0;//指向模式串的指针
	while(i < ts.length() && j < ps.length()){
		if(j == -1 || ts[i] == ps[j]){
			++i;
			++j;
		}
		else{
			//i = i - j + 1; //暴力思想
			//j = 0;
			j = next[j];
		}
	}
	if(j == ps.length)//匹配成功
		return i - j;
	else//不匹配
		return -1;
}

理解了上面的思想只能说掌握了KMP的15%,求next数组才是关键

求next数组

问题来了:如何求模式串的next数组呢?
基本思想:

  • 解释:这里有一个新的说法:前缀串、后缀串
  • 举例说明:对于字符串“ABA”,前缀串是不包含最后一个字符的所有子串{“A”,“B”,“AB”},后缀串是不包含第一个字符的所有子串{“B”,“A”,“BA”},在所有前缀串和后缀串中最长的相等子串的长度为next[3]的值,这里next[3] = 1
  • 简单说明:为什么是next[3]?
    求next[3]需要看下标3之前的前缀串和后缀串匹配的最大长度,这里不包括下标3对应的字符

求解方法:

  • 初始时:next[0] = -1.目的是当下标为0的位置处字符不匹配时,主串和模式串都需要向后移动一个位置
  • 定义两个变量:k指向前缀串的末尾,j 指向后缀串的末尾。初始时,k = -1, j = 0
  • 如果k = -1 或者 p[k] = p[j], 这里还需要考虑下 p[k + 1] 和 p[j + 1] 这两个字符是否相等
    解释下:为什么需要判断p[k + 1] 和 p[j + 1]是否相等?
    如果不考虑他们是否相等,当前不匹配的位置是 p[j + 1], 按照next数组 应该回溯到 k + 1个位置,但是这两个位置的字符相等,回溯到该位置依然不想等,那应该回溯到next[k + 1]个位置,其实这里也就相当于从后往前数,第一个和该字符不想等的位置。
    如果不想等:next[j + 1] = k + 1;
    如果相等:next[j + 1] = next[k + 1];
  • 如果p[k] != p[j], k = next[k];
    这里稍微有点难理解,其实这里和KMP算法中,模式串回溯是同一思想,这里后缀串相当于主串,前缀串相当于模式串,当出现字符不匹配时,模式串需要回溯,也就是这里的前缀串需要回溯
vector<int> GetNext(string p){
	vector<int> next(p.length());
	p[0] = -1;
	int k = -1, j = 0;
	while(j < p.length() - 1){
		if(k == -1 || p[k] == p[j]){
			if(p[++k] == p[++j])//两个字符相等,向前跳
				next[j] = next[k];
			else
				next[j] = k; 
		}
		else{
			k = next[k];
		}
	}
}

上述思想都是用文字进行描述,难免有些晦涩难懂,可以参考如下链接
KMP

你可能感兴趣的:(#,算法)