使用OC写算法之KMP算法

序言

当简友们看到这篇文章的时候,我默认大家都已经了解过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的,每一次匹配之后,当遇到匹配不成功的时候,又要重头开始去匹配,这是没有必要的,例如下面的例子:

使用OC写算法之KMP算法_第1张图片
image.png

很明显我们可以看到在这里C 和 D 无法匹配了,这时候如果按照BF算法的解法,我们理所当然就应该是拿A和B去匹配,这里很明显是没必要的,我们可以直接像下图一样匹配:

使用OC写算法之KMP算法_第2张图片
image.png

这里我就不用给大家证明为啥了吧,看不出来的一个一个试一下就清楚了,看到这里,我想大家应该知道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数组的代码吧。有什么地方没有理解的可以留言。

你可能感兴趣的:(使用OC写算法之KMP算法)