算法之KMP算法 全新思路介绍!

KMP算法是一个经典的字符串匹配算法,也是一种常用的字符串匹配算法。在KMP算法没出现之前,大家在字符串匹配的时候,都是两个for循环嵌套完成字符串之间的匹配,这种算法称作 BF算法(暴力求解)。

当时Knuth,Morris,Pratt三为大师发现这种方法太慢了,他们发现了一个规律,有些字符串匹配失败后,不需要从头重新开始匹配下一个,就这样,在他们的努力之下,完成了这个算法,后人为了纪念这三个大神,取他们名字的首字母将这个算法称为KMP算法,KMP的思想是字符串匹配历史上的重要转折点,后续基于KMP的思想,后人又开发出来了BF算法, Sunday算法等。

下面带领大家开始KMP算法的剖析之路。

1、字符串匹配问题

题目: 给定字符串 mother = “QABADABACDE” ,判断字符串 son = “ABACD”是否是mother的亲儿子,即字符串son是否是mother的子串。
青铜求解思路:暴力求解法

bool BF(const string &mother, const string &son) {
	for (int i = 0; i <= mother.size() - son.size(); i++) {
		int j = 0;
		for (; j < son.size(); j++) {
			if (mother[i+j] != son[j]) { // 不匹配,退出
				break;
			}
		}
		if (j >= son.size()) { 
			cout << "true" << endl;
			return true;
		}
	}
	cout<< "false" << endl;
	return false;
}

这种求解思路也是大家最容易想到的匹配方法。但是在匹配过程中,参考下图,大家有没有发现什么可以优化的地方?

在这里插入图片描述

2、KMP算法思路来源

聪明的童鞋可能已经发现了,说明你跟上面的三位大神有的一拼,只不过是生不逢时 (_).
在BF算法中,上图中母串第i个元素跟子串第j个元素比较发现不一致,则子串整体往后移动一个位置,重新匹配。而聪明的KMP三位童鞋发现,子串的“ABA”部分,第一个A和最后一个A相同,何不将子串移动到i-1的位置进行匹配呢?(见下图)
在这里插入图片描述

KMP三位大神一起抽了根烟,想起了数学上的归纳方法,就从简单到复杂列举了几个例子如下:

  1. 提取出j前面的子串“ABCD” ,发现前后没有相同的元素,此时下一次比较时,i与j=0对应进行比较。

在这里插入图片描述
2. 提取j前面的子串“ABA”,发现最前面的A和最后的A相同,此时下一次比较时,i与j=1对应进行比较。
在这里插入图片描述
3.提取j前面的子串“ABCAB”,发现字符串最前面和最后面有两个字符相同:“AB”,此时下一次比较时,i与j=2对应进行比较。

在这里插入图片描述

根据归纳方法,由一般到特殊,总结出下面的表格:

j前面的字符串前后相同元素个数下次跟i比较的子串的位置 j
00
11
22
33
44
nn

3、如何表示这种规律?

KMP三位大神发现了这个规律后,开始思考如何表示呢?他们发现,j前面的字符串都是son的子串,因此是否可以根据son字符串构造出,母串第i个元素跟子串的第j个元素不同的时候,下一次j的取值呢?因此,next数组出生了。

规定: next[0] = -1;
next[k]表示:表示son字符串中**[0, k-1]**子串中前后相同字符的个数
例如:
“ABA” : next[] = {-1, 0, 0};
“ABCDABCD”: repeat[] = {-1, 0, 0, 0, 0, 1, 2, 3} // k=7时,取[0,6]字串 “ABCDABC”前面的ABC和后面的ABC相同,因此next[7]=3

next数组如何求呢?
下面给出求解方法:

void GetNext(string son, int *next) {
    next[0] = -1;  // 0位置默认为-1
    int j = 0;     // j 表示next数组的下标
    int k = -1;    // 表示前后相同字符串的个数
    while (j < son.size() - 1) {
       if (k == -1 || son[j] == son[k]) { // k==-1时,设置next[++j] = 0 如果son[j] == son[k],如果相同,设置next[++j] = ++k
       		next[++j] = ++k; 
       } else {
           k = next[k];
       }
    }
    return;
} 

4、KMP算法实现

有了next数组了,接下来就是水到渠成的事情了,下面给出KMP算法的算法框架代码:

int KMP(const string &mother, const string &son)
{
	int motherId = 0;
	int sonId = 0;
	int next[son.size() + 1];
	GetNext(son, next);
	
	while (motherId < mother.size() && sonId < son.size()) {
		if (sonId == -1 || mother[motherId] == son[sonId]) {  // 字符相同,则继续比较下一个
			sonId++;
			motherId++;
		} else {        // 字符不匹配,根据next查找出下一个j的位置,继续比较。
			j = next[j];
		}
	}
	
	if (sonId >= son.size()) {
		return motherId - sonId;
	}
	
	return -1;
}

5、是否还可以优化?

按照上面给出的思路,你是否已经能够理解KMP算法了呢?如果不明白欢迎评论区留言。

在这里给大家留个疑问,夜深了,明天继续更新这个优化点。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值