序言
当简友们看到这篇文章的时候,我默认大家都已经了解过BF算法了,如果有对BF算法不了解的,建议可以先看下我上一篇文章:传送门
KMP简介
KMP简单来说是三位大牛:D.E.Knuth、J.H.Morris和V.R.Pratt共同发现的,看到这三位大牛的名字,我想你应该明白为什么叫KMP算法了,KMP算法要解决的问题就是在字符串(也叫主串)中的模式(pattern)定位问题。说简单点就是我们平时常说的关键字搜索。模式串就是关键字(接下来称它为P),如果它在一个主串(接下来称为T)中出现,就返回它的具体位置,否则返回-1(常用手段)。
BF算法存在的问题以及KMP解决的问题
其实BF算法存在的问题和KMP要解决的问题本身是一个问题,因为KMP要解决的问题恰好就是BF算法所存在的问题,在上篇文章其实我就有介绍BF算法存在的问题,这里我还是再简单介绍一下吧:
首先我们先看下BF算法的实现代码:
@param source 原字符串
@param target 匹配字符串
@return 成功返回索引 失败返回-1
*/
- (int)bruteForce2WithSource:(NSString *)source target:(NSString *)target {
//主串索引
int i = 0;
//字串索引
int j = 0;
while (i < source.length && j < target.length) {
//依次来一个一个对比
if ([source characterAtIndex:i] == [target characterAtIndex:j]) {
i++;
j++;
}else {//索引回溯 i的索引变化为下一个也就是 (i- j +1)而j = 0从头开始
i = i - j + 1;
j = 0;
}
}
//到这一步 判断如果j == 字符串长度证明匹配成功
if (j == target.length ) {
return i - j;
}
return -1;
}
了解BF算法的人应该都知道有一个痛点,因为它的时间复杂度是M*N的,每一次匹配之后,当遇到匹配不成功的时候,又要重头开始去匹配,这是没有必要的,例如下面的例子:
很明显我们可以看到在这里C 和 D 无法匹配了,这时候如果按照BF算法的解法,我们理所当然就应该是拿A和B去匹配,这里很明显是没必要的,我们可以直接像下图一样匹配:
这里我就不用给大家证明为啥了吧,看不出来的一个一个试一下就清楚了,看到这里,我想大家应该知道KMP是为了解决一个什么问题了。
next数组的求法
大家都称这个存放j下一个匹配位置的数组叫做next,那我这里也就叫它next数组吧,从字面上来看 我们也知道,这个next数组其实就是存放下一个要匹配的位置,也就是要移动的位数,下面我们不废话直接上代码(有详细的注释 不要为难我去解释到底next数组到底怎么生成了好不好?):
/**
获取next数组
@param pString 匹配字符串
@return next数组
*/
- (NSArray *)getNextArray:(NSString *)pString {
NSMutableArray *nextA = [NSMutableArray arrayWithCapacity:pString.length];
//定义前缀索引
int j = -1;
//定义后缀索引 大于前缀索引
int i = 0;
//给next数组的第0个元素赋值为-1
nextA[0] = @(-1);
while (i < pString.length) {
if (-1 == j || [pString characterAtIndex:i] == [pString characterAtIndex:j]) {
//当前缀和后缀相等时
i++;
j++;
//给next数组的元素赋值
nextA[i] = @(j);//这里说明一下 假设pString是 @"aba" 第0个元素的a 和 第2个元素的a相等 那么next[2]就应该是1 也就是要往后挪动一个位置
}else {
//当两个字符不相等的时候 我们就需要回溯 那具体回溯的位置是哪里呢? 一句next数组的概念 它里面存放的元素是下一次要移动的位数 所以我们的j = nextA[j]
j = [nextA[j] intValue];
}
}
return nextA;
}
KMP算法实现
#pragma mark - KMP算法
#pragma mark -
/**
KMP算法
@param tString 主串
@param pString 字串
@return 匹配成功返回匹配成功位置 失败返回-1
*/
- (int)KMP:(NSString *)tString pString:(NSString *)pString {
//主串tString的索引
int i = 0;
//字串pString的索引
int j = 0;
//获取主串和字串的字符串长度(特别注意因为使用length获取的长度是NSUInteger类型的,直接和它对比会出现问题 所以这里我都强制转换为int)
int tLength = (int)tString.length;
int pLength = (int)pString.length;
//获取next数组
NSArray *nextArray = [self getNextArray:pString];
while (i < tLength && j < pLength) {
if (-1 == j || [tString characterAtIndex:i] == [pString characterAtIndex:j]) {
i++;
j++;
}else {
//回溯到下一个位置
j = [nextArray[j] intValue];
}
}
if (j == pLength) {
return i- j;
}
return -1;
}
我们可以看到这个和BF算法唯一不同的地方就是,在匹配失效的时候 j的索引位置不在是:
i = i - j + 1;
j = 0;
而是换成了从next数组来获取:
//回溯到下一个位置
j = [nextArray[j] intValue];
这样就把KMP算法的核心问题换成了怎么获取next数组,所以大家多多理解next数组的代码吧。有什么地方没有理解的可以留言。