在对字符串的操作中,我们经常要用到子串的查找功能,我们称子串为模式串,模式串在主串中的查找过程我们成为模式匹配,KMP算法就是一个高效的模式匹配算法。KMP算法是蛮力算法的一种改进,下面我们先来介绍蛮力算法。

  蛮力算法使用两个int型变量当做当前匹配位置的指针,我们假设主串的位置指针为i,模式串的位置指针为j。蛮力算法的策略便是在i和j所指的位置的字符相等时,继续向后匹配,当发生失配时,便将i回溯到本次匹配前位置的后一个位置,而将j设置为0,从而对所有位置完成逐一比对,通过观察i和j是否越界判断整体匹配是否成功,若模式串位置指针j越界,显然此前所有位置都已完全匹配,那么也就可以返回i-j也即完全匹配时主串中匹配子串的下标位置。

int brute(char * mString ,char * subString) {
int i = ,j = ;
int m = strlen(mString),
n = strlen(subString);
while ( i < m &&j < n) {
if (mString[i] == subString[j]) {
i++;
j++;
}
else {
i -= j - ;
j = ;
}
}if (j >= n)
return i - j;
     if(i>=m)
return -1;
}

  通过观察,我们不难发现,如果主串中有大量与模式串相似的字符,从而每次比对都要比较到模式串的最后一个字符才发生失配,从而在每个比较位置都与模式串进行模式串长度n次的比较,而一共有主串长度m次的比较位置。从而时间复杂度达到了O(m*n)。这种情况在串中字符的种类较少时尤其容易发生,如主串为“00000000000001”,模式串为“00001”。

  显然,在每次发生失配时,i指针都要回溯到原来位置的下一个位置,而j指针则是复位至0,一切又从下一个位置从新开始。然而这种做法其实浪费了大量之前比较时所获得的有用信息。在发生失配时,可以分为两种情况:主串失配字符位置i之前的若干字符如果和模式串中的某个真前缀相同(为了避免丢失信息,必须选择最长的一种情况),那么显然只需将模式串与主串中对应字符对齐,而在当前位置与模式串的这个真前缀后的那个字符进行比较即可;而如果不能找到这样的情况,显然主串失配位置前的字符都无法派上用场,从而直接将模式串的开头与当前位置对齐并进行比较即可(注意,如果这种情况中开头位置依然失配,只需让i自增,从下一个位置开始和整个模式串的匹配即可)。这两种情况中的i指针始终没有回溯。因此在最坏情况下的时间复杂度也不会超过O(m)。

int KMP(char *mString ,char* subString) {
int i = ,j = ;
int mlen = strlen(mString);
int slen = strlen(subString);
int* next = (int*)malloc(sizeof(int)*strlen(subString));
getNext(subString, next);
while (i < mlen && j < slen) {
if (mString[i] == subString[j]) i++, j++;
else {
if (j == )i++;
j = next[j];//子串指针前移至最长公共前后缀的下标处
}
}
if(j>=slen)
return i - j;
if (i >= mlen)
return -;
}

  细心的读者可能发现,这里与蛮力算法不同的是多了一次主串适配位置之前字符与模式串前缀字符的比较操作,这样看来似乎复杂度没有改善,其实不然,由于发生失配时主串当前位置i和模式串当前位置j之前的所有字符必然相等,所以这种比较操作只取决于模式串,也就是说我们只需找出模式串每个位置的最长公共前后缀即可。我们只需在比较之前对模式串进行分析处理,将对应信息存入next[]数组来制表以供查询即可将这种操作简化为O(1)的复杂度。而这种预处理操作实际上只需O(n)的复杂度。

  next[]数组的获取我们可以使用递推的策略完成,由于next[]数组中存放的数值为当前位置最长公共前后缀的长度,也即最长公共前缀之后一个字符的下标位置,显然如果之前位置字符与之前位置的最长公共前缀的下一个字符相同,那么当前位置的最长公共前后缀的长度只需增加一即可(特别的,如果当前位置和当前位置最长公共前后缀的后一个字符相等,说明这次比较必然失败,于是应该取其此处不相等的最长公共前后缀);而如果这两个字符不同,我们希望找到之前字符稍短的一个公共前后缀,也即在之前字符的next[]值上再取一次next[]的值,再比较这个值处的字符和之前位置处字符,如果相等取此值加一即可,如果不相等,则继续循环,由于next[]数组中的值必然比数组内值小,所以循环一直继续下去必然收敛于0。当值为0时。取当前值为0即可。

void getNext(char * string, int * next) {
int len = strlen(string);
int i = ,j = ;
next[] = ;
while (j < len) {
if (i == )
next[++j] = ;
if (string[j] == string[i]) {
i++; j++;
         next[j] = string[j]!=string[i]?i:next[i];//避免出现重复比较
}
else
i = next[i]; }
}

纯属个人理解,如有错误,欢迎指出

串的模式匹配和KMP算法的更多相关文章

  1. 串的模式匹配,KMP算法

    串的模式匹配 现考虑一个常用操作,在字符串s(我们称为主串)中的第pos开始处往后查找,看在主串s中有没有和子串p相匹配的的,如果有,则返回字串p第一次出现的位置. 暴力求解 int Index(ch ...

  2. 《数据结构》之串的模式匹配算法——KMP算法

    //串的模式匹配算法 //KMP算法,时间复杂度为O(n+m) #include <iostream> #include <string> #include <cstri ...

  3. 串的应用与kmp算法讲解--学习笔记

    串的应用与kmp算法讲解 1. 写作目的 平时学习总结的学习笔记,方便自己理解加深印象.同时希望可以帮到正在学习这方面知识的同学,可以相互学习.新手上路请多关照,如果问题还请不吝赐教. 2. 串的逻辑 ...

  4. 字符串模式匹配之KMP算法图解与 next 数组原理和实现方案

    之前说到,朴素的匹配,每趟比较,都要回溯主串的指针,费事.则 KMP 就是对朴素匹配的一种改进.正好复习一下. KMP 算法其改进思想在于: 每当一趟匹配过程中出现字符比较不相等时,不需要回溯主串的 ...

  5. 【模式匹配】KMP算法的来龙去脉

    1. 引言 字符串匹配是极为常见的一种模式匹配.简单地说,就是判断主串\(T\)中是否出现该模式串\(P\),即\(P\)为\(T\)的子串.特别地,定义主串为\(T[0 \dots n-1]\),模 ...

  6. 模式匹配的KMP算法详解

    这种由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现的改进的模式匹配算法简称为KMP算法.大概学过信息学的都知道,是个比较难理解的算法,今天特把它搞个彻彻底底明明白白. 注意到这 ...

  7. 字符串模式匹配之KMP算法的next数组详解与C++实现

    相信来看next数组如何求解的童鞋已经对KMP算法是怎么回事有了一定的了解,这里就不再赘述,附上一个链接吧:https://www.cnblogs.com/c-cloud/p/3224788.html ...

  8. 串的模式之kmp算法实践题

    给定两个由英文字母组成的字符串 String 和 Pattern,要求找到 Pattern 在 String 中第一次出现的位置,并将此位置后的 String 的子串输出.如果找不到,则输出“Not ...

  9. 模式匹配之Kmp算法

    Kmp: 算法定义借鉴wikipedia: http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm#KMP_ ...

随机推荐

  1. MySql学习(七) —— 查询性能优化 深入理解MySql如何执行查询

    本篇深入了解查询优化和服务器的内部机制,了解MySql如何执行特定查询,从中也可以知道如何更改查询执行计划,当我们深入理解MySql如何真正地执行查询,明白高效和低效的真正含义,在实际应用中就能扬长避 ...

  2. C#中字符和字符串总结

    Char类是C#提供的字符类型,String是C#提供的字符串类型. 字符: Char类在C#中表示一个Unicode字符. Char类只定义一个Unicode字符. Char类常用的方法及说明如下: ...

  3. [Spark] - HashPartitioner & RangePartitioner 区别

    Spark RDD的宽依赖中存在Shuffle过程,Spark的Shuffle过程同MapReduce,也依赖于Partitioner数据分区器,Partitioner类的代码依赖结构主要如下所示: ...

  4. 在内存充足时malloc函数分配内存失败的原因及解决

    昨天在修改自己的代码的时候,碰到了malloc函数内存分配失败,上网翻了翻,一个很可能的原因是之前的代码出现了越界操作,导致malloc分配函数所涉及的一些信息被破坏.在这个思想的指导下,今天又是郁闷 ...

  5. strpos、 strstr、 substr三个函数的对比讲解

    mixed strpos ( string $haystack , mixed $needle [, int $offset = 0 ] ) 返回 needle 在 haystack 中首次出现的数字 ...

  6. eclipse+HBASE开发环境搭建(已实践)

    开发准备: jdk1.8.45 hbase-1.2.2(windows下和linux个留一份) hadoop-2.7.2(linux一份) Linux系统(centos或其它) Hadoop安装环境 ...

  7. B/S架构与C/S架构的区别

    C/S结构,即Client/Server(客户机/服务器)结构,是大家熟知的软件系统体系结构,通过将任务合理分配到Client端和Server端,降低了系统的通讯开销,可以充分利用两端硬件环境的优势. ...

  8. JNI调用的helloworld(JNI_OnLoad映射方式)

    本示例展示JNI的基本示例,helloworld级别的,不过是用JNI_OnLoad映射的方式. 直接看代码,先看包含native method的Person.java的代码: package hel ...

  9. JS入门(二)

    关于运算符: js中判断运算符跟数学的运算符基本差不多,就是大于>,小于<,大于等于>=,小于等于<=,等于==,不等于!=,全等于===:可以看出来,跟我们印象中的判断运算符 ...

  10. 用Use Case获取需求的方法是否有什么缺陷,还有什么地方需要改进?

    一.用例解释 用例是一种在开发新系统或者软件改造时捕获潜在需求的技术.每个用例提供了一个或多个场景,该场景揭示了系统是如何同最终用户或其它系统交互的,从而获得一个明确的业务目标.用例要避免技术术语,取 ...