KMP(二) 模式匹配算法实现


概述:本文主要在代码层面上分析KMP的实现过程,如果您还不了解KMP的推导过程,请参考
KMP(一) 模式匹配算法推导 --《部分匹配表》
KMP(二) 模式匹配算法实现
KMP(三) 字符串快速匹配示例
字符串快速匹配Demo下载


一.next 数组计算:

KMP(一) 模式匹配算法推导 --《部分匹配表》中给出的部分匹配值计算方式,用代码貌似并不容易实现(孤陋寡闻了,目前我还没想出来好的计算方式);我们再来看一种利用next 数组值实现KMP的方式;首先我们要计算next数组;
同样针对长度为 L 的字符串 T 先给出一个约定:
前缀: 字符串T[0~K], 0 <= k <=(L -1);
后缀:字符串T[K~L],0 <= k <=(L -1)
注意:此处没有组合的概念,表达不同于KMP(一) 模式匹配算法推导 --《部分匹配表》中的前缀和后缀
在给出next计算方式之前,结合KMP 算法推导中部分匹配值的计算方式,先看示例:

S = "ABAABAA"; 对应下表记为 i, 计算其next 数组;

  • i=0, 字符A前面没有字符串,默认约定next[0] = -1;
  • i = 1,字符B前面字符串"A",前缀和后缀都不存在,默认约定 next[1] = 0;
  • i = 2,字符A前面字符串"AB", 前缀后缀不纯在相等,则next[2]=0;
  • i = 3,字符A前面字符串"ABA",前缀和后缀相等部分为A,则next[3]=1;
  • i = 4,字符B前面字符串"ABAA",前缀和后缀相等部分为A,则next[4]=1;
  • i = 5,字符A前面字符串"ABAAB",前缀和后缀相等部分为AB,则next[4]=2;
  • i = 6,字符A前面字符串"ABAABA",前缀和后缀相等部分为ABA,则next[4]=3;
    即: next[L] = [-1,0,0,1,1,2,3];
    所谓的next数组就是求字符S[i]前面字符串中前后缀相等的最大长度而已。

二.next数组的作用:

与部分匹配值作用一致,next数组就是保存着当主串和模式串不匹配时,接下来与主串P[j]字符比较的模式串S[i]的位置,即S[i']=S[next[i]]。如:

主串: P = "ABABAABBB"; 下标以 j 表示
模式串(字串): S = "ABAABAA"; 下标以 i 表示

P[0~2] = S[0~2], P[3] != S[3],则比较到下标为 3 的位置时,按照KMP的原理,此时应将模式串 S 的待比较位置 移至 i = next[3] ,即S[next[3]]位置;
抽象图表示如下:

KMP(二) 模式匹配算法实现_第1张图片
图片引用来自CSDN一位大神,一时找不到来源了,抱歉

看到此处,基本已经了解next 数组的作用,以及KMP结合next的基本实现原理;下面看code;

三. next数组的代码实现

前面有对next求解的过程,然而那只是为了理解next的含义,真正算法编程却无法那样直观求出,那该如何求解next数组呢?这里用到了类似并查集的算法。
主串:abababbbab

  • 首先next[0]=-1,next[1]=0;
  • 之后每一位j的next求解:
  • 比较j-1字符与next[j-1]是否相等,
  • 如果相等则next[j]=next[j-1]+1,
  • 如果不相等,则比较j-1字符与next[next[j-1]]是否相等,
  • 如果相等则next[next[j-1]]+1,
  • 如果不相等则继续以此下去,直到next[…]=-1,则next[j]=0.

通俗易懂的话来说就是你要求解当前位的next值,则看前一位与前一位next所在位字符是否相等,相等则当前位的next等于前一位next+1,如果不等就找前一位next的next与前一位比较,如果相等,当前位的next等于那个与前一位字符相等的字符所在位置+1,如果还是不相等,则以此类推,直到出现比较位为next[]=-1,那当前位的next就等于-1。
然而在算法求解的时候,我们应该这样去理解,求解下一位的next等于当前位与当前位的next比较。算法如下:


-(NSArray *)getNextWithString:(NSString *)string{

    NSMutableArray * next = [NSMutableArray arrayWithCapacity:string.length];
    next[0] = @(-1);//初始化
    int j= 0,k= -1; //j:记录当前下标; k记录当前位的next

    while (j <  string.length - 1) {
        if (k== -1 || [string characterAtIndex:j] == [string characterAtIndex:k]) { // 比较当前(j)字符与当期位next处字符是否相等         
            next[++j] = @(++k); // 移动下标,并求解下一位的next;
        }else{
            k = [next[k] intValue]; // 回溯当前位的next
        }
    }
    return next;
}

四.KMP 算法的实现

根据上述next数组的用法,编写KMP实现代码如下:

-(NSArray *)indexKMPwithParentString:(NSString *)pString andSubstring:(NSString *)sString{
    NSArray *next = [self getNextWithString:sString]; // 计算next数组
    int index_s = 0 ,index_p = 0;    //标记 pString 和 sString 的当前比对位置
    NSMutableArray *ranges = [NSMutableArray array]; //用于保存匹配结果的range位置

    while ((index_p < pString.length) && (index_s < sString.length)) { // 一直比对至两个字符串结尾
        if ([pString characterAtIndex:index_p] == [sString characterAtIndex:index_s]) { // 当前比对位置的字符相等,则移动P和S继续下一位比对
            if ((index_s == sString.length - 1) && (index_p != pString.length -1)) { // 字串S比对至末尾,但是主串P未到末尾,即是说字串匹配成功,但是尚需确定主串的后续位置是否能匹配,故继续比对
                index_s = 0; //将字串S当前比对位置移至起始位置
                [ranges addObject:NSStringFromRange(NSMakeRange(index_p - sString.length + 1 , sString.length))]; //保存本次匹配的位置结果
                continue; // 完成一次匹配,跳出本次循环 index_p 不再移动
            }
            index_p ++; // 向后移动P的当前位置
            index_s ++; // 向后移动S的当前位置
        }else{  //当前比对位置的字符不相等,则保持主串P位置不回溯,根据next数组回溯字串S
            if (index_s != 0) { //特别处理 next[0] = "-1"的情况
                index_s = [next[index_s] intValue]; // 根据next回溯当前字串S的比对位置
            }else{
                 index_p ++; //如果起始位置不匹配,则直接移动主串P
            }
        }
    }
    return ranges;
}

不才看了两天半,算是基本弄明白这个KMP吧,代码中注释我尽量说的白明些,显得比较啰嗦,记录一下,欢迎一起探讨;

你可能感兴趣的:(KMP(二) 模式匹配算法实现)