KMP-其实也不难

引入:寻找子串在源串中的起始位置。

传统C++代码如下:

#include<iostream>
#include<string>
using namespace std;


//KMP---常规操作
int find_substr_location(string str, string pattern)
{
	int size1 = str.size();
	int size2 = pattern.size();
	for (int i = 0; i <= size1-size2; i++)
	{	
		int Flag = true;
		for (int j = 0; pattern[j]!= '\0'; j++)
		{
			if (str[i + j] != pattern[j])
			{
				Flag = false;
				break;
			}
			else;
		}
		if (Flag == true)
		{
			return i;
		}
		else;
	}
	return -1;
}

返回值:-1和其他,对应找不到和找到子串的起始位置下标。n = |S| 为串 S 的长度,m = |P| 为串 P 的长度,考虑最坏的情况即最后一个字符失配对应的时间复杂度为:O(n) = |P| *( |S| - |P| + 1) 考虑到主串一般远远大于子串,所以O(n) = |P|*|S|

字符串比较只能依次比较没有办法减少,所以考虑减少比较趟数。

核心:跳过不可能匹配成功的字符即依次失配后源串索引的移动不再是加一。

whiskycocktail
@whiskycocktail

针对模式串引入了next数组,定义::next[i] 表示 P[0] ~ P[i] 这一个子串,使得 前k个字符恰等于后k个字符 的最大的k,k<i+1.

next数组
@whiskycocktail

改进算法规则:失配之后源串索引移动很多位,跳过不可能匹配的字符位置,怎么确定移动多少位?

@whiskycocktail

第一次比较,模式串失配位置为S[3]、P[3]、模式串下标记为p,源串下表记为s,此时失配移动后的模式串下标p = next[p-1]=1、s=3。

第二次比较,起始位置S[3]、P[1],模式串失配位置下标为6,此时失配移动后的模式串下标p = next[p-1]=3,s=8。

第三次比较,起始位置S[8]、P[3]、匹配成功,返回s-p即可。

使用暴力构造next数组实现的KMP代码如下:

//字符串截取方法(start,end)前闭后闭区间
string str_truncation(string str, int start, int end)
{
	string s="";
	int size = str.size();
	if (start <= end && 0 <= start &&start < size && 0 <= end && end < size) {
		if (start == end) {
			s += str[start];
		}
		else {
			for (int i = start; i <= end; i++) {
				s += str[i];
			}
		}
	}
	else;
	return s;
}
// 暴力构建next数组
void get_next(string pattern, vector<int> &next)
{
	int size = pattern.size();
	int i = 0;
	next.push_back(0);
	for (int x = 1; x < size; x++)
	{
		int Flag = false;
		//下标为x的索引对应的next数组的值
		for (i = x; i >= 1; i--) {
			if (str_truncation(pattern, 0, i-1) == str_truncation(pattern, x - i + 1, x)) {
				next.push_back(i);
				Flag = true;
				break;
			}
		}
		if (Flag==false) {
			next.push_back(0);
		}
	}
}
int  KMP(string str, string pattern)
{
	int size = str.size();
	vector<int> next;

	get_next(pattern, next);
	int tar = 0;
	int pos = 0;
	while(tar <size) {
		if (str[tar] == pattern[pos]) {
			tar++;
			pos++;
		}
		//避免第一个字符就不匹配索引为负值
		else if (pos) {
			pos = next[pos - 1];
		}
		else {
			tar++;
		}
		if (pos == pattern.size()) {
			return tar - pos;
		}
	}
	return -1;

}
int main()
{
	//测试组()
	//string str1, pattern1;
	//cin >> str1 >> pattern1;
	//string str2, pattern2;
	//cin >> str2 >> pattern2;
	//cout << find_substr_location(str1, pattern1) << endl;
	//cout << find_substr_location(str2, pattern2) << endl;
	string str3, pattern3;
	cin >> str3>> pattern3;
	cout << KMP(str3, pattern3) << endl;

	//测试字符串截取函数str_truncation
	//string s = "sdsdfhhghg";
	//cout<<str_truncation(s,8,8) << endl;

	//测试 get_next方法
	//string s = "abcdefghabcd";
	//vector<int> vec;
	//get_next(s, vec);
	//for (int i = 0; i < vec.size(); i++) {
	//	cout << vec[i] << " ";
	//}
	return 0;
}

问题及解决方案:

下文均以tar、pos来依次代替源串索引、模式串索引。

1.0 考虑到字符比较不相等时会取其失配位置前一位对应的next值为新的pos即 pos= next[pos-1],可能从第一个元素就失配会造成越界访问,所以在更新pos位置时判断是否pos为0,不是则pos= next[pos-1],否则tar+=1.

2.0 字符串截取方法先使用了substr(start,n)方法,浪费大量时间找问题,居然最后是使用的函数有问题,大意了substr(int start,int n) 从start索引位置取n个字符(包括start)。所以后面自己实现了一个str_truncation来截取字符串。

现在比较的趟数已经大大减少,可否快速构建next数组?

@whiskycocktail

上图欲求X位置在next数组里的值,将X-1记为now,假设P[X]=d,P[next[now]=P[X]=d,那么易得出next[X] = next[now]+1。

如果P[X] != d,那么需要将now的位置左移,移动多少位?按照上图需要移动至now=next[now-1]即2,再来比较P[X]是否等于P[now],P[now]不等于P[X]时需要缩减now (now = next[now-1])直至P[X]=P[now],此刻如果now不等于0的条件下next[X] = next[now-1]+1。

next快速构建代码如下:

// 快速构建next数组
void get_next_fast(string pattern, vector<int>& next)
{
	next.push_back(0);
	int X = 1;
	int now = 0;
	while ( X<pattern.size() ) {
		if (pattern[now] == pattern[X]) {
			X++;
			now++;
			next.push_back(now);
		}
		//缩减now
		else if (now) {
			now = next[now - 1];
		}
		else {
			next.push_back(0);
			X++;
		}
	}
}

时间复杂度O(n)=m,再加上KMP匹配的复杂度为O(n) = n,总复杂度:O(n) = n+m 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值