KMP学习

一、引言

KMP算法是昨天才学习的算法,因为最初听别人讲的next数组很复杂,所以自己觉得比较繁琐。
但是在学习中,它的效率比暴力匹配快了许多,所以学习它也是很重要的。
下面是我对KMP算法的所有理解,由浅入深、逐步讲解。

二、暴力匹配

字符串匹配问题:给定一个n个字符组成的串[称为文本(text)],一个m(m<=n)个字符的串[称为模式(pattern)],从文本中寻找匹配模式的子串。

  • 如果匹配成功,即a[i + j] == b[j],则j++,继续匹配下一个字符。
  • 如果匹配失败,即没有进入while循环,则i++,j=0。每次匹配都是从模式的第一个位置开始匹配,所以j每次都要记为0。
  • 注意最后一轮子串匹配的起始位置是n-m(文本位置是从0到n-1)。在这个位置以后,再也没有足够的字符可以匹配整个模式了,因此,该算法也就没有必要再做比较了。
#include <iostream>
#include <string>
using namespace std;
int BruteFroceStringMatch(string a, string b) {
	int la = a.length();
	int lb = b.length();
	int i,j ;
	for (i = 0; i <=la-lb; i++) {
		j = 0;
		while (j < lb&&a[i + j] == b[j]) {
			j++;
			if (j == lb) return i;
		}
	}
	return -1;
}
int main() {
	string a, b;
	getline(cin, a);  //a为文本串
	getline(cin, b);  //b为模式串
	int result = BruteFroceStringMatch(a, b);
	cout << "文本匹配到字符串的起始位置为:" << result << endl;
	return 0;
}

例子:
文本a“abxabcabcaby”,模式b“abcaby”。过程如下:
1.a[0]为a,b[0]为a,匹配。执行while语句,j++。直到a[i + j] != b[j],即a[0+2]!=b[2]
在这里插入图片描述
2.a[1]和b[0]不匹配,继续执行i++。
在这里插入图片描述
3.直到a[3]和b[0]相匹配,又一次进行while循环。
在这里插入图片描述
4.一直进行for循环,直到i=n-m。此时,a[6]==b[0]。进行while循环,发现字符串匹配,输出匹配时的位置。
在这里插入图片描述
该算法的时间复杂度为O(mn),在此基础上学习优化的查找算法,KMP。

三、KMP

#include <iostream>
#include <string>
using namespace std;
int kmp(string a, string b) {
	int next[100];
	int i, j , k;
	j = 0;
	int lla = a.length(); int llb = b.length();
	next[0] = 0;
	i = 1;
	while (i < llb) {
		if (b[i] == b[j]) {  //j是第一个数的指针
			next[i] = j + 1;  
			j++;
			i++;
		}
		else {
			if (j != 0) {
				j = next[j - 1];
			}
			else {
				next[i] = 0;
				i++;
			}
		}
	}
	i = 0; k = 0;
	while (i < lla && k < llb) {
		if (a[i] == b[k]) {
			i++; k++;
		}
		else {
			if (k != 0) {
				k = next[k - 1];
			}
			else {
				i++;
			}
		}
	}
	if (k == llb) {
		return i - k;
	}
	else {
		return 0;
	}
}
int main() {
	string a, b;
	getline(cin, a);
	getline(cin, b);
	int result = kmp(a, b);
	cout << "文本匹配到字符串的起始位置为:"<<result<<endl;
	return 0;
}

3.1 前缀后缀

如果给定的模式串是:“ABCDABD”,从左至右遍历整个模式串,其各个子串的前缀后缀分别如下表格所示:
在这里插入图片描述

3.2 最大公共字符长度表

例子:
模式为a“aabaabaaa”,求最大公共字符长度的数组next。
1.设两个指针,j指向a[0],i指向a[1],当只有一个字符时,没有前缀和后缀,所以令next[0]=0。此时a[0]==a[1],所以next[1]=j+1=1,i++,j++。
在这里插入图片描述
2.由图可知,a[j]=a[1],a[i]=a[2],a[j]!=a[i]。所以回溯j=j-1,因j-1=0,所以next[i]=0,i++。
在这里插入图片描述
3.a[j]==a[i],next[i]==j+1。i++,j++。
在这里插入图片描述
4.后面逐步进行匹配,得下图结果。此时a[j]!=a[i],j=next[j-1],j=2,即在字符a前(包括字符a)最大公共字符串长度为2,转到j处。
在这里插入图片描述
此时a[j]!=a[i],j=next[j-1],j=1。
在这里插入图片描述
a[j]=a[i],next[i]=j+1;
在这里插入图片描述

3.3 基于最大公共字符长度表匹配

结合之前的最大公共字符长度表,进行字符串的匹配。如果给定文本串“BBC ABCDAB ABCDABCDABDE”,和模式串“ABCDABD”,现在要拿模式串去跟文本串匹配,如下图所示:
“ABCDABD”的next数组值为“0000120”
在这里插入图片描述

  1. 因为模式串中的字符A跟文本串中的字符B、B、C、空格一开始就不匹配,所以不必考虑结论,直接将模式串不断的右移一位即可,直到模式串中的字符A跟文本串的第5个字符A匹配成功:
    在这里插入图片描述
    2.继续往后匹配,当模式串最后一个字符D跟文本串匹配时失配,显而易见,模式串需要向右移动。但向右移动多少位呢?因为此时已经匹配的字符数为6个(ABCDAB),然后根据最大公共字符长度表可得失配字符D的上一位字符B对应的长度值为2,所以此时k=2,即从C开始匹配。
    在这里插入图片描述
    3.移动后,发现C处再度失配,因为此时已经匹配了2个字符(AB),且上一位字符B对应的最大长度值为0,k=next[k-1]。
    在这里插入图片描述
    4.A与空格失配,向右移动1 位。
    在这里插入图片描述
    5.继续比较,发现D与C 失配,故k为k=next[k-1],即k=2。
    在这里插入图片描述
  2. 经历第5步后,发现匹配成功,过程结束。
    在这里插入图片描述
    通过上述匹配过程可以看出,问题的关键就是寻找模式串中最大长度的相同前缀和后缀,找到了模式串中每个字符之前的前缀和后缀公共部分的最大长度后,便可基于此匹配。而这个最大长度便正是next 数组要表达的含义。

四、学习视频

慢慢看完、认真理解就能掌握KMP
https://www.bilibili.com/video/av3246487?t=590

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值